如何判断堆区中的对象可以被回收了
在java中,垃圾回收机制会帮助我们自动回收不再被使用的对象,已到达即使释放内存的效果,但是java又是怎么知道哪些对象不会再被我们继续使用了呢,希望你通过本篇文章,理解引用计数法与可达性分析法的运行方式
垃圾回收机制
在c/c++中,一个对象如果不再使用,就要手动将其释放掉,但是很多程序员在编写程序的时候经常忘记将一些对象回收,从而就导致了内存泄漏
在java中为了简化对象的内存释放,引入了自动的垃圾回收机制,通过垃圾回收器把不再使用的对象完成自动回收,垃圾回收器主要负责堆上的内存回收,那么垃圾回收器又是如何知道哪些对象可以被回收了呢?
在java中,一个对象是否可以被回收,主要是看这个对象是否被引用,如果对象被引用了,说明对象还在使用,是不可以被回收的,比如说如下代码中,堆内存中的demo对象被demo引用,那么堆中的demo对象就不会被回收:
public class demo { public static void main(string[] args) { demo demo = new demo(); } }
若此时将demo的引用设置为null:
demo = null;
那么此时demo对象就处于了没有被引用的状态:
此时demo会被垃圾回收器进行回收,那么垃圾回收器又是怎么知道demo对象目前没有被引用呢???
引用计数法
引用计数法会为每一个对象维护一个引用计数器,当对象被引用时加一,取消引用时减一,在上面的代码中,demo引用了堆上的demo对象,所以demo对象的引用计数器就加一,当把demo赋值为null的时候,也就是取消了demo的引用,此时demo对象引用计数器将减一成为0,此时垃圾回收器就认为demo对象此时没有被任何引用,可以回收
但是此时会出现一个问题,如果我new了两个对象a与b,并且a对象与b对象的互为彼此的成员变量,那么就会出现循环引用的现象,此时a对象被栈内存中的a1引用,也被b对象中的a变量引用,那么他的引用技术器应该为2:
public class demo { public static void main(string[] args) { a a1 = new a(); b b1 = new b(); a1.b = b1; b1.a = a1; } } class a { b b; } class b { a a; }
那么此时,我取消a1、b1对a与b的引用
a1 = null; b1 = null;
我们已经无法在程序中获取到a与b对象了,因为他们两个除了彼此间的引用关系,已经没有任何引用能够找到他们,所以按照常理来说,a与b对象都不会在程序中再使用了,理应被垃圾回收器进行回收,但是又由于存在彼此间的引用关系,引用计数器的值并不是0,那么此时垃圾回收器又会认为a与b都存在被引用的关系,所以并不会回收这两个类
那么这种情况显然是不对的,无法回收的对象有可能会导致内存泄漏,所以java并没有使用这种方法来判断类是否应该被回收,而是使用了另外一种方式
可达性分析法
java使用的是可达性分析算法来判断对象是否可以被回收
可达性分析法将对象分为两类:
- 垃圾回收根对象(gc root)
- 普通对象
对象与对象之间存在引用关系,形成一个引用链, 可达性分析算法就是指gc root对象到某个对象间是可达的,即从gc root对象开始,通过引用对象可以找到的对象爱国,即认为该对象还不能被回收
此时b、c、d对象都可以通过引用被gc root对象找到,即他们都是可达的,所以不会被视为可回收的对象,但是如果对象a与对象b之间取消引用关系,那么即使对象c与对象d任然存在引用关系,但他们是不可达的,因此他们会被回收
java虚拟机会持有一个所有gc root对象的列表,用来判断哪些对象是不可达的,不可达的对象将被垃圾回收器进行回收:
再次回看上面的案例,如果使用可达性分析法,那么堆内存中应该存在一个gc root对象引用了主线程里面mian方法的栈帧中的对象,此时如果a1与b1不再引用堆中的对象,那么就算a对象与b对象存在引用关系,但是他们是不可达的,就会被视为等待回收的对象:
那么哪些对象可以被当中gc root对象呢?
主要有四种gc root对象:
- 1.线程thread对象
- 引用线程栈帧中的方法、参数、局部变量等等,上面我们的案例中就是线程thread对象引用了mian方法栈帧中的a1与b1
- 2.类加载器加载到的java.lang.class对象
- 引用类中的静态变量
- 3.监视器对象
- 用来保存同步锁synchronized关键字持有的对象
- 4.本地方法方法调用时使用的全局对象
- 由java虚拟机来控制调用
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。
发表评论