JVM垃圾回收机制 引用计数算法与可达性分析算法
这里对引用计数算法作了较为细致的分析
首先需要判断哪些内存需要回收
由于线程私有的虚拟机栈、Native栈、程序计数器都是和线程并存亡的,在线程开始时得到分配,线程结束时线程私有内存被回收,这一块只剩下了如何回收的问题,这个问题留到后边解决。
而方法区之中的类信息要被回收掉,其判定是非常严格的。垃圾回收发生最频繁的地方,应当是在堆之中。而堆中几乎存放了所有的对象实例,首先需要做的就是判断哪些实例需要回收。
判断实例是否还存活——引用计数算法与可达性分析法
深入分析引用计数算法:
基本实现是:每个对象有一个引用计数器,当一个地方引用该对象时,计数器+1,当该对象的一个引用失效之后,计数器-1。为0时就意为着该对象已不可能再被使用。
这种实现效率较高,且设计简单,但它很难解决对象之间相互循环引用的问题。
那么什么是引用该对象,什么时候该对象的引用失效?
引用对象还比较好理解,就是某一个变量得到了堆中某一实例的引用时,算作一次引用。
引用失效则是指该原本拥有堆中某实例地址的引用的对象,失去了引用,算作引用失效。
这里举一个例子说明,网上也很少有资料谈及,找到一篇知乎的回答说的比较清晰,感觉也有道理,这里对作者的思路简单整理下:
publicstaticvoid main(String[] args) { GcObject obj1 = new GcObject(); //Step1 GcObjectobj 2 = new GcObject();//Step2 obj1.instance= obj2; //Step3 obj2.instance= obj1;////Step4 obj1= null; //Step5 obj2= null; //Step6 }
step1:new 了一个GcObject() 相当于是在堆中把该实例(暂且称作实例1)的引用赋给了obj1 实例1被引用 计数+1
step2:new 了一个GcObject() 相当于是在堆中把该实例(暂且称作实例2)的引用赋给了obj2 实例2被引用 计数+1
step3:给obj1.instance属性赋值obj2 而obj2是实例2的引用,所以实例2被引用 计数+1
step4:给obj2.instance属性赋值obj1 而obj1是实例1的引用,所以实例1被引用 计数+1
step5:obj1不再引用实例实例1引用失效一次 计数-1
此时实例1和实例2已不可能被引用了,不论是obj1还是obj2 或是他们的instance都随着 = null不再指向实例的引用了。
但此时实例1和实例2的计数器值都为1,发生了内存泄漏的情况。
一个假定情况:这里不是对象之间的相互循环引用,会是什么样?
换句话说,为什么引用计数算法的问题会发生在对象循环相互引用的时候?
原作者把这个问题留给了读者自己解决。
obj3= obj2; //Step3* obj4= obj1;//Step4* obj1= null; //Step5* obj2= null; //Step6*
step3*:给obj3赋值obj2 而obj2是实例2的引用,所以实例2被引用 计数+1
step4*:给obj4赋值obj1 而obj1是实例1的引用,所以实例1被引用 计数+1
step5*:obj1不再引用实例实例1引用失效一次 计数-1
step6*:obj2不再引用实例实例2引用失效一次 计数-1
但注意此时,obj3和obj4对实例1和实例2的引用仍然被保留,并没有随着obj1和obj2 = null 的操作而被清空,所以此时实例1和实例2的计数器都为1是正确的。
由此可见,引用计数算法在很多时候还是很可靠的,因此相当多的技术也采用了引用计数算法。
参考资料:原作者@Gityuan回答:https://www.zhihu.com/question/21539353
可达性分析算法:
这是目前JVM所采用的算法。
该算法通过一系列的GC Roots对象作为起始点,从这些对象向下搜索,与之相连接的就是存活的对象,如果一个对象不能连接到任何一个GC Roots,则该对象将被判为死亡。
GC Roots包括:
1、虚拟机栈中引用的对象
2、方法区中静态属性引用的对象
3、方法区中的常量引用的对象
4、本地方法栈中由本地方法引用的对象
以上是书中提到的,另网上资料说还有存活的Thread类对象,Class类等等的另一种列表,感觉手头的这本书只是对GC Roots分类方法和网络上的说法有所不同,实际上是差不多的东西,比如虚拟机栈中引用的对象就可能包括一些线程对象。