垃圾回收应该是jvm最被人所知的功能。那么jvm是怎么判断哪些对象能被回收,哪些对象不能回收呢?判断的标准和方法是什么?
简单来说,如果一个对象没有被其他对象所引用,那么jvm就会认为它是“无用”的对象,可以回收了。那么怎么判断一个对象有没有被引用呢?
最容易想到的就是引用计数法。用一个变量来记录对象被引用的次数,如果对象被引用了就+1,如果对象被取消了引用就-1。当这个变量的值为0时,就说明它没有被引用,可以被回收了。这种方法简单明了,容易判断。在实际应用中,也有使用引用计数法来管理内存的,比如Python ,使用ActionScript 3的FlashPlayer等。那么它有什么缺点吗?首先想到的就是记录引用次数的变量需要频繁的更新,要保证这个值绝对正确。除此之外,引用计数法还有一个比较大的漏洞,就是无法判断循环引用的情况。
什么是循环引用呢?假设有两个对象a和b。a和b互相引用,除此之外没有其他对象引用a或b。那么a和b其实是“无用的”,对程序来说没有任何用途,应该被回收。如果用引用计数法来判断的话,a和b的引用计数都为1,不为0,a和b不应该被回收。如果出现极端情况,系统中存在大量的循环引用,那么jvm将有大量的内存无法回收。
所以如果要使用引用计数法,需要配合大量额外的处理才能保证它正确的工作。
目前主流的jvm采用可达性分析算法来判断对象是否可以回收。那么什么是可达性分析算法?简单来说,可达性分析就是从特定的对象集合出发,分析所有能被这个对象集合引用的对象。如果从对象集合出发,没有任何路径可以到达一个对象,说明这个对象是“不可达的”,也就是没有被其他对象引用的,是“无用的,应该被回收掉。

最开始的这些对象集合又被叫做 GC Roots集合。一般可以理解为堆外指针指向堆内的引用。GC Roots包括:
1 在虚拟机栈中引用的对象 2 在本地方法栈中JNI(即通常说的native方法)引用的对象 3 在方法区中类静态属性引用的对象 4 在方法区中常量引用的对象,比如字符串常量池(String Table)里的引用 5 所有被同步锁(synchronized关键字)持有的对象 6 java虚拟机内部的引用(比如基本数据类型对应的Class对象,常驻的异常对象NullPointExcepiton、OutOfMemoryError等),系统类加载器 7 反映Java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等 8 根据垃圾回收器和当前回收的内存区域不同,其他“临时性”加入的对象。主要是和当前回收区域相连的其他内存区域
可达性分析可以解决循环引用的问题。还是以上面的例子来说,对象a和b存在循环引用,但是只要从GC Roots出发最终无法找到a和b,那么a和b依然会被回收。
参考资料:深入理解Java虚拟机:JVM高级特性与最佳实践(第2版) 周志明
ps:微信读书有这本书,电子版看起来很方便。




