gc垃圾回收实验

 

 

 

实验项目名称

Java GC垃圾收集

  • 实验目的
  1. 掌握垃圾回收算法,了解垃圾回收过程
  2. 能够进行GC调优,分析GC日志
  • 实验内容:

实验环境:JDK8

GC学习参考网站:https://www.bilibili.com/video/BV1PJ411n7xZ?from=search&seid=12974670655223980724

垃圾回收算法:

1.标记–清除算法

执行步骤:

标记:遍历内存区域,对需要回收的对象打上标记。

清除:再次遍历内存,对已经标记过的内存进行回收。

图解:

 

缺点:

效率问题;遍历了两次内存空间(第一次标记,第二次清除)。

空间问题:容易产生大量内存碎片,当再需要一块比较大的内存时,无法找到一块满足要求的,因而不得不再次出发GC。

2.复制算法

将内存划分为等大的两块,每次只使用其中的一块。当一块用完了,触发GC时,将该块中存活的对象复制到另一块区域,然后一次性清理掉这块没有用的内存。下次触发GC时将那块中存活的的又复制到这块,然后抹掉那块,循环往复。

图解:

 

 

优点

相对于标记–清理算法解决了内存的碎片化问题。

效率更高(清理内存时,记住首尾地址,一次性抹掉)。

缺点:

内存利用率不高,每次只能使用一半内存。

 

3. 标记–整理算法

因为前面的复制算法当对象的存活率比较高时,这样一直复制过来,复制过去,没啥意义,且浪费时间。所以针对老年代提出了“标记整理”算法。

执行步骤:

标记:对需要回收的进行标记

整理:让存活的对象,向内存的一端移动,然后直接清理掉没有用的内存。

图解:

 

 

优点

相对于标记–清理算法解决了内存的碎片化问题。

效率比复制算法低,内存利用效率高

4. 分代收集算法

当前大多商用虚拟机都采用这种分代收集算法,这个算法并没有新的内容,只是根据对象的存活的时间的长短,将内存分为了新生代和老年代,这样就可以针对不同的区域,采取对应的算法。如:

新生代,每次都有大量对象死亡,有老年代作为内存担保,采取复制算法

老年代,对象存活时间长,采用标记整理,或者标记清理算法都可。、

 

新生代垃圾回收过程:新生代将内存按8:1:1(默认情况下)分为一块较大的Eden(伊甸园)区和两块较小的survivor(存活)区:s0与s1(也可称之为from区和to区)
1、程序启动后,所有新建的对象都是出生在Eden(伊甸园)区,两块较小的survivor(存活)区都是空的
2、当Eden(伊甸园)区内存占满后,发生一次 minor gc(小型的垃圾回收),将存活的对象移动到其中一块survivor区(s0)并记录对象的年龄,清空Eden区
3、当再次Eden区占满后,发生第二次minor gc,同时将Eden区与s0区中存活的对象移动至s1区,并且记录对象的年龄,每有一次gc年龄就加1,清空Eden区,将s0与s1名称互换,便于下一次gc
4、对象的年龄存在一个阈值,当年龄超过这个阈值(默认是15)后,就将存活的对象移动至老年代,另一种情况是当某个年龄的对象占据幸存者区的一半,也会讲对象移动至老年代中。

老年代垃圾回收过程:当老年代对象占满后就发生一次full gc,标记整理老年代的存活对象

参考地址:https://www.cnblogs.com/pypua/p/9966050.html

Hotspot里System.gc的实现:

参考网站:https://www.cnblogs.com/hushaojun/p/4967529.html

在JDK中System.gc()的实现是通过调用Runtime.getRuntime().gc()源码如下:

/**

     * Runs the garbage collector.

     * <p>

     * Calling the <code>gc</code> 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.

     * <p>

     * The call <code>System.gc()</code> is effectively equivalent to the

     * call:

     * <blockquote><pre>

     * Runtime.getRuntime().gc()

     * </pre></blockquote>

     *

     * @see     java.lang.Runtime#gc()

     */

    public static void gc() {

        Runtime.getRuntime().gc();

    }

Runtime类中gc方法:

    /**

     * Runs the garbage collector.

     * Calling this 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 virtual machine has made

     * its best effort to recycle all discarded objects.

     * <p>

     * The name <code>gc</code> stands for "garbage

     * collector". The virtual machine performs this recycling

     * process automatically as needed, in a separate thread, even if the

     * <code>gc</code> method is not invoked explicitly.

     * <p>

     * The method {@link System#gc()} is the conventional and convenient

     * means of invoking this method.

     */

    public native void gc();

这里看到gc方法是native的,在java层面只能到此结束了。

Hotspot里System.gc的实现

上面提到了Runtime.gc是一个本地方法,那需要先在jvm里找到对应的实现,这里稍微提一下jvm里native方法最常见的也是最简单的查找,jdk里一般含有native方法的类,一般都会有一个对应的c文件,比如上面的java.lang.Runtime这个类,会有一个Runtime.c的文件和它对应,native方法的具体实现都在里面了,如果你有source,可能会猜到和下面的方法对应

1
2
3
4
5
JNIEXPORT void JNICALL
Java_java_lang_Runtime_gc(JNIEnv *env, jobject this)
{
    JVM_GC();
}

其实没错的,就是这个方法,jvm要查找到这个native方法其实很简单的,看方法名可能也猜到规则了,Java_pkgName_className_methodName,其中pkgName里的”.”替换成”_”,这样就能找到了,当然规则不仅仅只有这么一个,还有其他的,这里不细说了,有机会写篇文章详细介绍下其中细节

DisableExplicitGC参数

上面的方法里是调用JVM_GC(),实现如下

1
2
3
4
5
6
JVM_ENTRY_NO_ENV(void, JVM_GC(void))
  JVMWrapper("JVM_GC");
  if (!DisableExplicitGC) {
    Universe::heap()->collect(GCCause::_java_lang_system_gc);
  }
JVM_END

看到这里我们已经解释其中一个疑惑了,就是DisableExplicitGC这个参数是在哪里生效的,起的什么作用,如果这个参数设置为true的话,那么将直接跳过下面的逻辑,我们通过-XX:+ DisableExplicitGC就是将这个属性设置为true,而这个属性默认情况下是true还是false呢

1
2
product(bool, DisableExplicitGC, false,                                   \
          "Tells whether calling System.gc() does a full GC")

ExplicitGCInvokesConcurrent参数

这里主要针对CMSGC下来做分析,所以我们上面看到调用了heap的collect方法,我们找到对应的逻辑

void GenCollectedHeap::collect(GCCause::Cause cause) {

  if (should_do_concurrent_full_gc(cause)) {

#ifndef SERIALGC

    // mostly concurrent full collection

    collect_mostly_concurrent(cause);

#else  // SERIALGC

    ShouldNotReachHere();

#endif // SERIALGC

  } else {

#ifdef ASSERT

    if (cause == GCCause::_scavenge_alot) {

      // minor collection only

      collect(cause, 0);

    } else {

      // Stop-the-world full collection

      collect(cause, n_gens() - 1);

    }

#else

    // Stop-the-world full collection

    collect(cause, n_gens() - 1);

#endif

  }

}

 

bool GenCollectedHeap::should_do_concurrent_full_gc(GCCause::Cause cause) {

  return UseConcMarkSweepGC &&

         ((cause == GCCause::_gc_locker && GCLockerInvokesConcurrent) ||

          (cause == GCCause::_java_lang_system_gc && ExplicitGCInvokesConcurrent));

}

 

collect里一开头就有个判断,如果should_do_concurrent_full_gc返回true,那会执行collect_mostly_concurrent做并行的回收,其中should_do_concurrent_full_gc中的逻辑是如果使用CMS GC,并且是system gc且ExplicitGCInvokesConcurrent==true,那就做并行full gc,当我们设置-XX:+ ExplicitGCInvokesConcurrent的时候,就意味着应该做并行Full GC了,不过要注意千万不要设置-XX:+DisableExplicitGC,不然走不到这个逻辑里来了。

 

三、实验步骤及结果:

JVM参数参见:https://www.oracle.com/java/technologies/javase/vmoptions-jsp.html

public class TestGC {

 

    private static final int SIZE = 1024*1024;

 

    /**

     * VM参数:

     *        -Xms20M -Xmx20M -Xmn10M -XX:+UseParallelGC -XX:+PrintGCDetails

     *

     *  JVM初始分配的内存由-Xms指定,默认是物理内存的1/64

     *  JVM最大分配的内存由-Xmx指定,默认是物理内存的1/4

     *

     * -XX:+UseParallelGC

     * 代表新生代使用Parallel收集器,老年代使用串行收集器。

     *

     *

     * Ergonomics就是负责自动的调解gc暂停时间和吞吐量之间的平衡

     */

    public static void testGC1(){

        byte[] allocation1,allocation2,allocation3,allocation4;

        allocation1 = new byte[4*SIZE];

        allocation2 = new byte[2*SIZE];

        allocation3 = new byte[2*SIZE];

        allocation4 = new byte[2*SIZE];

    }

   

 

 

    /**

     * VM参数:

     *       -Xms20M -Xmx20M -Xmn10M -XX:+UseSerialGC -XX:+PrintGCDetails -XX:MaxTenuringThreshold=1 -XX:+PrintTenuringDistribution

     * -XX:+PrintTenuringDistribution参数作用:

     * JVM 在每次新生代GC时,打印出幸存区中对象的年龄分布。

     * -XX:+UseSerialGC

     *      使用串行回收器进行回收,这个参数会使新生代和老年代都使用串行回收器,新生代使用复制算法,

     *      老年代使用标记-整理算法。Serial收集器是最基本、历史最悠久的收集器,它是一个单线程收集器。

     *      一旦回收器开始运行时,整个系统都要停止。Client模式下默认开启,其他模式默认关闭

     */

    public static void testGC2(){

        byte[] allocation1,allocation2,allocation3,allocation4;

        allocation1 = new byte[4 * SIZE];

        allocation2 = new byte[4 * SIZE];

        allocation3 = new byte[4 * SIZE];

        allocation3 = null;

        allocation4 = new byte[4 * SIZE];

    }

 

 

    /**

     * VM参数:

     *  6. -Xms20M -Xmx20M -Xmn10M -XX:+UseSerialGC -XX:+PrintGCDetails -XX:MaxTenuringThreshold=4 -XX:+PrintTenuringDistribution

     *  -XX:+UseSerialGC

     * 使用串行回收器进行回收,这个参数会使新生代和老年代都使用串行回收器,新生代使用复制算法,

     * 老年代使用标记-整理算法。

     *

     * -XX:+PrintTenuringDistribution参数作用:

     * JVM 在每次新生代GC时,打印出幸存区中对象的年龄分布。

     */

    public static void testGC3(){

        byte[] allocation1,allocation2,allocation3,allocation4;

        allocation1 = new byte[SIZE / 4];

        allocation2 = new byte[4 * SIZE];

        allocation3 = new byte[4 * SIZE];

        allocation4 = null;

        allocation4 = new byte[4 * SIZE];

        System.gc();

    }

 

    public static void main(String[] args) throws Exception {

        testGC1();

    }

 

}

 

执行testGC1()方法,VM参数:-Xms20M -Xmx20M -Xmn10M -XX:+UseParallelGC -XX:+PrintGCDetails

结果:

[GC (Allocation Failure) [PSYoungGen: 7995K->840K(9216K)] 7995K->6992K(19456K), 0.0044359 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]

[Full GC (Ergonomics) [PSYoungGen: 840K->0K(9216K)] [ParOldGen: 6152K->6743K(10240K)] 6992K->6743K(19456K), [Metaspace: 3152K->3152K(1056768K)], 0.0051839 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]

Heap

 PSYoungGen      total 9216K, used 4420K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)

  eden space 8192K, 53% used [0x00000000ff600000,0x00000000ffa51268,0x00000000ffe00000)

  from space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)

  to   space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)

 ParOldGen       total 10240K, used 6743K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)

  object space 10240K, 65% used [0x00000000fec00000,0x00000000ff295c10,0x00000000ff600000)

 Metaspace       used 3189K, capacity 4496K, committed 4864K, reserved 1056768K

  class space    used 347K, capacity 388K, committed 512K, reserved 1048576K

分析:PSYoungGen: 7995K->840K(9216K)红色是在进行GC前年轻代内存的使用情况,蓝色是GC后年轻代内存的使用情况,绿色是整个年轻代内存大小,后面的分析方法一样。从输出内容上可明显看出:Eden:From:To = 8:1:1,当发现一个大对象在Eden区+Survior1区中存不下的时候就需要分配担保机制把当前Eden区+Survior1区的所有对象都复制到老年代中去。

修改testGC1方法:如下

对对象分配过程:

 

执行testGC2()方法,VM参数:-Xms20M -Xmx20M -Xmn10M -XX:+UseSerialGC -XX:+PrintGCDetails -XX:MaxTenuringThreshold=1 -XX:+PrintTenuringDistribution

结果:

[GC (Allocation Failure) [DefNew

Desired survivor size 524288 bytes, new threshold 1 (max 1)

- age   1:     614800 bytes,     614800 total

: 5947K->600K(9216K), 0.0032761 secs] 5947K->4696K(19456K), 0.0033109 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]

[GC (Allocation Failure) [DefNew

Desired survivor size 524288 bytes, new threshold 1 (max 1)

- age   1:        128 bytes,        128 total

: 4780K->0K(9216K), 0.0025522 secs] 8876K->8789K(19456K), 0.0025747 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]

[GC (Allocation Failure) [DefNew: 4151K->4151K(9216K), 0.0000128 secs][Tenured: 8788K->8790K(10240K), 0.0014736 secs] 12940K->8790K(19456K), [Metaspace: 3183K->3183K(1056768K)], 0.0015235 secs] [Times: user=0.02 sys=0.00, real=0.00 secs]

Heap

 def new generation   total 9216K, used 4460K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)

  eden space 8192K,  54% used [0x00000000fec00000, 0x00000000ff05b010, 0x00000000ff400000)

  from space 1024K,   0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)

  to   space 1024K,   0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000)

 tenured generation   total 10240K, used 8790K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)

   the space 10240K,  85% used [0x00000000ff600000, 0x00000000ffe95b38, 0x00000000ffe95c00, 0x0000000100000000)

 Metaspace       used 3206K, capacity 4496K, committed 4864K, reserved 1056768K

  class space    used 351K, capacity 388K, committed 512K, reserved 1048576K

查看TestGC.clss字节码,用jclasslib插件打开:

 

以testGC3为例,ldc将常量压入栈中,newarray是创建指定类型的数组,astore_0是将栈顶引用类型值保存到局部变量1中,aconst_null是将null入栈。

字节码指令参考:https://www.cnblogs.com/longjee/p/8675771.html

四、实验体会:

通过本次实验,了解了JVM的内部结构,对垃圾回收算法也有了一定的了解,对GC日志有了一定的认识,能够进行一定的分析。

 

 

 

评语:

 

 

 

 

成绩:                                                        指导教师:

                                                            2020 年  12 月 14日

 

(说明:实验报告内容包括:实验目的和要求、内容、原理、步骤、结果分析等。每份实验报告应有教师评语和成绩。计算机上机类的实验报告如果保存的是电子稿,也要教师批改,有评语和成绩。)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

王野也不野

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值