校招面试准备——垃圾回收

先分享一些很好的博客:【我博客内的图有些也是从这些博客里来的】

触发full gc的几种情况:
  https://www.cnblogs.com/jichi/p/12588087.html
垃圾回收三连:
  https://www.cnblogs.com/chenpt/p/9797126.html
  https://www.cnblogs.com/chenpt/p/9799095.html
  https://www.cnblogs.com/chenpt/p/9803298.html

老年代的对象:

  https://www.cnblogs.com/huaiyinxiaojiang/p/7307822.html

 

一般来说,垃圾回收的主要区域是堆,所以先说堆的垃圾回收。

要回收垃圾,首先判断谁是垃圾(我是)

有两种方法:

1. 引用计数法:每个对象都维护一个引用计数器,每当有一个地方引用它,计数器+1,当引用失效的时候,计数器就-1.等到计数器为0时,对象就该被回收了。但实际上虚拟机都不用这个方法的,因为会有循环引用的情况:

2. 可达性分析

以一系列“GC Roots”作为起点,从这些roots开始向下搜索,搜索走过的路径叫做引用链,到一个对象和GC Roots连不上的时候,说明该对象是垃圾。“GC Roots”包括下面几种:

  1. 虚拟机栈(本地变量表)里的引用
  2. 方法区静态变量引用(static)
  3. 方法区中常量引用(final)
  4. 本地方法栈中的引用

这种方法的缺点是:分析过程中需要停止除垃圾回收外的其他线程,因为如果其他线程边工作 边搜索的话,对象的引用会发生变化,没法搜索。

在一个对象经过第一次可达性分析后,发现没有引用连接到它,并不一定会回收,而要进行进行判断。

判断:判断对象是否需要执行finalize()方法。如果该对象的finalize()方法没有被程序员覆盖 或者 这个对象之前已经被执行过一次这个方法了,就认为不需要执行finalize()方法。

后续:需要执行finalize()方法的对象被放入F-Queue队列中,等待稍后触发finalize()方法。如果在这个方法中,该对象与引用链中的任何一个对象连接上了,那该对象逃过一劫,不会被回收。否则还是要被回收。    不需要执行finalize()方法的对象就会被回收。

 

引用有几种?

如果有引用指向对象,就一定不会被回收吗?其实并不是,Java一共有4种引用,只有当有个对象有强引用时,才能保证它不会被当垃圾回收了:

强引用:普遍存在的引用。当一个对象有强引用时,他一定不会被当成垃圾回收

软引用:有用但非必须的引用。当内存不够时(抛出OurOfMemoryError之前)会被回收。通过SoftReference类来实现软引用

弱引用:非必须的引用。垃圾回收器就会回收这种对象,例如ThreadLocal中的map中,键 就是对ThreadLocal对象的引用就是弱引用。采用WeakReference类来实现弱引用

虚引用:一个对象是否有虚引用的存在,完全不会对其生存时间构成影响, 唯一目的就是能在这个对象被回收时收到一个系统通知, 采用PhantomRenference类实现

 

垃圾回收算法

刚刚说了如何判断一个对象是否为垃圾,那么当判断出来哪些对象是垃圾后,就直接把他们清除掉 就结束了吗?也并不是。下面讨论几种常见的收集算法:

1. 标记清除

就是最基本的再标记了哪些对象是垃圾以后,直接简单得将他们清除掉。

这是最基本的方法,但是有弊端,就是堆内存中很可能有很多小的碎片空间。当要放大一点的对象时,明明空间还够,但这些空间不连续,导致放不下了。

2. 复制算法

首先将堆空间分为大小相等的两块,每次只使用其中一块。当这一块快用完时,将对象挨着复制到另一块上。再把刚使用过的一块清理掉。缺点:代价太高,使得可用的堆内存缩小了一半

3. 标记整理

这种方法在标记 并清理垃圾之后,又将存活的对象整理了一下,避免了碎片空间。

4. 分代收集算法

根据对象存活周期的不同将内存划分为几块。一般把java堆分为新生代和老年代。JVM根据各个年代的特点采用不同的收集算法。

新生代中,每次进行垃圾回收都会发现大量对象死去,只有少量存活,因此比较适合复制算法。只需要付出少量存活对象的复制成本就可以完成收集。

老年代中,因为对象存活率较高,没有额外的空间进行分配担保,所以适合标记-清理、标记-整理算法来进行回收。

 

垃圾回收器

在有了垃圾回收算法后,会有一些垃圾回收器使用这些方法完成垃圾回收的工作。我们主要关注HotSpot虚拟机中的各种垃圾回收器。 首先HotSpot在大多数情况下使用了分代的方法,即将堆空间分为新生代和老年代两部分,并且为不同的部分使用了不同的垃圾回收算法,也就是说我们的垃圾回收器也分为新生代的回收器和老年代的回收器。

新生代和老年代是什么

一般新生代和老年代的大小比为1:2,新生代会存放一些较新的对象,老年代会存放经过了很多次垃圾回收 却仍然存活的对象,或者很大的对象。

新生代被分为三个部分:Eden(占80%),survivor from(占10%),survivor To(占10%)

  • 当一个对象被新建时会被放到Eden区。
  • 进行第一次对新生代的垃圾回收时(survivor都是空的),Eden存活下来的对象会被复制到survivor from区域。
  • 之后再进行新生代的垃圾回收时,会将Eden和刚用的呢个survivor区存活下来的对象复制到另一个surivore区域
  • 当新生代的对象经历了15次垃圾回收后仍然存活,就将它放进老年代里

进入老年代的对象有三种:

  • 刚刚提过的:存活过15次垃圾回收的对象会从新生代进入老年代
  • 很大的对象,如很大的数组会直接进入老年代
  • 并不是严格要求年龄到达15的对象才能进入老年代。如果survivor中某个年龄的对象的大小总和达到了survivor大小的一般,则这个年龄的对象以及大于这个年龄的对象就会被放入老年代中。

 

什么时候进行新生代的垃圾回收

Eden区快满的时候,会触发Minor GC

 

什么时候进行老年代的垃圾回收

  • 调用System.gc()方法时。但这只是建议JVM进行full GC,并不一定进行
  • 老年代空间不足。如果新生代的对象在进入老年代时或者大对象直接进入老年代 时发现老年代空间不足,就进行一次full GC
  • 方法区(永生代)空间不足。如果设置了不对这个区域进行垃圾回收,就没有这种情况
  • 使用CMS时出现promotion failed和concurrent mode failure。promotion failed是在进行Minor GC时,survivor space放不下,对象要被放在老年代里,但是老年代也放不下。concurrent mode failure是在进行CMS GC的过程中同时有对象要放入老年代,而此时老年代空间不足造成的(有时候“空间不足”是CMS GC时当前的浮动垃圾过多导致暂时性的空间不足触发Full GC)
  • 空间分配担保。在每次进行新生代垃圾回收之前,会先判断老年代最大可用连续空间的大小是否大于新生代所有对象总空间。如果大于就没事儿了,如果小于,看一下是否允许担保失败。如果不允许,则进行full GC;如果允许,就判断一下老年代最大连续空间是否大于历次 从新生代晋升到老年代的对象大小的平均值,如果小于,就只进行minor GC,如果是大于,则要进行full GC

 

回收器:

Serial:

新生代回收器,单线程,采用复制算法。适用于Client模式下的虚拟机

ParNew:

serial的多线程版本。一般垃圾回收线程数量和CPU的数量相同。server模式下的首选的新生代收集器

Parallel Scavenge:

吞吐量优先的收集器,也是采用了复制算法 的多线程收集器。吞吐量:吞吐量 = 运行用户代码时间 / ( 运行用户代码时间 + 垃圾收集时间 ) 别的收集器都关注停顿时间,尽量使停顿时间短一些,不影响用户体验。而这个收集器考虑如何高效得利用CPU,尽快完成任务。因此适用于主要在后台计算 不用和用户进行太多交互的情况。

 

serial old:

serial收集器的老年版本。同样是单线程,但是是标记整理算法

Parallel Old

是Parallel Scavenge的老年版本,多线程,采用标记整理算法。注重高吞吐量以及CPU资源敏感的场合,都可以优先考虑Parallel Scavenge+Parallel Old 收集器。

CMS:

努力做到并发收集,因为是并发 所以只能采用标记清理算法,因为并发的时候,还有其他的线程可能在往堆中放新的对象,因此不能整理。它的收集过程分为4步:

初始标记:快速地标记那些和GC Roots直接相连的对象,但仍然需要停掉其他线程(单线程)

并发标记:见名知意,这次标记不停掉其他线程,主要是为了标记对象与对象的连接路径。

重新标记:又标记一次,为了修正并发标记过程中 发生变动的那些对象(多线程)

并发清除:对标记的对象进行处理

缺点:

  • 1. 并发的时候,CMS会默认开启(CPU数量 + 3)/4个线程进行并发标记或者并发清除,当CPU个数不足4个时,CMS的并发会让用户线程的数量很少,影响了用户线程
  • 2.并发清理的时候,导致了碎片空间,很容易导致full GC
  • 3.会导致浮动垃圾。在并发清理的时候,用户线程可能会使其他的对象也变成垃圾,但因为它们没有被标记,因此这一次垃圾回收不能回收它们,只能等下一次。这就叫浮动垃圾。浮动垃圾导致 我们不能等老年代的空间被用得差不多了才回收,一般会在使用率达到一个阈值(1.6为92%)就启动垃圾回收。因为如果老年代用得差不多了才回收,会导致在并发清理的时候,用户线程往老年代新放置的对象没地儿放

 

G1收集器:没太了解,先放上

https://mp.weixin.qq.com/s/ZwlT89vsvD2e0qEuxZto3Q

https://blog.csdn.net/xiaoye319/article/details/85252195

 

方法区回收:

方法区回收主要回收废弃的常量和无用的类

3.1 回收废弃常量

  回收废弃常量与Java堆的回收类似。下面举个栗子说明

  假如一个字符串“abc” 已经进入常量池中,但当前系统没有一个string对象是叫做abc的,也就是说,没有任何string对象的引用指向常量池中的abc常量,也没用其他地方引用这个字面量。如果这是发生内存回收,那么这个常量abc将会被清理出常量池。常量池中的其他类(接口)、方法、字段的符号引用也与此类似。

3.2 回收无用的类

  需要同时满足下面3个条件的才能算是无用的类。

  1. 该类所有的实例都已经被回收,也就是Java堆中无任何改类的实例。
  2. 加载该类的ClassLoader已经被回收。
  3. 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法

  虚拟机可以对同时满足这三个条件的类进行回收,但不是必须进行回收的。是否对类进行回收,HotSpot虚拟机提供了-Xnoclassgc参数进行控制。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值