专栏原创出处:github-源笔记文件 ,github-源码 ,欢迎 Star,转载请附上原文出处链接和本声明。
Java JVM-虚拟机专栏系列笔记,系统性学习可访问个人复盘笔记-技术博客 Java JVM-虚拟机
前言
在堆里面存放着 Java 世界中几乎所有的对象实例,垃圾收集器在对堆进行回收前,第一件事情就是要确定这些对象之中哪些还「存活」着,哪些已经「死去」
「死去」即不可能再被任何途径使用的对象。
对象存活的判断算法
引用计数算法
引用计数算法很简单,它实际上是通过在对象头中分配一个空间来保存该对象被引用的次数。
- 如果该对象被其它对象引用,则它的引用计数加一
- 如果删除对该对象的引用,那么它的引用计数就减一
- 当该对象的引用计数为 0 时,那么该对象就会被回收
如果对象 a 和 b 都有字段 instance,赋值令 a.instance=b 及 b.instance=a ,此时出现了循环引用问题。解决循环引用的问题需要配合其他大量的工作。
Java 虚拟机并不是通过引用计数算法来判断对象是否存活的。
可达性分析算法
算法基本思路如下:
- 通过一系列称为「GC Roots」的根对象作为起始节点集
- 从这些节点开始,根据引用关系向下搜索,搜索过程所走过的路径称为「引用链」(Reference Chain)
- 如果某个对象到 GC Roots 间没有任何引用链相连,或者用图论的话来说就是从 GC Roots 到这个对象不可达时
- 则证明此对象是不可能再被使用的
在 Java 技术体系里面,固定可作为 GC Roots 的对象包括以下几种:
- 在虚拟机栈(栈帧中的本地变量表)中引用的对象,比如各个线程被调用的方法堆栈中使用到的参数、局部变量、临时变量等。
- 在方法区中类静态属性引用的对象,比如 Java 类的引用类型静态变量。
- 在方法区中常量引用的对象,比如字符串常量池(String Table)里的引用。
- 在本地方法栈中 JNI(即通常所说的 Native 方法)引用的对象。
- Java 虚拟机内部的引用,如基本数据类型对应的 Class 对象,一些常驻的异常对象(比如 NullPointExcepiton、OutOfMemoryError)等,还有系统类加载器。
- 所有被同步锁(synchronized 关键字)持有的对象。
- 反映 Java 虚拟机内部情况的 JMXBean、JVMTI 中注册的回调、本地代码缓存等。
除了这些固定的 GC Roots 集合以外,根据用户所选用的垃圾收集器以及当前回收的内存区域不同,还可以有其他对象「临时性」地加入,共同构成完整 GC Roots 集合。
如果只针对堆中某一块区域进行垃圾收集时,必须考虑其他区域可能引用该区域对象,此时需要将这些关联区域的对象也一并加入 GC Roots 集合中去,才能保证可达性分析的正确性。
Java 虚拟机通过可达性分析算法判断对象是否存活的。
死亡过程
第一次标记
- 对象在进行可达性分析后发现没有与 GC Roots 相连接的引用链,那它将会被第一次标记。
第二次标记
-
假如对象没有覆盖 finalize 方法,或者 finalize 方法已经被虚拟机调用过,那么不执行 finalize 方法。
如果有必要执行 finalize 方法,那么该对象将会被放置在一个名为 F-Queue 的队列之中,并在稍后由一条由虚拟机自动建立的、低调度优先级的 Finalizer 线程去执行它们的 finalize 方法。 -
finalize 方法是对象逃脱死亡命运的最后一次机会,稍后收集器将对 F-Queue 中的对象进行第二次小规模的标记,如果对象要在 finalize 中成功拯救自己,只要重新与引用链上的任何一个对象建立关联即可。
如果对象这时候还没有逃脱,那基本上它就真的要被回收了。
这里所说的「执行 finalize 方法」是指虚拟机会触发这个方法开始运行,但并不承诺一定会等待它运行结束。这样做的原因是,如果某个对象的 finalize 方法执行缓慢,或者更极端地发生了死循环,将很可能导致 F-Queue 队列中的其他对象永久处于等待,甚至导致整个内存回收子系统的崩溃。
总结
-
对象存活判断可通过「引用计数算法」「可达性分析算法」,Java 虚拟机使用「可达性分析算法」。
-
对象死亡过程不是在不可达 GC Roots 时直接回收,而是结合了 finalize 方法处理,可以在 finalize 进行自救。
参考
- 《深入理解 Java 虚拟机:JVM 高级特性与最佳实践(第 3 版)》周志明 著
来源:CSDN
作者:2.wa
链接:https://blog.csdn.net/u011278496/article/details/103837408