「JVM 编译优化」提前编译器

1996 年 JDK 1.0 发布,同年 7 月 外挂即时编译器发布(JDK 1.0.2),而 Java 提前编译发布在之后几个月(IBM High Performance Compiler for Java),1998 年 GNU 组织公布 GCC 家族新成员 GNU Compiler for Java(GCJ,2018 年从 GCC 家族除名),在 OpenJDK 流行起来之前,GCJ 是 Linux 发行版自带的 Java 实现版本;

AOT Compiler 因丢失了平台中立性而在 Java 世界沉寂长达 15 年;至 2013 年,因 Android 提前编译的 ART(Andriod Runtime)蹂躏式的取代即时编译的 Dalvik VM(Andriod 4.4),大大震撼了 Java 世界,Java AOT Compiler 重新被翻了出来;

提前编译就是以平台中立性、字节膨胀(提前编译的本地二进制体积明显大于字节码体积)、动态扩展(提前编译要求程序封闭,不能外部动态加载新的字节码)等特性换取足够好的性能;

1. 提前编译的优缺点

提前编译的两种方案

  • 方案一,在程序运行之前把程序代码编译成机器码的静态翻译(类似传统 C、C++ 编译器,Substrate VM);
    • 优势在于,提前编译可以放心大胆的采用全程序优化措施以获取更好的运行时性能;即时编译所消耗的时间和资源是原本可以用来运行程序的时间,这导致即使编译不得不放松一些全程序分析(Inter-Procedural Analysis,IPA,或称过程间分析;如分析变量的值是否一定为常量、代码块是否永远不会被执行,虚方法的调用是否只会存在一个版本,这些分析对流敏感、对路径敏感、对上下文敏感、对字段敏感,需要大量计算工作;目前 JVM 大多使用方法内联达到过程内分析的效果,或借助假设激进优化,不求精确结果);
  • 方案二,将原本即时编译器运行时编译要做的事提前做好并保存下来,下次运行到这些代码时直接加载并运行(如公共库代码在同一机器被多个 Java 进程使用);(Jaotc);
    • 相当于给即时编译做缓存加速,又称动态提前编译(Dynamic AOT)或即时编译缓存(JIT Caching),已被主流商用 JDK 完全支持(CDS,Class Data Sharing,编译质量低于即时编译);基于 Graal 编译器实现的 Jaotc 提前编译器会针对目标机器为应用程序做较高质量优化的提前编译,然后 HotSpot 在运行时可以直接加载运行(快启动、快响应);

即时编译的优势

  • 性能分析制导优化(Profile-Guided Optimization,PGO),即时编译会在解释器或客户端编译器运行过程中收集性能监控信息,从而做出明显偏好性资源分配(如抽象类的实际类型、判断条件走哪个分支、方法调用的实际版本、循环会走多少次等,在静态分析中是无法得到或无法唯一确认的,通过这些可以实现集中优化和偏好资源分配,如分支预测、寄存器、缓存等资源);
  • 激进预测性优化(Aggressive Speculative Optimization),静态优化必须保证优化前后执行结果等效;而即时编译可以不那么保守,只要监控信息支持,它可以大胆执行预测性的优化,从而大幅度降低目标代码复杂度、提升运行速度;
  • 链接时优化(Link-Time Optimization,LTO),提前编译的主程序与动态链接库的代码在他们编译时完全独立,两者各自编译、优化,存在明显的边界隔阂(比如方法内联会很困难);而即时编译在对 Class 文件做优化时是基于程序整体的;

2. Jaotc 提前编译

Jaotc 支持对 Class 文件和模块进行提前编译,但这些功能必须针对特定物理机器和目标 VM Arguments

HelloWorld 提前编译演示

# 前端编译,生成 HelloWorld.class
$ javac HelloWorld.java
# 直接通过 HelloWorld.class 运行
$ java HelloWorld

# 提前编译 HelloWorld.class
$ jaotc --output libHelloWorld.so HelloWorld.class

# 确认 libHelloWorld.so 是否一个静态链接库
$ ldd libHelloWorld.so
statically linked

# 查看 HelloWorld 的构造函数和 main 方法
$ nm libHelloWorld.so
...
0000000000002a20 t HelloWorld.()V
0000000000002b20 t HelloWorld.main([Ljava/lang/String;)V
...

# 通过静态链接库(而非 HelloWorld.class)运行
java -XX:AOTLibrary=./libHelloWorld.so HelloWorld

java.base 模块提前编译演示

  • java.base-list.txt(排除无法提前编译的类);
# jaotc: java.lang.StackOverflowError
exclude sun.util.resources.LocaleNames.getContents()[[Ljava/lang/Object;
exclude sun.util.resources.TimeZoneNames.getContents()[[Ljava/lang/Object;
exclude sun.util.resources.cldr.LocaleNames.getContents()[[Ljava/lang/Object;
exclude sun.util.resources..*.LocaleNames_.*.getContents\(\)\[\[Ljava/lang/Object;
exclude sun.util.resources..*.LocaleNames_.*_.*.getContents\(\)\[\[Ljava/lang/Object;
exclude sun.util.resources..*.TimeZoneNames_.*.getContents\(\)\[\[Ljava/lang/Object;
exclude sun.util.resources..*.TimeZoneNames_.*_.*.getContents\(\)\[\[Ljava/lang/Object;
# java.lang.Error: Trampoline must not be defined by the bootstrap classloader
exclude sun.reflect.misc.Trampoline.<clinit>()V
exclude sun.reflect.misc.Trampoline.invoke(Ljava/lang/reflect/Method;Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;
# JVM asserts
exclude com.sun.crypto.provider.AESWrapCipher.engineUnwrap([BLjava/lang/String;I)Ljava/security/Key;
exclude sun.security.ssl.*
exclude sun.net.RegisteredDomain.<clinit>()V
# Huge methods
exclude jdk.internal.module.SystemModules.descriptors()[Ljava/lang/module/ModuleDescriptor;
# -J 参数传递与目标 VM Arguments 相关的运行时参数;
$ jaotc -J-XX:+UseCompressedOops -J-XX:+UseG1GC -J-Xmx4g --compile-for-tiered --info --compile-commands java.base-list.txt --output libjava.base-coop.so --module java.base
Compiling libjava.base-coop.so...
6177 classes found (335 ms)
55845 methods total, 49575 methods to compile (1037 ms)
Compiling with 4 threads
......
49575 methods compiled, 0 methods failed (138821 ms)
Parsing compiled code (906 ms)
Processing metadata (10867 ms)
Preparing stubs binary (0 ms)
Preparing compiled binary (103 ms)
Creating binary: libjava.base-coop.o (2719 ms)
Creating shared library: libjava.base-coop.so (5812 ms)
Total time: 163609 ms

# 用 -XX:AOTLibrary 指定链接库位置,运行 HelloWorld
$ java -XX:AOTLibrary=java_base/libjava.base-coop.so, ./libHelloWorld.so HelloWorld
Hello World!

# 用 -XX:+PrintAOT 打印使用了提前编译的方法
$ java -XX:+PrintAOT -XX:AOTLibrary=./libHelloWorld.so HelloWorld
    11  1   loaded  ./libHelloWorld.so aot library
    105 1   aot[ 1] HelloWorld.()V
    105 2   aot[ 1] HelloWorld.main([Ljava/lang/String;)V
Hello World!

Jaotc 还难以直接编译 SpringBoot、MyBatis 等常见的第三方库,Java 标准库也只能比较顺利的编译 java.base 模块;Graal 编译器也无法支持 ZGC 与 Shenandoah GC,只能支持 G1 和 Parallel(PS + PS Old);未来很长时间 Java 后端编译的主角应该还是即时编译


上一篇:「JVM 编译优化」即时编译器
下一篇:「JVM 编译优化」编译器优化技术

PS:感谢每一位志同道合者的阅读,欢迎关注、评论、赞!


参考资料:

  • [1]《深入理解 Java 虚拟机》
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Aurelius-Shu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值