Java中System.gc()详解

1、定义

咱们先看看注释,如果不细看估计大部分朋友都会被这个注释误解

Java中System.gc()详解_第1张图片

这个说明要分三块:

①、Runs the garbage collector.

这第一行翻译为:运行垃圾回收器 如果不往下看就会被误解

②、Calling the gc method suggests that the Java Virtual Machine expend effort toward recycling unused objects in order to make the memory they currently occupy available for quick reuse. When control returns from the method call, the Java Virtual Machine has made a best effort to reclaim space from all discarded objects.

翻译(主要):运行.gc()方法,意味着JVM将尽力回收不再被使用的对象(垃圾对象)。这里说的是尽力,而不是说肯定会回收

③、The call System.gc() is effectively equivalent to the call: Runtime.getRuntime().gc()

翻译:运行.gc(),和运行Runtime.getRuntime().gc() 效果一致。

2、误解澄清

因为官方这个注释,绝大部分朋友,包括我本人之前都觉得运行System.gc()方法会显示进行Full GC,其实不然,运行System.gc()只是提醒JVM的垃圾回收器执行GC,但是不确定是否马上执行GC。下面咱们用实际例子看下。

/**
 * 
 * @author liuchao
 * @date 2023/3/5
 */
public class SystemGcTest {

    /**
     * 此方法执行后,有时候SystemGcTest 对象会被回收,有时候不会回收
     * @param args
     */
    public static void main(String[] args) {
        new SystemGcTest();
        System.gc();
    }

    /**
     * 如果对象被回收,则此方法将会被执行
     *
     * @throws Throwable
     */
    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("GC 即将回收当前对象");
    }
}

3、通过手动调用System.gc(),理解哪些对象在GC时会被回收

3.1、对象不可被回收

代码:

/**
 * -XX:+PrintGCDetails
 *
 * @author liuchao
 * @date 2023/3/5
 */
public class SystemGcTest {

    /**
     * 声明一个变量 大小5M,
     */
    public void test1() {
        byte[] bytes = new byte[5 * 1024 * 1024];
        System.gc();
    }

    public static void main(String[] args) {
        SystemGcTest test = new SystemGcTest();
        test.test1();
    }

}

执行结果:

Java中System.gc()详解_第2张图片

原因剖析:bytes对象是在test1方法内声明的,bytes对象的作用域是整个test1方法,而System.gc()在test1方法内被调用的时候bytes对象还被引用,说白了现在JVM不知道bytes对象在System.gc()这行代码调用之后,是否还会被调用,所以不会被回收。根据可达性算法理论(方法内的局部变量都会作为GC Root),test1方法未被执行完,bytes对象就是GC Root,则不会被当做垃圾被回收。

3.2、方法内对象主动赋值为空

代码:

/**
 * -XX:+PrintGCDetails
 *
 * @author liuchao
 * @date 2023/3/5
 */
public class SystemGcTest {

    /**
     * 声明一个变量 大小5M,
     */
    public void test1() {
        byte[] bytes = new byte[5 * 1024 * 1024];
        //bytes失去了对象实例的地址引用
        bytes = null;
        System.gc();
    }

    public static void main(String[] args) {
        SystemGcTest test = new SystemGcTest();
        test.test1();
    }

}

执行结果:

Java中System.gc()详解_第3张图片

原因剖析: 根据可达性算法理论,虽然bytes对象可以被作为GC Root,但是因为bytes 主动赋值null,失去了对象地址的引用,堆内的对象实例只能等待被GC回收了。

代码性能优化方案:在方法中,如果对象不被使用了,是不是可以主动赋值null,这样方法没有执行完,这个对象是不是就有可能被回收呢?

3.3、方法代码块内增加变量,失去作用域后会被清除吗?

根据3.1、3.2的情况,如果看到这个第一反应就是会被清除,其实不然。

代码:

/**
 * -XX:+PrintGCDetails
 *
 * @author liuchao
 * @date 2023/3/5
 */
public class SystemGcTest {

    /**
     * 声明一个变量 大小5M,
     */
    public void test1() {
        {
            byte[] bytes = new byte[5 * 1024 * 1024];
        }
        System.gc();
    }

    public static void main(String[] args) {
        SystemGcTest test = new SystemGcTest();
        test.test1();
    }

}

执行结果:

Java中System.gc()详解_第4张图片

什么原因呢?

这个要从局部变量表说起,咱们知道局部变量表存储的是当前执行方法中所有的变量、参数,如果是实例方法则会多出一个局部变量this,而局部变量表中使用槽位存储数据的。其中long\double类型变量占用两个槽位,其他都是1个槽位,如果局部变量表中变量占用着槽位,则此变量不会被回收。

Java中System.gc()详解_第5张图片

3.4、其他变量占用了上一个变量的槽位,会被回收吗

代码:

/**
 * -XX:+PrintGCDetails
 *
 * @author liuchao
 * @date 2023/3/5
 */
public class SystemGcTest {

    /**
     * 声明一个变量 大小5M,
     */
    public void test1() {
        {
            byte[] bytes = new byte[5 * 1024 * 1024];
        }
        int i = 1;
        System.gc();
    }

    public static void main(String[] args) {
        SystemGcTest test = new SystemGcTest();
        test.test1();
    }

}

执行结果:

Java中System.gc()详解_第6张图片
Java中System.gc()详解_第7张图片

结论:

按照3.3的理论,是不是增加一行代码,就会是3个槽位呢,其实不然。

这里就要说到槽位复用的情况,因为bytes放在代码块中,代码走到int i= 1,那为了节省内存,i变量就使用了bytes对象的槽位,所以就是2个槽位,bytes对象就被回收了,这是不是就叫鸠占鹊巢呢。

3.5、3.1实例中的对象怎么能被回收呢?

代码:

/**
 * -XX:+PrintGCDetails
 *
 * @author liuchao
 * @date 2023/3/5
 */
public class SystemGcTest {

    /**
     * 声明一个变量 大小5M,
     */
    public void test1() {
        byte[] bytes = new byte[5 * 1024 * 1024];
    }


    /**
     * 方法外调用System.gc()
     */
    public void test2() {
        test1();
        System.gc();
    }

    public static void main(String[] args) {
        SystemGcTest test = new SystemGcTest();
        test.test2();
    }

}

执行实例:

Java中System.gc()详解_第8张图片

原因剖析:咱们都知道栈内是一个一个的栈帧,一个方法就是一个栈帧,当前方法执行完以后,这个栈帧的生命周期也就到头了,栈帧内的局部变量表、操作数栈等也就会被销毁。这段代码中的test2方法调用了test1,test1方法执行完以后,存储在局部变量表中的bytes对象就会被销毁,那存储在堆内的对象实例,则没有指针引用了,根据可达性算法理论,无指针引用的非Root对象,就会被作为垃圾被回收了。

好了,本篇文章讲解到这里,大家明白了。如果有疑问,欢迎朋友们留言沟通,如未及时回应可关注公众号私聊,谢谢。

你可能感兴趣的:(jvm,java,jvm,System.gc,gc,GC算法)