前言 

最近 有一个想法就是, 调试一下 DefNewGeneration 里面的晋升失败的情况 

呵呵 对于这块的代码上面, 看着感觉有一些疑问的地方, 因此想通过 实际的调试, 来验证一下 实际的情况 

然后 之前写了一个用例, 但是 和心中的期望差距甚大, 当然 主要的问题 还是自己对于 细节的把控不足, 参见 一次 younggc fullgc 的调试 

因此 这次又来了, 当然 这个用例 在 vm 层面上做了一定的修改, 以方便 调试 "Promotion failed"

以下代码, 截图 基于 jdk9 

一下调试可能是来自于多次调试, 因此 地址信息, 内存大小信息 未必能够对上  

测试用例

package com.hx.test08;

import org.openjdk.jol.vm.VM;
import org.openjdk.jol.vm.VirtualMachine;

import static com.hx.test07.Test30PromotionFailed.touchMinorGc;

/**
 * Test01PromotionFailed
 *
 * @author Jerry.X.He <970655147@qq.com>
 * @version 1.0
 * @date 2020-08-16 15:43
 */
public class Test01PromotionFailed {

  // identStr
  private String identStr = "identStr";

  public Test01PromotionFailed(String identStr) {
    this.identStr = identStr;
  }

  // Test01PromotionFailed
  // -Xmx32M -XX:+UseSerialGC -XX:NewRatio=1 -XX:MaxTenuringThreshold=1 -XX:+PrintGCDetails -cp .:/Users/jerry/.m2/repository/org/openjdk/jol/jol-core/0.8/jol-core-0.8.jar
  // 需要运行时 调整 DefNewGeneration 的相关代码, 这个示例 还需要在稍微调整一下, 能够体现出关系的
  public static void main(String[] args) throws Exception {

    VirtualMachine vm = VM.current();
    int _1M = 1 * 1024 * 800;

    Test01PromotionFailed[] alreadyInOld = new Test01PromotionFailed[3 * _1M];
    alreadyInOld[0] = new Test01PromotionFailed("alreadyInOld[0]");
    System.out.println("alreadyInOld : " + vm.addressOf(alreadyInOld));
    System.out.println("alreadyInOld[0] : " + vm.addressOf(alreadyInOld[0]));
    touchMinorGc();
    System.out.println("alreadyInOld : " + vm.addressOf(alreadyInOld));
    System.out.println("alreadyInOld[0] : " + vm.addressOf(alreadyInOld[0]));

    System.out.println(" ------------ alreadyInOld ------------ ");

    Test01PromotionFailed[] promotionFailed = new Test01PromotionFailed[3 * _1M];
    promotionFailed[0] = new Test01PromotionFailed("promotionFailed[0]");
    promotionFailed[1] = alreadyInOld[0];
    System.out.println("promotionFailed : " + vm.addressOf(promotionFailed));
    System.out.println("promotionFailed[0] : " + vm.addressOf(promotionFailed[0]));
    touchMinorGc();
    System.out.println("promotionFailed : " + vm.addressOf(promotionFailed));
    System.out.println("promotionFailed[0] : " + vm.addressOf(promotionFailed[0]));
    System.out.println("alreadyInOld : " + vm.addressOf(alreadyInOld));
    System.out.println("alreadyInOld[0] : " + vm.addressOf(alreadyInOld[0]));

    System.out.println(" ------------ promotionFailed ------------ ");

  }

}

执行之后 日志如下 

[0.025s][warning][gc] -XX:+PrintGCDetails is deprecated. Will use -Xlog:gc* instead.
Signal: SIGSEGV (signal SIGSEGV)
[2.545s][info   ][gc] Using Serial
[2.545s][info   ][gc,heap,coops] Heap address: 0x00000007be000000, size: 32 MB, Compressed Oops mode: Zero based, Oop shift amount: 3
Signal: SIGSEGV (signal SIGSEGV)
# WARNING: Unable to get Instrumentation. Dynamic Attach failed: null. You may add this JAR as -javaagent manually.
WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by org.openjdk.jol.vm.sa.ServiceabilityAgentSupport (file:/Users/jerry/.m2/repository/org/openjdk/jol/jol-core/0.8/jol-core-0.8.jar) to field sun.management.RuntimeImpl.jvm
WARNING: Please consider reporting this to the maintainers of org.openjdk.jol.vm.sa.ServiceabilityAgentSupport
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release
# WARNING: Unable to attach Serviceability Agent. You can try again with escalated privileges. Two options: a) use -Djol.tryWithSudo=true to try with sudo; b) echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope
[15.909s][info   ][gc,start     ] GC(0) Pause Young (Allocation Failure)
max_promotion_in_bytes = 1 = (size_t) 1
[20.978s][info   ][gc,heap      ] GC(0) DefNew: 13184K->1600K(14784K)
[20.979s][info   ][gc,heap      ] GC(0) Tenured: 0K->468K(16384K)
[20.979s][info   ][gc,metaspace ] GC(0) Metaspace: 11260K->11260K(1060864K)
[20.985s][info   ][gc           ] GC(0) Pause Young (Allocation Failure) 12M->2M(30M) 5069.789ms
[20.985s][info   ][gc,cpu       ] GC(0) User=0.10s Sys=0.21s Real=5.08s
alreadyInOld : 33253750272
alreadyInOld[0] : 33253505256
max : 12M, used : 10M, touchMinorGc created 4 byte[1M] 
[21.304s][info   ][gc,start     ] GC(1) Pause Young (Allocation Failure)
max_promotion_in_bytes = 1 = (size_t) 1
[22.969s][info   ][gc,heap      ] GC(1) DefNew: 14781K->391K(14784K)
[22.969s][info   ][gc,heap      ] GC(1) Tenured: 468K->11651K(16384K)
[22.969s][info   ][gc,metaspace ] GC(1) Metaspace: 12040K->12040K(1060864K)
[22.969s][info   ][gc           ] GC(1) Pause Young (Allocation Failure) 14M->11M(30M) 1665.263ms
[22.969s][info   ][gc,cpu       ] GC(1) User=0.11s Sys=0.00s Real=1.67s
alreadyInOld : 33270016504
alreadyInOld[0] : 33266216512
 ------------ alreadyInOld ------------ 
promotionFailed : 33254801960
promotionFailed[0] : 33254540032
max : 12M, used : 11M, touchMinorGc created 3 byte[1M] 
[22.996s][info   ][gc,start     ] GC(2) Pause Young (Allocation Failure)
max_promotion_in_bytes = 1 = (size_t) 1
[23.969s][info   ][gc,promotion ] Promotion failed
[24.825s][info   ][gc,start     ] GC(3) Pause Full (Allocation Failure)
[24.827s][info   ][gc,phases,start] GC(3) Phase 1: Mark live objects
[25.017s][info   ][gc,phases      ] GC(3) Phase 1: Mark live objects 190.929ms
[25.017s][info   ][gc,phases,start] GC(3) Phase 2: Compute new object addresses
[25.031s][info   ][gc,phases      ] GC(3) Phase 2: Compute new object addresses 13.600ms
[25.031s][info   ][gc,phases,start] GC(3) Phase 3: Adjust pointers
[25.145s][info   ][gc,phases      ] GC(3) Phase 3: Adjust pointers 113.455ms
[25.145s][info   ][gc,phases,start] GC(3) Phase 4: Move objects
[25.153s][info   ][gc,phases      ] GC(3) Phase 4: Move objects 8.183ms
[25.178s][info   ][gc             ] GC(3) Pause Full (Allocation Failure) 24M->21M(30M) 353.170ms
[25.178s][info   ][gc,heap        ] GC(2) DefNew: 13320K->9600K(14784K)
[25.178s][info   ][gc,heap        ] GC(2) Tenured: 11651K->12026K(16384K)
[25.178s][info   ][gc,metaspace   ] GC(2) Metaspace: 12040K->12040K(1060864K)
[25.179s][info   ][gc             ] GC(2) Pause Young (Allocation Failure) 24M->21M(30M) 2183.003ms
[25.179s][info   ][gc,cpu         ] GC(2) User=0.40s Sys=0.00s Real=2.18s
promotionFailed : 33252442112
promotionFailed[0] : 33262272528
alreadyInOld : 33270016504
alreadyInOld[0] : 33281179280
 ------------ promotionFailed ------------ 
[25.184s][info   ][gc,heap,exit   ] Heap
[25.184s][info   ][gc,heap,exit   ]  def new generation   total 14784K, used 12163K [0x00000007be000000, 0x00000007bf000000, 0x00000007bf000000)
[25.184s][info   ][gc,heap,exit   ]   eden space 13184K,  92% used [0x00000007be000000, 0x00000007bebe0d00, 0x00000007bece0000)
[25.184s][info   ][gc,heap,exit   ]   from space 1600K,   0% used [0x00000007bee70000, 0x00000007bee70000, 0x00000007bf000000)
[25.184s][info   ][gc,heap,exit   ]   to   space 1600K,   0% used [0x00000007bece0000, 0x00000007bece0000, 0x00000007bee70000)
[25.184s][info   ][gc,heap,exit   ]  tenured generation   total 16384K, used 12026K [0x00000007bf000000, 0x00000007c0000000, 0x00000007c0000000)
[25.184s][info   ][gc,heap,exit   ]    the space 16384K,  73% used [0x00000007bf000000, 0x00000007bfbbea60, 0x00000007bfbbec00, 0x00000007c0000000)
[25.184s][info   ][gc,heap,exit   ]  Metaspace       used 12046K, capacity 12844K, committed 13056K, reserved 1060864K
[25.184s][info   ][gc,heap,exit   ]   class space    used 1305K, capacity 1466K, committed 1536K, reserved 1048576K
[25.189s][info   ][gc,verify,start] Verifying 
[25.570s][info   ][gc,verify      ] Verifying  381.932ms

从上面可以看到, 整个过程中 总共是触发了 四次 gc 

在 分配 alreadyInOld 之前空间不够 触发了一次 young gc 

然后 之后 alreadyInOld 之后的 touchMinorGc 触发了一次 young gc, 吧 alreadyInOld 放到了 old(to空间不够, promotion 到了 old)

然后 promotionFailed 之后又触发了一次 gc, 这次 young gc 发生了 "Promotion failed ", 然后 之后 又触发了一次 full gc 

我们这里 核心需要关注的是 第三次 gc, 也就是 发生了 "Promotion failed " 的这一次 

第一次 gc 的触发时机 

62 一次 Promotion failed 的调试_System

第二次 gc, alreadyInOld 的去向

62 一次 Promotion failed 的调试_System_02

上面日志中有一行 "max_promotion_in_bytes = 1 = (size_t) 1", 这个是为了 更轻松的构造 "Promotion failed ", 运行时修改了一个条件, 修改的是 如下的 collection_attempt_is_safe() 

// If the next generation is too full to accommodate promotion
  // from this generation, pass on collection; let the next generation
  // do it.
  if (!collection_attempt_is_safe()) {
    log_trace(gc)(":: Collection attempt not safe ::");
    gch->set_incremental_collection_failed(); // Slight lie: we did not even attempt one
    return;
  }

第三次 gc, Promotion failed

我们这里主要关注几个东西, alreadyInOld[0], promotionFailed, promotionFailed[0] 这三个引用对应的 oop, 以及这三个引用本身  

alreadyInOld 在这里并不是那么重要, 仅仅是一个 占用 old 的 dummy 

首先来看一下 promotionFailed 

[Lcom.hx.test08.Test01PromotionFailed; 
{0x00000007be33f6f8} - klass: 'com/hx/test08/Test01PromotionFailed'[]
 - length: 2457600
 -   0 : a 'com/hx/test08/Test01PromotionFailed'{0x00000007be200300}
 -   1 : a 'com/hx/test08/Test01PromotionFailed'{0x00000007bed22ef8}
 -   2 : NULL
 -   3 : NULL

结合日志 发现这里的地址是对上的 

alreadyInOld : 33270017464
alreadyInOld[0] : 33266216696
 ------------ alreadyInOld ------------ 
promotionFailed : 33255847672
promotionFailed[0] : 33254540032

我们再来看一下 对于 promotionFailed 的处理 

promotionFailed 的 age 为 0, to 里面分配不了空间, 因此尝试 promotion 到 old, old 里面也分配不了空间, 然后 记录了一下 "Promotion failed ", 最后返回的是  promotionFailed 对应的 oop 的地址, 所以 young gc 之后 promotionFailed  对应的 oop 是没有变化的 

promotionFailed 这个引用本身, 也没有变化, 也还是 0x00000007be33f6f8 

62 一次 Promotion failed 的调试_gc_03

再来看一下 promotionFailed[0]

com.hx.test08.Test01PromotionFailed 
{0x00000007be200300} - klass: 'com/hx/test08/Test01PromotionFailed'
 - ---- fields (total size 2 words):
 - private 'identStr' 'Ljava/lang/String;' @12  "promotionFailed[0]"{0x00000007be200310} (f7c40062 5)

再来看一下 promotionFailed[0] 的处理, 通过 刚才 "Promotion failed " 的 promotionFailed 找到了 promotionFailed[0]

吧 promotionFailed[0] 复制到了 to, 因此 promotionFailed[0] 被复制到了 to 

promotionFailed[0] 这个引用 也指向了 新的地址 

62 一次 Promotion failed 的调试_gc_04

再来看一下 alreadyInOld[0]/promotionFailed[1]

com.hx.test08.Test01PromotionFailed 
{0x00000007bed22ef8} - klass: 'com/hx/test08/Test01PromotionFailed'
 - ---- fields (total size 2 words):
 - private 'identStr' 'Ljava/lang/String;' @12  "alreadyInOld[0]"{0x00000007becea2e0} (f7d9d45c d)

再来看一下 alreadyInOld[0] 的处理, 通过 刚才 "Promotion failed " 的 promotionFailed 找到了 alreadyInOld[0]

吧 alreadyInOld[0] 复制到了 to, 因此 alreadyInOld[0] 被复制到了 to 

alreadyInOld[0] 这个引用 也指向了 新的地址 

62 一次 Promotion failed 的调试_sed_05

最后, 来到这一次 gc 的末尾, 我们看一下 各个地址的情况 

可以看懂 promotionFailed 最后还是在该对象原来的位置, promotionFailed[0], promotionFailed[1] 指向的是 这两个 oop 复制之后的位置

promotionFailed[0], promotionFailed[1] 这两个 oop 之前的位置的数据还在, 因为 "Promotion failed " 不能清理空间, 但是已经没有 引用引用 这两个 oop 了 

[1145.114s][info   ][gc,promotion ] Promotion failed
[Lcom.hx.test08.Test01PromotionFailed; 
{0x00000007be33f6f8} - klass: 'com/hx/test08/Test01PromotionFailed'[]
 - length: 2457600
 -   0 : a 'com/hx/test08/Test01PromotionFailed'{0x00000007bee70000}
 -   1 : a 'com/hx/test08/Test01PromotionFailed'{0x00000007bfb680a8}
 -   2 : NULL
 -   3 : NULL
 - 省略掉预览的其他 251 个 oop 的展示
 - <2457344 more elements, increase MaxElementPrintSize to print>
com.hx.test08.Test01PromotionFailed 
{0x00000007be200300} - klass: 'com/hx/test08/Test01PromotionFailed'
 - ---- fields (total size 2 words):
 - private 'identStr' 'Ljava/lang/String;' @12  "promotionFailed[0]"{0x00000007be200310} (f7c40062 5)
com.hx.test08.Test01PromotionFailed 
{0x00000007bed22ef8} - klass: 'com/hx/test08/Test01PromotionFailed'
 - ---- fields (total size 2 words):
 - private 'identStr' 'Ljava/lang/String;' @12  "alreadyInOld[0]"{0x00000007becea2e0} (f7d9d45c 5)
com.hx.test08.Test01PromotionFailed 
{0x00000007bee70000} - klass: 'com/hx/test08/Test01PromotionFailed'
 - ---- fields (total size 2 words):
 - private 'identStr' 'Ljava/lang/String;' @12  "promotionFailed[0]"{0x00000007bee70028} (f7dce005 d)
com.hx.test08.Test01PromotionFailed 
{0x00000007bed22ef8} - klass: 'com/hx/test08/Test01PromotionFailed'
 - ---- fields (total size 2 words):
 - private 'identStr' 'Ljava/lang/String;' @12  "alreadyInOld[0]"{0x00000007becea2e0} (f7d9d45c 5)

第四次 gc 

上面的调试输出是 第三次 gc 期间的运行时的数据 

第四次 之后的日志 便是上面的, 当然 我们这里 并不是那么关心, 因此 不多赘述 

promotionFailed : 33252442112
promotionFailed[0] : 33262272528
alreadyInOld : 33270017464
alreadyInOld[0] : 33281179816

 第四次 gc 触发的时机 

62 一次 Promotion failed 的调试_sed_06

完 

参考

一次 younggc fullgc 的调试