对象是“生”是“死”
简介
垃圾收集器对堆内存进行回收时,首先要判断堆中的对象是否还存活。
对象是“生存”还是“死亡”?这是一个问题。经典的判断算法是引用计数法,但存在一定缺陷。现代虚拟机是通过可达性分析算法判断对象是否存活。
引用计数算法
引用计数法是一个很好理解的算法。在对象中添加一个引用计数器,每当有一个引用指向该对象时,就将计数器值加一;当引用失效时,计数器值减一。任何时刻计数器值为零的对象可认为是“死亡”的。
一切都看似很正常,但实际上存在循环引用的问题。请看以下代码:
1 | public class ReferenceCountingGc { |
分析:testGc()
方法被调用时,JVM
同步创建一个栈帧压入当前执行线程的虚拟机栈中,随后,虚拟机栈中存在两个引用objA
和objB
分别指向堆内的两个new ReferenceCountingGc()
对象,然后,堆内两个对象的成员变量instance
互相指向对方,再然后,将虚拟机栈内的引用指向null
,最后触发gc
。此时,堆内的这两个对象互相引用着对方,引用计数器的值不为零,但它们已经无法再由程序进行访问了,所以如果采用引用计数算法进行垃圾回收,这两个对象将无法被回收,即会出现内存泄露问题。
可达性分析算法
现代标准虚拟机都是采用可达性分析算法判断对象是否存活。基本思路是通过一系列称为GC Roots
的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过程所走过的路径称为“引用链”,如果某个对象到GC Roots
间没有任何引用链相连,则称该对象是不可达的,即需要被回收。
在Java
技术体系里,固定可作为GC Roots
的对象包括以下几种:
- 在虚拟机栈(栈帧中的局部变量表)中引用的对象,例如各个线程被调用的方法堆栈中使用到的参数、局部变量、临时变量等。
- 在方法区中类静态属性引用的对象,即一个
Java
类的引用类型静态变量。 - 在方法区中常量引用的对象,例如字符串常量池中的引用。
- 在本地方法栈中本地
Native
方法引用的对象。 - 虚拟机内部的引用,例如基本数据类型对应的
Class
对象;一些常驻的异常类对象,例如NullPointException
和OutOfMemoryError
等,另外还有系统类加载器类对象。 - 所有被同步锁(
synchronized
)持有的对象。 - 反应虚拟机内部情况的
JMXBean
、JVMTI
中注册的回调和本地代码缓存等。
以上是固定作为GC Roots
节点集的对象,但虚拟机还会根据所选垃圾收集器和当前进行垃圾回收的内存区域,可以将一些其它对象“临时性”地加入节点集,共同组成完整的GC Roots
集合,确保可达性分析的正确性。
参考
- 《深入理解
Java
虚拟机:JVM
高级特性与最佳实践(第3
版)》 - 周志明
-------------有过牵挂了无牵挂-------------
欢迎关注微信公众号【打工这件小事】~
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 打工这件小事!
评论