JVM 之垃圾回收相关算法
垃圾标记阶段的算法
垃圾标记阶段的主要任务:区分内存中哪些是存活对象,哪些是已经死亡的对象。只有被标记为已经死亡的对象,GC才会执行垃圾回收时,释放掉其所占用的内存空间。
简单来说,当一个对象已经不再被任何的存活对象继续引用是,就可以宣判为已经死亡。
一般有两种方式判断对象存活:引用计数算法和可达性分析算法(HotSpot使用的是这种)
1.引用计数算法
- 概念:
- 对每个对象保存一个整型的引用计数器 。
- 优点:
- 实现简单,垃圾对象便于辨识,判定效率高,回收没有延迟性。
-缺点: - 需要单独的字段存储计数器,增加存储空间的开销。
- 每次有新的引用都需要更新计数器,伴随着假发和减法操作,增加时间开销。
- 无法处理循环引用,所以Java的垃圾回收器中没有使用这类算法。
从图中可以看到,p引用不再指向该循环链表,但是它们每个节点又互相引用,如果使用引用计数器,每个节点的引用计数器的值都为1,造成无法被回收。
- 实现简单,垃圾对象便于辨识,判定效率高,回收没有延迟性。
- 但是引用计数算法是很多语言的资源回收的选择,比如Python
- 但是它是如何解决循环引用?
- 手动解除
- 使用弱引用weakref
- 但是它是如何解决循环引用?
2. 可达性分析算法(根搜索算法)
- 优点:
- 能够有效地解决在引用计数算法中循环引用的问题,防止内存泄漏的发生。
-**java 、 C#**使用的是这种算法进行垃圾回收。
- 能够有效地解决在引用计数算法中循环引用的问题,防止内存泄漏的发生。
- GCRoots,一组必须活跃的引用。
- 算法基本思路:
- 该算法以根对象集合(GC Roots)为起点,从上至下搜索被跟对象集合所连接的目标对象是否可达。
- 搜索之后,内存中存活对象都会被跟对象集合直接或间接链接,搜索所走过的路径称为引用链(Reference Chain)。
- 如果想要回收的目标对象没有与任何引用链相连,则是不可达,可以被标记为垃圾对象。
- 在该算法中,只有能够被跟对象集合直接或间接链接的对象才是存活对象。如下图所示。
- 在Java语言中,哪些元素可以成为跟对象?
- 虚拟机栈中的引用对象: 局部变量表中的变量等。
- 本地方法栈内JNI引用的对象。
- 方法区中类变量 引用的对象
- 方法区中常量引用的对象。
- 所有被同步锁synchronized持有的对象。
- Java虚拟机背不得引用
- 比如 基本数据类型所属的Class对象、一些常驻的异常对象(NPException ,OOMError),系统类加载器。
- 等等。
- 由于Root采用栈方式存放变量和指针,所以如果一个指针,他保存了对内存里面的对象,但是自己又不在对内存里面,它就是一个Root
- 注意
- 如果要使用可达性分析算法来判断对象是否存活,必须保证一个能保障一致性的快照中进行,(如果不能保证一致性,有可能,你这边刚标记完存活,下一刻目标对象就不再被引用,或者这一刻目标对象不被引用,下一刻就会被引用),所以不满足这一点的话标记的准确性就无法保证。
- 上面这个点也是导致进行GC是必须**STW(stop the world)**的一个重要原因。
- 号称(几乎)永不停顿的CMS收集器也会在,枚举根节点时也是必须要停顿的。
3.对象的finalization机制(最后的复活机会,仅有一次)
-
该机制允许开发人员提供** 对象被销毁前的自定义处理逻辑**。
-
来及回收目标对象之前,总会先调用这个对象的finalize()方法。
-
object 中的finalize()方法允许被子类重写,用于对象被回收之前进行资源释放,比如关闭文件、数据库连接等。
-
永远不要主动嗲用对象的finalize()方法,应该交给垃圾回收机制自行调用。
- 在调用该方法时可能会导致对象复活。
- 该方法的执行时间是没有保障的,它完全由GC线程决定,该方法的执行优先级很低。
- 一个糟糕的finalize()会严重影响GC性能
-
由于finalize()方法的存在,虚拟机中的对象一般处于三种可能的状态。
- 可触及的:从根节点开始,可达该对象。
- 可复活的:对象的所有引用都被释放,但是对象有可能在finalize()中复活,比如重新引用该对象。
- 不可触及的:该对象的finalize()被调用,并且没有复活,该对象就不可能再被复活,以为finalize()方法只会被调用一次。
-
判断一个目标对象A 是否可回收,至少姚经理两次标记的过程:
- 如果A没有到GCROOTs的引用链,则第一次被标记。
- 进行筛选,判断A是否要必需执行finalize()方法:
- 如果A没有重写finaliz()方法,或者该方法已经被调用过一次,虚拟机会视为该方法没有必要执行,A就被判定为不可触及的。
- A重写了finalize()方法,而且该方法没有被执行过,虚拟机会把A插入到F-Queue中,之后,会有一个虚拟机创建的、低优先级的Finalizer线程触发该对象的finalize()方法。
- fianlize()方法是对象逃脱死亡的最后机会,稍后GC会对F-Queue中的对象进行第二次标记。如果在第二次标记之前A在finalize()方法中与**与引用链上的任何一个对象建立了联系,那么第二次标记就会将该对象移除“即将回收”的集合, 如果再次出现没有被任何对象引用,finalize()不会再被调用。