zhouxihua0 2020-06-06
因为Java对象主要存放在Java堆里,所以垃圾收集器(Garbage Collection)在对Java堆进行回收前,第一件事情就是要确定这些对象之中哪些还“存活”着,哪些已经“死去”(不被引用了)。
引用计数算法,很容易理解,在对象中添加一个引用计数器,每有一个地方引用它时,计数器值就加一;当引用失效是,计数器值就减一;任何时刻计数器为零的对象就是不可以能再被使用的对象。
引用计数算法的原理简单,判定效率也很高。市面上也确实有一些技术使用的此类算法来判定对象是否存活,像ActionScript 3 的FlashPlayer、Python语言等。但是在主流的Java虚拟机里面都没有选用引用计算法来管理内存,主要是使用此算法时,必须要配合大量的额外处理才能保证正确的工作,例如要解决对象之间的相互循环引用的问题。
public class OneTest { public Object oneTest = null; private static final int _1MB = 1024 * 1024; private byte[] bigSize = new byte[256 * _1MB]; /** * 这个成员属性的唯一意义就是占点内存,以便能在GC日志中看清楚是否有回收过。 */ @Test public void testGC(){ OneTest test1 = new OneTest(); OneTest test2 = new OneTest(); test1.oneTest = test2; test2.oneTest = test1; test1 = null; test2 = null; // 假设在这行发生GC,test1和test2是否能被回收? System.gc(); } }
分析代码,test1和test2对象都被设置成了null,在后面发生GC的时候,如果按照引用计数算法,这两个对象虽然都被设置成了null,但是test1引用了test2,test2又引用了test1,所以这两个对象的引用计数值都不为0,所以都不会被回收,但是真正的实际运行结果是,这两个对象都被回收了,这也说明HotSpot虚拟机并不是用引用计数法来进行的内存管理。
当前主流的商用程序语言(Java、C#等),都是通过可达性分析(Reachability Analysis)算法来判断对象是否存活的。这个算法的基本思路就是通过一一系列称为“GC Roots” 的根对象作为起始节点集,从这些节点开始根据引用关系向下搜索,搜索走过的的路径称为“引用链”(Reference Chain),如果某个对象到GC Roots 间没有任何引用链相连,或者从GC Roots 到这个对象不可达时,则证明此对象是不可能再被使用的。
如下图,object10、object11、object12这三个对象,虽然互相有关联,但是它们到GC Roots是不可达的,因此它们会被判定为可回收的对象。
在Java程序中,固定可作为GC Roots 的对象包括以下几种:
无论是通过引用计数算法判断对象的引用数量,还是通过可达性分析算法判断对象是否引用链可达,判断对象是否存活都和“引用”离不开关系。在JDK1.2之前,Java里对引用的概念是:如果reference类型的数据中存储的数值代表的是另外一块儿内存的地址,就称该reference数据是代表某块内存、某个对象的引用。
在JDK1.2版之后,Java对引用的概念进行了扩充,将引用分为强引用(Strongly Reference)、软引用(Soft Reference)、弱引用(Weak Reference)、虚引用(Phantom Reference)4种,这4种引用强度依次逐渐减弱。
Object obj = new Object()
这种引用关系。无论在任何情况下,只要强引用关系还存在,垃圾收集器就不会回收掉被引用的对象。
即使在可达性分析算法中,判断为不可达的对象,也不是“非死不可”的,要真正宣告一个对象死亡,至少要经历两次标记过程:
需要注意的是:任何一个对象的finalize()方法都只会被系统自动调用一次,如果对象面临第二次回收,它的finalize()方法不会被再次执行。
还有一点就是Java官方已经明确声明不推荐手动调用finalize()方法了,因为它的运行代价高昂,不确定性大,无法保证各个对象的调用顺序,并且finanlize()能做的所有工作,使用try-finally或其他方式都可以做的更好、更及时。
方法区垃圾收集的“性价比”通常比较低,并且方法区回收也有过于苛刻的判定条件。
方法区的垃圾收集主要回收两部分内容:废弃的常量和不再使用的类型,回收废弃常量时,如果当前系统没有一个常量的值是当前常量值,且虚拟机中也没有其他地方引用这个常量。如果这个时候发生垃圾回收,常量就会被系统清理出常量池。
判定一个类型是否属于“不再使用的类”的条件就比较苛刻了,要同时满足如下三个条件:
同时满足了上述的三个条件后,也只是被允许进行回收了,关于是否要对类型进行回收还要对虚拟机进行一系列的参数设置,这里就不赘述了,感兴趣的可以自己去查询。