• 通过 JFR 与日志深入探索 JVM - 调试 JVM 的工具 WhiteBox API


    在之后的 JFR 事件学习以及调试的过程中,我们会经常用到 WhiteBox API 来触发 JVM 的一些机制或者临界点。例如强制 JVM 现在立刻进行 FullGC 等等。

    什么是 WhiteBox API

    WhiteBox API 是 HotSpot VM 自带的白盒测试工具,将内部的很多核心机制的 API 暴露出来,用于白盒测试 JVM,压测 JVM 特性,以及辅助学习理解 JVM 并调优参数。WhiteBox API 是 Java 7 引入的,目前 Java 8 LTS 以及 Java 11 LTS(其实是 Java 9+ 以后的所有版本,这里只关心 LTS 版本,Java 9 引入了模块化所以 WhiteBox API 有所变化)都是有的。但是默认这个 API 并没有编译在 JDK 之中,但是他的实现是编译在了 JDK 里面了。所以如果想用这个 API,需要用户自己编译需要的 API,并加入 Java 的 BootClassPath 并启用 WhiteBox API。

    WhiteBox API 如何实现的

    WhiteBox API 是一个 Java 类,位于 JDK 的测试包中,默认没有编译进标准发行版的 JDK 中

    [这里是代码001]

    package sun.hotspot;
    public class WhiteBox {
      //仅举两个例子,省略其他 api 以及代码
      // Force Young GC
      public native void youngGC();
      // Force Full GC
      public native void fullGC();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    可以看出,其实里面的所有 API 都是 JNI 调用,具体实现是:

    [这里是代码003]

    WB_ENTRY(void, WB_FullGC(JNIEnv* env, jobject o))
      Universe::heap()->soft_ref_policy()->set_should_clear_all_soft_refs(true);
      Universe::heap()->collect(GCCause::_wb_full_gc);
    #if INCLUDE_G1GC
      if (UseG1GC) {
        // Needs to be cleared explicitly for G1
        Universe::heap()->soft_ref_policy()->set_should_clear_all_soft_refs(false);
      }
    #endif // INCLUDE_G1GC
    WB_END
    
    WB_ENTRY(void, WB_YoungGC(JNIEnv* env, jobject o))
      Universe::heap()->collect(GCCause::_wb_young_gc);
    WB_END
    
    {CC"youngGC",  CC"()V",                             (void*)&WB_YoungGC },
    {CC"fullGC",   CC"()V",                             (void*)&WB_FullGC },
    
    //省略其他代码
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    可以看出,JNI 调用实现直接调用了底层 JVM 的相关接口,相当于把 JVM 的一些关键机制暴露出来,用于白盒测试。但是如之前所说,JDK 发行版没有包括 test 下的测试代码,也就是 WhiteBox API 所在的 jar 包并没有打进默认的 JDK 中。这就需要我们自己编译一下这个代码。

    什么是 BootClassPath

    Java 内有三种不同的类加载器:应用类加载器(application classloader),扩展类加载器(extension classloader)还有根类加载器(bootstrap classloader)

    • 应用类加载器,加载我们classpath目录下的所有类文件
    • 扩展类加载器,加载标准 Java 类库扩展的类,就是你的jre目录下的/lib/ext目录下的所有类
    • 根类加载器(bootstrap classloader),扫描 BootClassPath 下的 标准 Java 类库的类加载器。标准 Java 类库限制了一些包路径的类,必须通过根类加载器加载。

    对于 WhiteBox API,由于是他的包为sun.hotspot,普通的类加载器是不能加载这个包路径的类的,需要通过根类加载器加载。

    怎么指定 BootClassPath

    在 Java 8,通过 -Xbootclasspath: 或者 -Xbootclasspath/p:指定,例如:

    -Xbootclasspath:/home/project/whitebox.jar
    -Xbootclasspath/p:/home/project/whitebox.jar
    
    • 1
    • 2

    在 Java 9 之后的版本,这两个参数已经过期了,需要改成-Xbootclasspath/a:,例如:

    -Xbootclasspath/a:/home/project/whitebox.jar
    
    • 1

    否则会报错-Xbootclasspath is no longer a supported option.

    这里对应的 JDK 源码是:
    [这里是代码012]

    // -bootclasspath:
    } else if (match_option(option, "-Xbootclasspath:", &tail)) {
        jio_fprintf(defaultStream::output_stream(),
          "-Xbootclasspath is no longer a supported option.
    ");
        return JNI_EINVAL;
    // -bootclasspath/a:
    } else if (match_option(option, "-Xbootclasspath/a:", &tail)) {
      //将参数添加到 bootclasspath 中
      Arguments::append_sysclasspath(tail);
    // -bootclasspath/p:
    } else if (match_option(option, "-Xbootclasspath/p:", &tail)) {
        jio_fprintf(defaultStream::output_stream(),
          "-Xbootclasspath/p is no longer a supported option.
    ");
        return JNI_EINVAL;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    使用 WhiteBox API

    1. 编译 WhiteBox API

    https://github.com/openjdk/jdk/tree/master/test/lib路径下的sun目录取出,编译成一个 jar 包,名字假设是 whitebox.jar

    2. 编写测试程序

    whitebox.jar 添加到你的项目依赖,之后写代码

    public static void main(String[] args) throws Exception {
            WhiteBox whiteBox = WhiteBox.getWhiteBox();
            //获取 ReservedCodeCacheSize 这个 JVM flag 的值
            Long reservedCodeCacheSize = whiteBox.getUintxVMFlag("ReservedCodeCacheSize");
            System.out.println(reservedCodeCacheSize);
            //打印堆内存各项指标
            whiteBox.printHeapSizes();
            //执行full GC
            whiteBox.fullGC();
            
            //保持进程不退出,保证日志打印完整
            Thread.currentThread().join();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    3. 启动程序查看效果

    使用启动参数 -Xbootclasspath/a:/home/project/whitebox.jar -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xlog:gc 启动程序。其中前三个 Flag 表示启用 WhiteBox API,最后一个表示打印 GC info 级别的日志到控制台。

    我的输出:

    [0.025s][info][gc] Using G1
    251658240
    Minimum heap 8388608 Initial heap 268435456 Maximum heap 4276092928 Space alignment 2097152 Heap alignment 2097152
    [0.899s][info][gc] GC(0) Pause Full (WhiteBox Initiated Full GC) 5M->0M(20M) 45.183ms
    
    • 1
    • 2
    • 3
    • 4

    至此,我们就准备好了 WhiteBox 调试环境,接下来,我们开始逐一分析 JFR 各个事件。

  • 相关阅读:
    Vite + Vue3 实现前端项目工程化
    leetcode 594.最长和谐子序列(滑动窗口)
    Java代码审计——WebGoat XML外部实体注入(XXE)
    list模拟实现
    代码随想录day31
    【​毕业季·进击的技术er​】--毕业到工作小结
    面试时,MySQL这些基础知识你回答的出来吗?
    webgl 系列 —— 着色器语言
    前端代理模式之【策略模式】
    《Linux从练气到飞升》No.28 Linux中的线程同步
  • 原文地址:https://blog.csdn.net/m0_67391401/article/details/126618243