61 UseSerialGc的新生代回收调试

Posted 蓝风9

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了61 UseSerialGc的新生代回收调试相关的知识,希望对你有一定的参考价值。

前言

呵呵 很久之前看到这样的两篇文章 

[讨论] HotSpot VM Serial GC的一个问题

新生代回收调试的一些心得 

在第一篇帖子中 R大 详细的讲述了 cheney 算法, 以及自己编写的 cheney 算法, 以及 DefNewGeneration 的具体的一些细节, 以及 和现有的例子的对比 

另外还有一些 关于 rember set 的一些 讨论 

呵呵 可以看出 R大 讨论到一些细节的时候, 在这篇文章中 似乎是真的有些兴奋了 

找了一下 记录, mac 上面能找到的是 今年3月, 但是 实际上应该还有更早的记录才对 

然后 当时写了一个 测试用例, 稍微调试了一下 

但是 最近重新看了一下 新生代回收调试的一些心得, 在原作者的基础上面 重新整理了一下 测试用例, 呵呵 

作为 本文的测试用例, 因为 原作者的测试用例, 我觉得 那个循环没有多大意义, 并且 我这里需要模拟出 跨代引用 的场景 

在原作者的 测试用例 上面做了一些调整  

以下代码, 截图 基于 jdk9 

另外就是 一下截图 可能来自于多次调试, 可能地址 会对不上 

测试用例

package com.hx.test05;

import java.util.ArrayList;

/**
 * DefNewGc
 *
 * @author Jerry.X.He <970655147@qq.com>
 * @version 1.0
 * @date 2020-05-02 18:15
 */
public class Test20DefNewGc implements Cloneable 

  // static vars
  private static Test20DefNewGc objInOldGen;
  private static Test20DefNewGc objInYoungGen;
  private static Test20DefNewGc objWillGc;
  private static ArrayList list;
  // instance vars
  private String identStr;
  private Object refObj;

  public Test20DefNewGc(String identStr)
    this.identStr = identStr;
  

  // Test20DefNewGc
  // refer : https://hllvm-group.iteye.com/group/topic/35798
  // vmOpts : -Xint -server -Xmx600m -Xms600m -XX:PermSize=128M -XX:MaxPermSize=128M -XX:NewSize=128m -XX:MaxNewSize=128m -XX:SurvivorRatio=8 -XX:-UseTLAB -XX:+UseSerialGC -XX:CMSInitiatingOccupancyFraction=50 -XX:+PrintGCDetails
  // vm调试 : -Xint -server -Xmx600m -Xms600m -XX:PermSize=128M -XX:MaxPermSize=128M -XX:NewSize=128m -XX:MaxNewSize=128m -XX:SurvivorRatio=8 -XX:-UseTLAB -XX:+UseSerialGC -XX:CMSInitiatingOccupancyFraction=50 -XX:+PrintGCDetails  com.hx.test05.Test20DefNewGc
  public static void main(String[] args) throws Exception 

    // objInOldGen in old gen
    objInOldGen = new Test20DefNewGc("objInOldGen");
    for(int i=0; i<16; i++) 
      touchMinorGc();
    

    // objInOldGen.refObj in young gen
    objInOldGen.refObj = new Object();

    // objInYoungGen in young gen
    objInYoungGen = new Test20DefNewGc("objInYoungGen");
    objInYoungGen.refObj = new Object();

    // objWillGc in young gen
    objWillGc = new Test20DefNewGc("objWillGc");
    objWillGc.refObj = new Object();
    objWillGc = null;

    // list in young gen
    list = new ArrayList();
    list.add(new Test20DefNewGc("objInList"));
    list.add(new String("this is second element"));

    // objRefByStackVar in youngGen
    Test20DefNewGc objRefByStackVar = new Test20DefNewGc("objRefByStackVar");
    objRefByStackVar.refObj = new Object();

    // invoke final gc
    doClone(objInOldGen);
    System.out.println(" before final gc ");
    touchMinorGc();

  

  /**
   * touchMinorGc
   *
   * @return void
   * @author Jerry.X.He<970655147@qq.com>
   * @date 2020-05-02 21:20
   */
  public static void touchMinorGc() 
    int _1K = 1 * 1000;
    int _10M = 10 * 1000 * _1K;
    for(int i=0; i<12; i++) 
      byte[] bytes = new byte[_10M];
    
  

  /**
   * doClone
   *
   * @author Jerry.X.He <970655147@qq.com>
   * @version 1.0
   * @date 2020-04-05 11:01
   */
  public static void doClone(Test20DefNewGc obj) 
    try 
      obj.clone();
     catch (Exception e) 
      e.printStackTrace();
    
  


首先 在 clone 方法里面打上一个断点, 我们看一下 各个对象的一些情况吧 

// Universe::heap()
 def new generation   total 118016K, used 19532K [0x000000079a800000, 0x00000007a2800000, 0x00000007a2800000)
  eden space 104960K,  18% used [0x000000079a800000, 0x000000079bb13070, 0x00000007a0e80000)
  from space 13056K,   0% used [0x00000007a1b40000, 0x00000007a1b40000, 0x00000007a2800000)
  to   space 13056K,   0% used [0x00000007a0e80000, 0x00000007a0e80000, 0x00000007a1b40000)
 tenured generation   total 483328K, used 1088K [0x00000007a2800000, 0x00000007c0000000, 0x00000007c0000000)
   the space 483328K,   0% used [0x00000007a2800000, 0x00000007a29101f0, 0x00000007a2910200, 0x00000007c0000000)
 Metaspace       used 5504K, capacity 5628K, committed 5760K, reserved 1056768K
  class space    used 544K, capacity 577K, committed 640K, reserved 1048576K

// com.hx.test05.Test20DefNewGc.class
java.lang.Class
0x00000007a2800eb0 - klass: 'java/lang/Class'
 - ---- fields (total size 16 words):
 - private volatile transient strict 'cachedConstructor' 'Ljava/lang/reflect/Constructor;' @12  NULL (0 0)
 - private volatile transient strict 'newInstanceCallerCache' 'Ljava/lang/Class;' @16  NULL (0 f450476a)
 - private transient 'name' 'Ljava/lang/String;' @20  "com.hx.test05.Test20DefNewGc"0x00000007a2823b50 (f450476a f4500159)
 - private transient 'module' 'Ljava/lang/Module;' @24  a 'java/lang/Module'0x00000007a2800ac8 (f4500159 f4500148)
 - private final 'classLoader' 'Ljava/lang/ClassLoader;' @28  a 'jdk/internal/loader/ClassLoaders$AppClassLoader'0x00000007a2800a40 (f4500148 f4504ffe)
 - private transient 'packageName' 'Ljava/lang/String;' @32  "com.hx.test05"0x00000007a2827ff0 (f4504ffe 0)
 - private final strict 'componentType' 'Ljava/lang/Class;' @36  NULL (0 f4505c34)
 - private volatile transient strict 'reflectionData' 'Ljava/lang/ref/SoftReference;' @40  a 'java/lang/ref/SoftReference'0x00000007a282e1a0 (f4505c34 0)
 - private volatile transient 'genericInfo' 'Lsun/reflect/generics/repository/ClassRepository;' @44  NULL (0 0)
 - private volatile transient strict 'enumConstants' '[Ljava/lang/Object;' @48  NULL (0 0)
 - private volatile transient strict 'enumConstantDirectory' 'Ljava/util/Map;' @52  NULL (0 0)
 - private volatile transient 'annotationData' 'Ljava/lang/Class$AnnotationData;' @56  NULL (0 0)
 - private volatile transient 'annotationType' 'Lsun/reflect/annotation/AnnotationType;' @60  NULL (0 0)
 - transient 'classValueMap' 'Ljava/lang/ClassValue$ClassValueMap;' @64  NULL (0 f450486c)
 - private volatile transient 'classRedefinedCount' 'I' @96  0
 - signature: Lcom/hx/test05/Test20DefNewGc;
 - fake entry for mirror: 'com/hx/test05/Test20DefNewGc'
 - fake entry for array: NULL
 - fake entry for oop_size: 16
 - fake entry for static_oop_field_count: 4
 - private static 'objInOldGen' 'Lcom/hx/test05/Test20DefNewGc;' @112  a 'com/hx/test05/Test20DefNewGc'0x00000007a282e1c8 (f4505c39 f37625a6)
 - private static 'objInYoungGen' 'Lcom/hx/test05/Test20DefNewGc;' @116  a 'com/hx/test05/Test20DefNewGc'0x000000079bb12d30 (f37625a6 0)
 - private static 'objWillGc' 'Lcom/hx/test05/Test20DefNewGc;' @120  NULL (0 f37625e3)
 - private static 'list' 'Ljava/util/ArrayList;' @124  a 'java/util/ArrayList'0x000000079bb12f18 (f37625e3 80ec6079)

// Test20DefNewGc.objInOldGen
com.hx.test05.Test20DefNewGc
0x00000007a282e1c8 - klass: 'com/hx/test05/Test20DefNewGc'
 - ---- fields (total size 3 words):
 - private 'identStr' 'Ljava/lang/String;' @12  "objInOldGen"0x00000007a2825728 (f4504ae5 f37625a4)
 - private 'refObj' 'Ljava/lang/Object;' @16  a 'java/lang/Object'0x000000079bb12d20 (f37625a4 0)

// Test20DefNewGc.objInYoungGen
com.hx.test05.Test20DefNewGc
0x000000079bb12d30 - klass: 'com/hx/test05/Test20DefNewGc'
 - ---- fields (total size 3 words):
 - private 'identStr' 'Ljava/lang/String;' @12  "objInYoungGen"0x000000079bb12d48 (f37625a9 f37625b0)
 - private 'refObj' 'Ljava/lang/Object;' @16  a 'java/lang/Object'0x000000079bb12d80 (f37625b0 0)

// Test20DefNewGc.objWillGc
0x000000079bb12d90 - klass: 'com/hx/test05/Test20DefNewGc'
 - ---- fields (total size 3 words):
 - private 'identStr' 'Ljava/lang/String;' @12  "objWillGc"0x000000079bb12da8 (f37625b5 f37625bc)
 - private 'refObj' 'Ljava/lang/Object;' @16  a 'java/lang/Object'0x000000079bb12de0 (f37625bc 0)

// Test20DefNewGc.list
java.util.ArrayList
0x000000079bb12f18 - klass: 'java/util/ArrayList'
 - ---- fields (total size 3 words):
 - protected transient 'modCount' 'I' @12  2
 - private 'size' 'I' @16  2
 - transient 'elementData' '[Ljava/lang/Object;' @20  a 'java/lang/Object'[10] 0x000000079bb12f80 (f37625f0 1)

// Test20DefNewGc.list.elementData
[Ljava.lang.Object;
0x000000079bb12f80 - klass: 'java/lang/Object'[]
 - length: 10
 -   0 : a 'com/hx/test05/Test20DefNewGc'0x000000079bb12f30
 -   1 : "this is second element"0x000000079bb12fb8
 -   2 : NULL
 -   3 : NULL
 -   4 : NULL
 -   5 : NULL
 -   6 : NULL
 -   7 : NULL
 -   8 : NULL
 -   9 : NULL

// Test20DefNewGc.list.elementData[0]
com.hx.test05.Test20DefNewGc
0x000000079bb12f30 - klass: 'com/hx/test05/Test20DefNewGc'
 - ---- fields (total size 3 words):
 - private 'identStr' 'Ljava/lang/String;' @12  "objInList"0x000000079bb12f48 (f37625e9 0)
 - private 'refObj' 'Ljava/lang/Object;' @16  NULL (0 0)

// objRefByStackVar
0x000000079bb13010 - klass: 'com/hx/test05/Test20DefNewGc'
 - ---- fields (total size 3 words):
 - private 'identStr' 'Ljava/lang/String;' @12  "objRefByStackVar"0x000000079bb13028 (f3762605 f376260c)
 - private 'refObj' 'Ljava/lang/Object;' @16  a 'java/lang/Object'0x000000079bb13060 (f376260c 0)

我们先看一下 各个对象的一个情况, 再来看下 各个引用的情况 

1. 首先是 Test20DefNewGc.class 结合 Universe::heap() 的信息, 可以看到 它是在 oldGen 里面 

2. 然后是 Test20DefNewGc.objInOldGen, 结合 Universe::heap() 的信息, 可以看到 它是在 olGen 里面, 当然结合 程序来推断 也可以这样判断, Test20DefNewGc.objInOldGen.refObj 在 youngGen 

3.  Test20DefNewGc.objInYoungGen 在 youngGen, Test20DefNewGc.objInYoungGen.refObj 在 youngGen 

4.  Test20DefNewGc.objWillGc 在 youngGen, Test20DefNewGc.objWillGc.refObj 在 youngGen 

5. Test20DefNewGc.list 在 youngGen, Test20DefNewGc.list[0] 在 youngGen, Test20DefNewGc.list[1] 在 youngGen 

6. objRefByStackVar 在 youngGen 

然后 我们再来看下 各个引用的情况 

以上对象 除了 Test20DefNewGc.objWillGc 对应的对象, 其他的对象 都有强引用 引用该对象 

Test20DefNewGc.objInOldGen, Test20DefNewGc.objInYoungGen, Test20DefNewGc.list 对应的对象引用 来自于 Test20DefNewGc.class 

objRefByStackVar 对应的对象 引用来自于 objRefByStackVar(栈帧中)

所以 我们期望的结果 实际上是 gc 之后, 除了 objWillGc 会被回收之外, 其他的 对象应该 都还是存活的 

接下来 我们便来 调试一下 各个对象 在这次 minor gc 中的一些细节吧 

Test20DefNewGc.class

因为 Test20DefNewGc.class 持有跨代引用, 因此 它本身也是属于 需要遍历的强引用

Test20DefNewGc.class 本身在 oldGen 里面, 不需要处理 

在遍历的过程中 会发现 objInOldGen, objInYoungGen, objWillGc, list 这几个引用(class的其他字段对于本文而言不重要, 暂时忽略) 

然后 objInOldGen 因为是在 oldGen 里面处理的时候 会过滤掉(不处理)  

另外几个 都会复制到 to space 

Test20DefNewGc.objInOldGen

上面 Test20DefNewGc.class 的时候, 并没有处理 Test20DefNewGc.objInOldGen 

因为 Test20DefNewGc.objInOldGen 本身也持有 跨代引用, 因此 它本身也是属于 需要遍历的强引用 

Test20DefNewGc.objInOldGen 里面又两个 引用, 一个是 identStr, 一个是 refObj 

identStr 是出于 oldGen, 处理的时候 会过滤掉(不处理) 

refObj 是在 youngGen, 因此 会走 FastScanClosure 的相关业务处理  

查看一下 Test20DefNewGc.objInOldGen 的相关数据 

(lldb) x 0x7a282f478
0x7a282f478: 79 00 00 00 00 00 00 00 86 1f 01 f8 e5 4a 50 f4  y............JP.
0x7a282f488: a4 25 76 f3 00 00 00 00 79 00 00 00 00 00 00 00  .%v.....y.......
// 定位一下 identStr 的数据 -> objInOldGen
(lldb) x 0x00000007a2825728
0x7a2825728: 79 00 00 00 00 00 00 00 e7 02 00 f8 ae 73 50 f4  y............sP.
0x7a2825738: 00 00 00 00 00 00 00 00 79 00 00 00 00 00 00 00  ........y.......
(lldb) x 0x7A2839D70
0x7a2839d70: 79 00 00 00 00 00 00 00 fa 00 00 f8 0b 00 00 00  y...............
0x7a2839d80: 6f 62 6a 49 6e 4f 6c 64 47 65 6e 00 00 00 00 00  objInOldGen.....

Test20DefNewGc.objInOldGen.refObj 的地址为 0xf37625a4 * 8 = 0x79BB12D20

Test20DefNewGc.objInOldGen.refObj 被复制到 to_space 

Test20DefNewGc.objInYoungGen

Test20DefNewGc.objInYoungGen 的引用来自于 Test20DefNewGc.class 

Test20DefNewGc.objInYoungGen.identStr 的引用来自于 StringTable, 遍历 Test20DefNewGc.objInYoungGen 的引用列表的时候, 发现 identStr 已经被复制到了 to space, 更新下引用就行 

遍历 Test20DefNewGc.objInYoungGen 的引用列表的时候, 发现 identStr 已经被复制到了 to space, 更新下引用就行, 遍历到 Test20DefNewGc.objInYoungGen.refObj 的时候, 将 Test20DefNewGc.objInYoungGen.refObj 复制到 to space 

Test20DefNewGc.objWillGc

因为没有引用 引用 这个对象, 因此 这个引用不会被 复制到 to space, 逻辑上 被清理掉了 

Test20DefNewGc.list

Test20DefNewGc.list 的引用来自于 Test20DefNewGc.class 

遍历 Test20DefNewGc.list  的引用列表的时候, 复制 ArrayList 中的 elementData(Object[])

然后 遍历 Test20DefNewGc.list.elementData 的引用列表的时候, 复制 list[0], list[1]

Test20DefNewGc.list[0]

Test20DefNewGc.list[0].identStr 的引用来自于 StringTable, 遍历 Test20DefNewGc.list[0] 的引用列表的时候, 发现 identStr 已经被复制到了 to space, 更新下引用就行 

refObj 为 null 

objRefByStackVar

是直接作为 GCROOTS, 复制到了 to space 

objRefByStackVar.identStr 的引用来自于 StringTable, 遍历 objRefByStackVar 的引用列表的时候, 发现 identStr 已经被复制到了 to space, 更新下引用就行 

遍历 objRefByStackVar 的引用列表的时候, 发现 identStr 已经被复制到了 to space, 更新下引用就行, 遍历到 objRefByStackVarrefObj 的时候, 将 objRefByStackVar.refObj 复制到 to space 

完 

参考

[讨论] HotSpot VM Serial GC的一个问题

新生代回收调试的一些心得 

以上是关于61 UseSerialGc的新生代回收调试的主要内容,如果未能解决你的问题,请参考以下文章

JVM_垃圾回收串行并行并发算法(总结)

jvm垃圾回收

JVM 垃圾回收器详解

GC原理---垃圾收集器

JVM04——七个GC垃圾收集器

JVM04——七个GC垃圾收集器