Java内存学习-JIT(Just In Time) 即时编译器 2- 相关优化及分析工具

优化技术

Escape Analysis逃逸分析

运行样例代码,对比逃逸分析参数的开启及关闭对GC影响,开启逃逸分析后Java虚拟机没有GC操作

在这里插入图片描述

-XX:-DoEscapeAnalysis 关闭(JDK8中,逃逸分析默认开启)

java -Xms1G -Xmx1G -XX:+PrintGCDetails -verbose:gc -XX:-DoEscapeAnalysis EscapeTest
[GC (Allocation Failure) [PSYoungGen: 262144K->336K(305664K)] 262144K->336K(1005056K), 0.0022917 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
[GC (Allocation Failure) [PSYoungGen: 262480K->320K(305664K)] 262480K->328K(1005056K), 0.0026415 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
[GC (Allocation Failure) [PSYoungGen: 262464K->320K(305664K)] 262472K->328K(1005056K), 0.0025196 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 262464K->320K(305664K)] 262472K->328K(1005056K), 0.0030588 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 262464K->320K(305664K)] 262472K->328K(1005056K), 0.0015717 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
[GC (Allocation Failure) [PSYoungGen: 262464K->320K(348672K)] 262472K->328K(1048064K), 0.0019862 secs] [Times: user=0.00 sys=0.01, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 348480K->0K(348672K)] 348488K->292K(1048064K), 0.0025398 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 348160K->32K(348672K)] 348452K->324K(1048064K), 0.0014722 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 348192K->0K(348672K)] 348484K->292K(1048064K), 0.0010888 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 348160K->0K(348672K)] 348452K->292K(1048064K), 0.0013491 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
50004476
Heap
 PSYoungGen      total 348672K, used 174045K [0x00000000eab00000, 0x0000000100000000, 0x0000000100000000)
  eden space 348160K, 49% used [0x00000000eab00000,0x00000000f54f7650,0x00000000fff00000)
  from space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
  to   space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
 ParOldGen       total 699392K, used 292K [0x00000000c0000000, 0x00000000eab00000, 0x00000000eab00000)
  object space 699392K, 0% used [0x00000000c0000000,0x00000000c0049060,0x00000000eab00000)
 Metaspace       used 2822K, capacity 4486K, committed 4864K, reserved 1056768K
  class space    used 295K, capacity 386K, committed 512K, reserved 1048576K

-XX:+DoEscapeAnalysis 使用(在JDK 6u23版本以后,逃逸分析默认开启)

java -Xms1G -Xmx1G -XX:+PrintGCDetails -verbose:gc EscapeTest
Heap
 PSYoungGen      total 305664K, used 20971K [0x00000000eab00000, 0x0000000100000000, 0x0000000100000000)
  eden space 262144K, 8% used [0x00000000eab00000,0x00000000ebf7afa0,0x00000000fab00000)
  from space 43520K, 0% used [0x00000000fd580000,0x00000000fd580000,0x0000000100000000)
  to   space 43520K, 0% used [0x00000000fab00000,0x00000000fab00000,0x00000000fd580000)
 ParOldGen       total 699392K, used 0K [0x00000000c0000000, 0x00000000eab00000, 0x00000000eab00000)
  object space 699392K, 0% used [0x00000000c0000000,0x00000000c0000000,0x00000000eab00000)
 Metaspace       used 2821K, capacity 4486K, committed 4864K, reserved 1056768K
  class space    used 295K, capacity 386K, committed 512K, reserved 1048576K

JITWatch分析
逃逸分析新建对象 Object 不会逃逸出方法run,将会在栈上直接分配。如下jitwatch截图在提示未在堆分配

45: new             #8   // class ZCEscapeTest
object of type java.lang.object does not escape method,Heap allocation has been eliminated

在这里插入图片描述注意:
分析heap发现某些对象不存在,那么就有可能被优化成栈分配。

逃逸分析

逃逸分析的基本行为就是分析对象动态作用域:当一个对象在方法中被定义后,它可能被外部方法所引用,例如作为调用参数传递到其他地方中,称为方法逃逸。
在方法中创建对象之后,如果这个对象除了在方法体中还在其它地方被引用了,此时如果方法执行完毕,由于该对象有被引用,所以 GC 有可能是无法立即回收的,此时便成为 内存逃逸现象。

以下是一段有问题的C代码:

int * get_the_int() {
int i = 42;
return &i;
}

这段C代码在栈上创建了一个int类型的变量,然后把它的指针作为函数的返回值返回了。这样做是有问题的,因为当gettheint()函数返回的时候,int所在的栈帧就已经被销毁了,后面你再去访问这个地址的话,就不知道里面存储的到底是什么了。编译时会提示

gcc int1.c
int1.c: In function ‘get_the_int’:
int1.c:3:8: warning: function returns address of local variable [-Wreturn-local-addr]
    3 | return &i;
      |        ^~

Java平台设计的一个主要目标就是要消除这种类型的bug。从设计上,JVM就不具备这种低级的“根据位置索引来读内存”的能力。这类操作对应的Java字节码是putfield和getfield。如果一个对象没有逃逸出去,那也就是说JVM可以针对这个对象做一些类似“栈自动分配”的事情。在这个例子当中,这个对象不会从堆上分配空间,因此它也不需要垃圾回收器来回收。一旦使用这个“栈分配(stack-allocated)”对象的方法返回了,这个对象所占用的内存也就自动被释放掉了。
事实上,HotSpot VM的C2编译器做的事情要比栈分配要复杂得多。其次也依赖于其它优化(自动内联)。

逃逸状态

1.全局逃逸(GlobalEscape):即一个对象的作用范围逃出了当前方法或者当前线程
一般有以下几种场景:
① 对象是一个静态变量
② 对象是一个已经发生逃逸的对象
③ 对象作为当前方法的返回值
2.参数逃逸(ArgEscape):即一个对象被作为方法参数传递或者被参数引用,但在调用过程中不会发生全局逃逸,这个状态是通过被调方法的字节码确定的。
3.没有逃逸:即方法中的对象没有发生逃逸。

优化方式

1.同步省略、或同步消除、或锁消除:如果一个对象被发现只能从一个线程被访问到,那么对于这个对象的操作可以不考虑同步。因为同步锁是非常消耗性能的,所以当编译器确定一个对象没有发生逃逸时,它便会移除该对象的同步锁。
在 JDK1.8 中是默认开启的,但是要建立在已开启逃逸分析的基础之上。
开启锁消除:-XX:+EliminateLocks
关闭锁消除:-XX:-EliminateLocks

2.栈上分配、或栈内存分配:如果一个对象在子程序中被分配,那么指向该对象的指针永远不会逃逸,对象有可能会被优化为栈分配。可以减少堆内存的占用,从而减少 GC 的频次。

3.分离对象、或标量替换(scalar replacement):标量(Scalar)是指一个无法再分解成更小的数据的数据。Java中的原始数据类型及 reference 类型就是标量。相对的,那些还可以分解的数据叫做聚合量(Aggregate),Java中的对象就是聚合量,因为他可以分解成其他聚合量和标量。
有的对象可能不需要作为一个连续的内存结构存在也可以被访问到,那么对象的部分(或全部)可以不存储在内存,而是存储在 CPU 寄存器中。
对象是聚合量,它又可以被进一步分解成标量,将其成员变量分解为分散的变量,这就叫做标量替换。如果一个对象没有发生逃逸,那压根就不用创建它,只会在栈或者寄存器上创建它用到的成员标量,节省了内存空间,也提升了应用程序性能。
标量替换在 JDK1.8 中也是默认开启的,建立在已开启逃逸分析的基础之上。
开启标量替换:-XX:+EliminateAllocations
关闭标量替换:-XX:-EliminateAllocations
显示标量替换详情:-XX:+PrintEliminateAllocations

使用过程中参数

使用jmap查看创建的对象数量
jmap -history java_pid

测试的参数
不会发生GC
-Xmx10m -Xms10m -XX:+DoEscapeAnalysis -XX:+PrintGC -XX:+EliminateAllocations
*
发生大量GC
-Xmx10m -Xms10m -XX:-DoEscapeAnalysis -XX:+PrintGC -XX:+EliminateAllocations
-Xmx10m -Xms10m -XX:+DoEscapeAnalysis -XX:+PrintGC -XX:-EliminateAllocations

样例代码

逃逸分析测试代码

public class EscapeTest {
private final int val;
public EscapeTest(final int val) { this.val = val; }
public boolean equals(EscapeTest et) { return this.val == et.val; }
public static int run() {
int matches = 0;
java.util.Random random = new java.util.Random();
for (int i = 0; i < 100_000_000; i++) {
int v1 = random.nextBoolean() ? 1 : 0;
int v2 = random.nextBoolean() ? 1 : 0;
final EscapeTest e1 = new EscapeTest(v1);
final EscapeTest e2 = new EscapeTest(v2);
if (e1.equals(e2)) { matches++; }
}
return matches;
}
public static void main(final String[] args) {
System.out.println(run());
}
}

标量替换演示代码

public class ZCScalarTest {
    public static void main(String[] args) {
        alloc();   
    }
    public static void alloc(){
        ZCPoint zcpoint = new ZCPoint(1,2);
    }
}
class ZCPoint{
    private int x;
    private int y;
    public ZCPoint(int x,int y){
        this.x = x;
        this.y = y;
    }
}

经过标量替换会变成如下

public static void alloc(){
    int x = 1;
    int y = 2;
}

Inline 内联

在这里插入图片描述

在这里插入图片描述

(转)Inline 自动内联

JVM系列之:深入学习方法内联
https://juejin.cn/post/7078671997143629832

方法内联是指,在编译过程中,当遇到方法调用时,将目标方法的方法体纳入编译范围之中,并取代原方法调用的优化手段。
方法内联有许多规则。除了一些强制内联以及强制不内联的规则外,即时编译器会根据方法调用的层数、方法调用指令所在的程序路径的热度、目标方法的调用次数及大小来决定方法调用能否被内联。

方法调用的开销是比较大的,尤其是在调用量非常大的情况下。拿简单的 getter/setter 方法来说,这种方法在 Java 代码中大量存在,我们在访问的时候,需要创建相应的栈帧,访问到需要的字段后,再弹出栈帧,恢复原程序的执行。
如果能够把这些对象的访问和操作,纳入到目标方法的调用范围之内,就少了一次方法调用,速度就能得到提升,这就是方法内联的概念。
C2 编译器会在解析字节码的过程中完成方法内联。内联后的代码和调用方法的代码,会组成新的机器码,存放在 CodeCache 区域里。
在 JDK 的源码里,有很多被 @ForceInline 注解的方法,这些方法会在执行的时候被强制进行内联;而被 @DontInline 注解的方法,则始终不会被内联,比如下面的一段代码。
以使用 -XX:-Inline 参数来禁用方法内联,如果想要更细粒度的控制,可以使用 CompileCommand 参数,例如:

-XX:CompileCommand=exclude,java/lang/String.indexOf

JIT 编译之后的二进制代码,是放在 Code Cache 区域里的。这个区域的大小是固定的,而且一旦启动无法扩容。如果 Code Cache 满了,JVM 并不会报错,但会停止编译。所以编译执行就会退化为解释执行,性能就会降低。不仅如此,JIT 编译器会一直尝试去优化你的代码,造成 CPU 占用上升。
通过参数 -XX:ReservedCodeCacheSize 可以指定 Code Cache 区域的大小,如果你通过监控发现空间达到了上限,就要适当的增加它的大小。
通过参数-XX:+PrintInlining打印内联日志

使用过程中参数

第一,由 -XX:CompileCommand 中的 inline 指令指定的方法,以及由 @ForceInline 注解的方法(仅限于 JDK 内部方法),会被强制内联。 而由 -XX:CompileCommand 中的 dontinline 指令或 exclude 指令(表示不编译)指定的方法,以及由 @DontInline 注解的方法(仅限于 JDK 内部方法),则始终不会被内联。
-XX:+PrintCompilation -XX:CompileCommand=exclude,com/msdn/java/javac/jit/MethodInlineTest::bar

第二,如果调用字节码对应的符号引用未被解析、目标方法所在的类未被初始化,或者目标方法是 native 方法,都将导致方法调用无法内联。
第三,C2 不支持内联超过 9 层的调用(MaxInlineLevel默认为9,可以调整该参数大小),以及 1 层的直接递归调用(可以通过虚拟机参数 -XX:MaxRecursiveInlineLevel 调整)。

-XX:InlineSmallCode:
如果目标方法已被编译,且其生成的机器码大小超过该值,则无法内联
-XX:MaxTrivialSize:如果方法的字节码大小小于该值,则直接内联
-XX:MinlnliningThreshold:如果目标方法的调用次数低于该值,则无法内联
-XX:InlineFrequencyCount:如果方法调用指令执行次数超过该值,则认为是热点方法
-XX:MaxInlineSize:如果非热点方法的字节码大小超过该值,则无法内联
-XX:FreqlnlineSize:如果热点方法的字节码大小超过该值,则无法内联
-XX:LiveNodeCountInliningCutoff:编译过程中IR节点数目的上限

样例代码

public class InlineElimAlloc
{
    public long outer(int i)
    {
        long sum = 0;
        for (int j = 0; j < 20_000; j++)
        {
             sum += inner(i, j);
        }
         return sum;
    }

    public long inner(int i, int j)
    {
        long sum = 0;
        int[] parts = new int[2];
        parts[0] = i;
        parts[1] = j;
        java.util.Random random = new java.util.Random();
        if (random.nextBoolean())
        {
            sum += parts[0];
        }
        else
        {
            sum += parts[1];
        }
        return sum;
    }
    public static void main(final String[] args)
    {
        InlineElimAlloc test = new InlineElimAlloc();
        long sum = 0;
        for (int i = 0; i < 20_000; i++)
        {
            sum += test.outer(i);
        }
        System.out.println(sum);
    }
}

HSDIS/JITWatch工具

hsdis/jitwatch安装

ubuntu安装

PRETTY_NAME="Ubuntu 22.04.3 LTS"
NAME="Ubuntu"
VERSION_ID="22.04"
VERSION="22.04.3 LTS (Jammy Jellyfish)"

maven安装

mvn -v
Apache Maven 3.6.3
Maven home: /usr/share/maven
Java version: 17.0.10, vendor: Private Build, runtime: /usr/lib/jvm/java-17-openjdk-amd64
Default locale: en_US, platform encoding: UTF-8
OS name: "linux", version: "6.2.0-37-generic", arch: "amd64", family: "unix"

配置文件.setting内容

<?xml version="1.0" encoding="UTF-8"?>
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
  <pluginGroups>
  </pluginGroups>
  <proxies>
  </proxies>
  <servers>
  </servers>
  <mirrors>
        <mirror>
          <id>alimaven</id>
          <name>aliyun maven</name>
          <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
          <mirrorOf>central</mirrorOf>
        </mirror>
  </mirrors>
  <profiles>
        <profile>
                <id>myjdk</id>
                <activation>
                        <activeByDefault>true</activeByDefault>
                        <jdk>1.8</jdk>
                </activation>
                <properties>
                        <maven.compiler.source>1.8</maven.compiler.source>
                        <maven.compiler.target>1.8</maven.compiler.target>
                        <maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion>
                </properties>
        </profile>
  </profiles>
        <activeProfiles>
          <activeProfile>myjdk</activeProfile>
        </activeProfiles>
</settings>

jdk安装

jdk版本
 java -version
openjdk version "17.0.10" 2024-01-16
OpenJDK Runtime Environment (build 17.0.10+7-Ubuntu-122.04.1)
OpenJDK 64-Bit Server VM (build 17.0.10+7-Ubuntu-122.04.1, mixed mode, sharing)
jdk版本切换

配置多版本jdk

export JAVA_HOME=/usr/local/jdk
export JRE_HOME=${JAVA_HOME}/jre
export CLASSPATH=.:${JAVA_HOME}/lib:${JRE_HOME}/lib
export PATH=${JAVA_HOME}/bin:$PATH
update-alternatives --install /usr/local/jdk jdk /usr/lib/jvm/java-8-openjdk-amd64 2001
update-alternatives --install /usr/local/jdk jdk /usr/lib/jvm/java-11-openjdk-amd64 2002
update-alternatives --install /usr/local/jdk jdk /usr/lib/jvm/java-17-openjdk-amd64 2003
update-alternatives --install /usr/local/jdk jdk /usr/lib/jvm/java-19-openjdk-amd64 2004

查看jdk版本

update-alternatives --list jdk
/usr/lib/jvm/java-11-openjdk-amd64
/usr/lib/jvm/java-17-openjdk-amd64
/usr/lib/jvm/java-19-openjdk-amd64
/usr/lib/jvm/java-8-openjdk-amd64

使用如下命令切换jdk版本

update-alternatives --config jdk

hsdis安装

HSDIS:HotSpot disassembler

wget https://chriswhocodes.com/hsdis/hsdis-amd64.so
cp hsdis-amd64.so /usr/local/jdk/lib/server/
cp hsdis-amd64.so /usr/local/jdk/lib/

jitwatch安装

cd /root
git clone https://github.com/AdoptOpenJDK/jitwatch
cd /root/jitwatch
mvn clean package && java -jar ui/target/jitwatch-ui-shaded.jar

HSDIS收集分析数据

java -XX:+PrintAssembly -XX:+LogCompilation -XX:+UnlockDiagnosticVMOptions  -XX:LogFile=jitlog.log javaclass

-XX:+UnlockDiagnosticVMOptions 打开JVM诊断模式
-XX:+PrintAssembly 输出编译方法的汇编代码
-XX:+LogCompilation 记录编译器日志
-XX:LogFile=jitlog.log 指定编译器日志文件

java -XX:+PrintAssembly -XX:+LogCompilation -XX:+UnlockDiagnosticVMOptions -XX:LogFile=jitlog.log -XX:-UseCompressedOops org
.adoptopenjdk.jitwatch.demo.MakeHotSpotLog

或者如

java -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly -Xcomp -XX:CompileCommand=dontinline,*Bar.sum -XX:CompileCommand=compileonly,*Bar.sum Bar

参数 -Xcomp 指定虚拟机以编译模式执行代码,不需要执行足够次数来预热都能触发 JIT 编译。
参数 -XX:CompileCommand 指定编译器不要内联 sum() 并且只编译 sum()

日志内容样例

  0x00007fa7e1400252:   je     0x00007fa7e140026a
  0x00007fa7e1400258:   cmp    0x140(%r10),%r15
  0x00007fa7e140025f:   je     0x00007fa7e140026a
  0x00007fa7e1400265:   jmp    0x00007fa7e8988780           ;   {runtime_call wrong_method_stub}
  0x00007fa7e140026a:   mov    %eax,-0x14000(%rsp)
  0x00007fa7e1400271:   push   %rbp
  0x00007fa7e1400272:   sub    $0x50,%rsp
  0x00007fa7e1400276:   movabs $0x7fa783007d98,%rsi         ;   {metadata(method data for {method} {0x00007fa7873abf80} '<clinit>' '()V' in 'java/lang/invoke
/MethodHandle')}
  0x00007fa7e1400280:   mov    0xf4(%rsi),%edi
  0x00007fa7e1400286:   add    $0x2,%edi
  0x00007fa7e1400289:   mov    %edi,0xf4(%rsi)
  0x00007fa7e140028f:   jmp    0x00007fa7e14004b1           ;*ldc {reexecute=0 rethrow=0 return_oop=0}
                                                            ; - java.lang.invoke.MethodHandle::<clinit>@0 (line 441)
  0x00007fa7e1400294:   movabs $0xffe05c00,%rsi             ;   {oop(a 'java/lang/Class'{0x00000000ffe05c00} = 'java/lang/invoke/MethodHandle')}
  0x00007fa7e140029e:   mov    %rsi,%rdi
  0x00007fa7e14002a1:   movabs $0x7fa783007d98,%rbx         ;   {metadata(method data for {method} {0x00007fa7873abf80} '<clinit>' '()V' in 'java/lang/invoke
/MethodHandle')}
  0x00007fa7e14002ab:   addq   $0x1,0x138(%rbx)
  0x00007fa7e14002b3:   nopl   0x0(%rax)

IBMJDK收集分析数据

不需要安装hsdis,基于gpf、abort收集jitdump

-Xdump:jit:
    events=gpf+abort,
    file=/home/user/jitdump.%Y%m%d.%H%M%S.%pid.%seq.dmp,
    range=1..0,
    priority=200,
    request=serial

jitwatch解析

首先设置DISPLAY环境变量

export DISPLAY=:1

打开jitwatch

/root/jitwatch
java -jar ui/target/jitwatch-ui-shaded.jar

open log指定日志文件后,点击start解析
在这里插入图片描述在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

jitwatch启动报错无法打开DISPLAY

启动jitwatch过程提示错误无法打开display
java -jar ui/target/jitwatch-ui-shaded.jar
Mar 05, 2024 9:22:35 AM com.sun.javafx.application.PlatformImpl startup
WARNING: Unsupported JavaFX configuration: classes were loaded from 'unnamed module @fdc8126'
Exception in thread "main" java.lang.UnsupportedOperationException: Unable to open DISPLAY
        at com.sun.glass.ui.gtk.GtkApplication.lambda$new$6(GtkApplication.java:202)
        at java.base/java.security.AccessController.doPrivileged(AccessController.java:318)
        at com.sun.glass.ui.gtk.GtkApplication.<init>(GtkApplication.java:200)
        at com.sun.glass.ui.gtk.GtkPlatformFactory.createApplication(GtkPlatformFactory.java:41)
        at com.sun.glass.ui.Application.run(Application.java:146)
        at com.sun.javafx.tk.quantum.QuantumToolkit.startup(QuantumToolkit.java:290)
        at com.sun.javafx.application.PlatformImpl.startup(PlatformImpl.java:293)
        at com.sun.javafx.application.PlatformImpl.startup(PlatformImpl.java:163)
        at com.sun.javafx.application.LauncherImpl.startToolkit(LauncherImpl.java:659)
        at com.sun.javafx.application.LauncherImpl.launchApplication1(LauncherImpl.java:679)
        at com.sun.javafx.application.LauncherImpl.lambda$launchApplication$2(LauncherImpl.java:196)
        at java.base/java.lang.Thread.run(Thread.java:840)

手动设置环境变量就可以启动jitwatch

export DISPLAY=:1
使用非root用户无法打开display

使用非root用户提示无法打开display,开启ubuntu desktop允许root用户后,再切换root用户登录ubuntu desktop可以正常打开jitwatch

IdealGraphVisualizer 工具

准备 jdk fastdebug版本

gcc 版本

gcc --version
gcc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0
Copyright (C) 2021 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

g++ 版本

g++ --version
g++ (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0
Copyright (C) 2021 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

准备前置包

apt-get install autoconf
apt-get install libx11-dev libxext-dev libxrender-dev libxrandr-dev libxtst-dev libxt-dev
apt-get install libcups2-dev
apt-get install libfontconfig1-dev
apt-get install libasound2-dev

编译fastdebug

cd /root/openjdk
git clone https://github.com/openjdk/jdk.git
cd jdk
bash configure --with-boot-jdk=/usr/lib/jvm/java-22-openjdk-amd64 --with-debug-level=fastdebug
make all

检查java版本

/root/openjdk/jdk/build/linux-x86_64-server-fastdebug/jdk/bin/java -version
openjdk version "23-internal" 2024-09-17
OpenJDK Runtime Environment (fastdebug build 23-internal-adhoc.root.jdk)
OpenJDK 64-Bit Server VM (fastdebug build 23-internal-adhoc.root.jdk, mixed mode

运行代码输出IGV数据

javac StackOverflowErrorTestBean.java
javac StackOverflowErrorTestBeanMain.java
java -XX:PrintIdealGraphLevel=2 -XX:PrintIdealGraphFile=ideal.xml StackOverflowErrorTestBeanMain

-XX:PrintIdealGraphLevel

N=0: no output (default)
N=1: after parsing, before matching, and final code (also for failed compilations, if available)
N=2: additionally, after every major phase
N=3: additionally, after every minor phase
N=4: additionally, after every loop optimization
N=5: additionally, after every effective IGVN and every macro expansion step (slow)
N=6: additionally, after parsing every bytecode (very slow)
日志内容样例
Object::&lt;init&gt; @ bci:-1 (line 45)</p>
<p name='is_dead_loop_safe'>
true</p>
<p name='con'>
4</p>
<p name='dump_spec'>
ReturnAdr</p>
<p name='short_name'>
RA</p>
<p name='bci'>
-1 </p>
</properties>
</node>
<node id='3'>
<properties>
<p name='name'>
Start</p>
<p name='idx'>
3</p>
<p name='type'>
tuple:</p>
<p name='bottom_type'>
{0:control, 1:abIO, 2:memory, 3:rawptr:BotPTR, 4:return_address, 5:java/lang/Object:NotNull *}</p>
<p name='phase_type'>
{0:control, 1:abIO, 2:memory, 3:rawptr:BotPTR, 4:return_address, 5:java/lang/Object:NotNull *}</p>
<p name='category'>
mixed</p>
<p name='dump_spec'>
 #{0:control, 1:abIO, 2:memory, 3:rawptr:BotPTR, 4:return_address, 5:java/lang/Object:NotNull *}</p>
<p name='is_block_start'>
true</p>
java代码

StackOverflowErrorTestBean

public class StackOverflowErrorTestBean {
        StackOverflowErrorTestBean bean = new StackOverflowErrorTestBean(this);
            public StackOverflowErrorTestBean(StackOverflowErrorTestBean bean){
                this.bean = bean;
            }
        }

StackOverflowErrorTestBeanMain

public class StackOverflowErrorTestBeanMain {
            public static void main(String[] args) {
                StackOverflowErrorTestBean j = null;
                while(true){
                    j = new StackOverflowErrorTestBean(j);
                }
            }
        }

使用IdealGraphVisualizer

编译

cd /root/openjdk/jdk/src/utils/IdealGraphVisualizer
mvn install

使用

cd /root/openjdk/jdk/src/utils/IdealGraphVisualizer
sh igv.sh

在这里插入图片描述在这里插入图片描述

参考资源

The Infinite Java Training - high-performance
https://training.xceptance.com/java/420-high-performance.html

Understanding HotSpot JVM Performance with JITWatch
https://www.chrisnewland.com/images/slides/JavaZone2016_JITWatch.pdf

小师妹学JVM之:深入理解JIT和编译优化-你看不懂系列
https://www.cnblogs.com/flydean/p/jvm-jit-in-detail.html

  • 24
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值