优化技术
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::<init> @ 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