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具备类卸载的功能来防止永久代的溢出

你可能感兴趣的:(Java虚拟机)