在GC机制中,垃圾回收器在对对象进行回收之前,首先要确定哪些对象是存活的,哪些对象又是死去的。
首先给出的方案是引用计数器方法,在这种方法中,当一个对象没有任何引用去引用它的话,就可以被判断不再被使用。
具体的思路如下:给对象添加一个引用计数器,每当有一个地方引用它时,计数器数值就加1,当引用失效的时候,计数器数值减1,当计数器变为0的时候,就可以判断对象已经不再被使用。
但有没有考虑到下面这样的例子?
objA.instance = objB;
objB.instance = objA;
这样的互相引用,导致使用引用计数器的GC不能回收他们,但实际上如果Main方法中,没有用到他们呢?类似于一种死锁(类比可能不准确),谁也不放过谁,但是外界又没有引用,就导致资源的浪费。
那么现在的Java用的是什么方案进行判断的呢?
答案就是可达性分析.
可达性分析中,有一个GC Roots,从GC Roots向下搜索,可以到达的对象,我们判断为不可回收,不可能到达的对象就判断为可回收。期间走过的路径,称为引用链。
如上图,5,6,7三个对象就不可能通过GC Roots来访问到,所以就被判定为可回收。
那么哪些对象可以作为可达性分析的GC Roots?
- 方法区中的静态属性引用的对象
- 方法区中的常量引用的对象
- 虚拟机栈中引用的对象
- 本地方法栈引用的对象
要说这上面这个问题,也经常在面试中遇到过,那么到底为什么他们能被当作GC Roots,原因在于,作为一个GC Roots首先自身不能被回收,GC管理的主要区域是Java堆,一般情况下只针对堆进行垃圾回收。方法区、栈和本地方法区不被GC所管理,因而选择这些区域内的对象作为GC roots,被GC roots引用的对象不被GC回收。
是不是被判定无法通过GC Roots抵达的对象一定会被回收?
答案是否定的,被判定不可达的对象,会被标记一次,然后会进行一次筛选,筛选的条件是看对象是否有必要执行finalize()方法,当finalize()没有被重写,或者已经执行过了的情况下,都会被视作没必要执行。
如果有必要执行finalize(),那么对象就会被放在名为F-Queue的队列之中,并在稍后由一条虚拟机自动建立的、低优先级的Finalizer线程去执行。如果一个对象finalize()方法中执行缓慢,或者发生死循环(更极端的情况),将很可能会导致F-Queue队列中的其他对象永久处于等待状态,甚至导致整个内存回收系统崩溃。
这样说可能有点迷,我用我拙劣的画技绘制了一个过程图。
上图表示的很清晰,在F-Queue里,会有专门的线程去帮助对象逃逸,如果逃逸失败,只能是被回收的下场。
而且一个对象的finalize只能被执行一次,一次自救失败,意味着不可再重生。
参考资料
周志明《深入理解Java虚拟机》
https://blog.csdn.net/u010744711/article/details/51371535
https://blog.csdn.net/luzhensmart/article/details/81431212
网友评论