Java垃圾回收机制(如何判断一个对象是否该回收)

Java垃圾回收机制(如何判断一个对象是否该回收)

Java语言和C、C++语言的一个比较大的区别就是,Java语言不用关心它的内存开辟与释放,而是交给JVM去处理;所以要好好理解它的回收机制,当出现问题时才能上手分析;

如何判断对象已死

1.引用计数法

给对象增加一个计数器,当有引用它时,计数器就加一,当引用失效时,计数器就减一;

JVM并没有采用这种方式来判断对象是否已死

原因循环引用会导致引用计数法失效,循环引用就是A类中一个属性引用了B类对象,B类中一个属性引用了A类对象,这样一来,就算你把A类和B类的实例对象引用置为null,它们还是不会被回收;

2.可达性分析法

Java则是用了这种方法来判断是否需要回收对象;

此算法的核心思想为 : 通过一系列称为"GC Roots"的对象作为起始点,从这些节点开始向下搜索,搜索走过的路径称之为"引用链",当一个对象到GC Roots没有任何的引用链相连时(从GC Roots到这个对象不可达)时,证明此对象是不可用的;

可作为GC Roots的对象有以下几种:

  1. 虚拟机栈(栈帧中的本地变量表)中引用的对象
  2. 方法区中类静态属性引用的对象
  3. 方法区中常量引用的对象
  4. 本地方法栈中JNI(Native方法)引用的对象

在JDK1.2之后,Java补充了几种引用方式,来对垃圾回收进行更合理的管理:

  1. 强引用 : 强引用指的是在程序代码之中普遍存在的,类似于"Object obj = new Object()"这类的引用,只
    要强引用还存在,垃圾回收器永远不会回收掉被引用的对象实例。
  2. 软引用 : 软引用是用来描述一些还有用但是不是必须的对象。对于软引用关联着的对象,在系统将要发生
    内存溢出之前,会把这些对象列入回收范围之中进行第二次回收。如果这次回收还是没有足够的内存,才
    会抛出内存溢出异常。在JDK1.2之后,提供了SoftReference类来实现软引用。
  3. 弱引用 : 弱引用也是用来描述非必需对象的。但是它的强度要弱于软引用。被弱引用关联的对象只能生存
    到下一次垃圾回收发生之前。当垃圾回收器开始进行工作时,无论当前内容是否够用,都会回收掉只被弱
    引用关联的对象。在JDK1.2之后提供了WeakReference类来实现弱引用。
  4. 虚引用 : 虚引用也被称为幽灵引用或者幻影引用,它是最弱的一种引用关系。一个对象是否有虚引用的存
    在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用的
    唯一目的就是能在这个对象被收集器回收时收到一个系统通知。在JDK1.2之后,提供了
    PhantomReference类来实现虚引用。

3.对象的自我拯救(覆写finalize()方法)

我们知道,在Object类中,有这样一个方法:

protected void finalize() throws Throwable { }

这是一个受保护的方法,是不能直接调用的;
这个方法到底有啥作用呢?
这个方法是用来抵免一次垃圾回收的,注意只有一次,子类只需覆写该方法即可;

例子:

class Test1 {
    public static Test1 test1;

    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        test1 = this;
        System.out.println("Test1类对象执行了finalize方法");
    }
}
public class TestFinalize {
    public static void main(String[] args) {
        Test1.test1 = new Test1();
        Test1.test1 = null;
        //第一次回收,因为该类覆写了finalize方法,则会在第一次回收的时候拯救
        System.gc();
        //若没有这个延时,会观察不到想要的结果,因为gc()方法调用后并不是马上就执行,类似于中断一样;
        try {
            Thread.sleep(1000);
        }catch (InterruptedException e) {
            e.printStackTrace();
        }
        if(Test1.test1 != null) {
            System.out.println("test1 is alive!");
        }else {
            System.out.println("test1 is dead!");
        }

        //第二次回收
        Test1.test1 = null;
        System.gc();
        if(Test1.test1 != null) {
            System.out.println("test1 is alive!");
        }else {
            System.out.println("test1 is dead!");
        }
    }
}

输出:
Test1类对象执行了finalize方法
test1 is alive!
test1 is dead!

方法区的回收

方法区(永久代)的垃圾回收主要收集两部分内容 : 废弃常量和无用的类。
回收废弃常量和回收Java堆中的对象十分类似。以常量池中字面量(直接量)的回收为例,假如一个字符串"abc"已经进入了常量池中,但是当前系统没有任何一个String对象引用常量池的"abc"常量,也没有在其他地方引用这个字面量,如果此时发生GC并且有必要的话,这个"abc"常量会被系统清理出常量池。常量池中的其他类(接口)、方法、字段的符号引用也与此类似。

判定一个类是否是"无用类"则相对复杂很多。类需要同时满足下面三个条件才会被算是"无用的类" :

  1. 该类所有实例都已经被回收(即在Java堆中不存在任何该类的实例)
  2. 加载该类的ClassLoader已经被回收
  3. 该类对应的Class对象没有在任何其他地方被引用,无法在任何地方通过反射访问该类的方法JVM可以对同时满足上述3个条件的无用类进行回收,也仅仅是"可以"而不是必然。在大量使用反射、动态代理等场景都需要JVM具备类卸载的功能来防止永久代的溢出

猜你喜欢

转载自blog.csdn.net/eternal_yangyun/article/details/89787416