jvm优化
1.简介
在本教程的这一部分中,我们将继续我们的旅程,以学习更多有关
GraalVM
项目的信息,重点是与JVM平台的集成。 为了了解
GraalVM
如何适应整体情况,我们可能应该首先将其分解为一组构建块。
目录
2.分解GraalVM
在本教程的
前一部分
中,我们简要讨论了
GraalVM
组成的一些关键组件,但是肯定还有更多。 让我们简短地重申一下。
-
GraalVM编译器
:用Java编写,可以与Java
Java HotSpot VM
集成或独立运行 -
底物VM
:将Java / JVM应用程序提前(
AOT
)编译为可执行映像或共享对象 -
Truffle
:用于为
GraalVM
创建语言的语言实现框架 -
苏龙
:运行发动机
LLVM
的位码
GraalVM
-
GraalWasm
:在
GraalVM
上运行
WebAssembly
程序的引擎 -
工具
:用于调试和监视
GraalVM
平台上的应用程序的许多工具
稍后我们将讨论
GraalVM
多语言功能,而在JVM集成的背景下,我们感兴趣的是
GraalVM编译器
,
Substrate VM
和一些适用工具。 当
下载适用
于
所选
平台的
GraalVM
发行版时,您将捆绑其中的大多数组件。您可能不知道的事实是,
GraalVM编译器
可能已在其中可用(但默认情况下未激活)。您今天使用的JDK发行版可以运行您的应用程序和服务。
3.将GraalVM与OpenJDK一起使用
jaotc
工具是一种
提前
编译功能(也称为
JEP-295
),
最初
是在
OpenJDK 9
版本中引入的,它是一项实验功能。 它使用
GraalVM编译器
作为代码生成后端。 不久之后,在
OpenJDK 10
发行版随附的
JEP-317
(
基于实验Java的JIT编译器
)的范围内,
GraalVM编译器
本身就可以作为实验JIT编译器使用。
在六个月的发布周期之后,
OpenJDK 9
和
OpenJDK 10
均已在不久前达到
使用寿命
。 实际上,
到目前为止
,大多数JVM应用程序和服务仍在
JDK 8
上运行,这基本上意味着
jaotc
和
GraalVM编译器
在大多数部署中都无法使用(破坏者警报–
稍后将
对此进行详细介绍)。
但是,您可能会在
LTS版本
(例如
OpenJDK 11)上运行
,或者在与
OpenJDK 15
(截止到今天
为止
可用的最新
OpenJDK
版本)之间保持平衡。 无论如何,您都可以从
GraalVM编译器中
受益,但仍被标记为试验性功能。 为了将其打开,只需将
-XX:+UnlockExperimentalVMOptions -XX:+UseJVMCICompiler
命令行参数添加到JVM启动选项即可。
$ java -XX:+UnlockExperimentalVMOptions -XX:+UseJVMCICompiler <...>
严格来说,我们还必须添加
-XX:+EnableJVMCI
但是当存在
-XX:+UseJVMCICompiler
选项时,它将自动打开。 更重要的是,由于默认情况下
OpenJDK
发行版中不存在
GraalVM编译器,
因此不会将其作为
本机共享库
加载。 如果可用,可以通过使用两个额外的命令行选项(自
OpenJDK 13
发行版引入)
-XX:+UseJVMCINativeLibrary
和
-XX:JVMCILibPath=<path>
来启用它。 最后但并非最不重要的一点,请检查
JDK-8232118
了解更多更改
JVMCI
命令行参数的方法。
值得注意的是,
JVMCI
和
GraalVM编译器
具有许多可配置选项,可以通过分别以
jvmci.*
和
graal.*
为前缀的系统属性来指定。 这些属性必须在JVM命令行中设置,并且可以通过包含
-XX:+JVMCIPrintProperties
命令行参数来检索所有受支持属性的列表。
$ java -XX:+UnlockExperimentalVMOptions -XX:+EnableJVMCI -XX:+JVMCIPrintProperties
但是,如果您坚持使用
JDK 8,该
怎么办? 如果您
不愿意
切换到相应的
GraalVM发行版
,请考虑看看支持
JVMCI
的jdk8u / hotspot
的
分支
。 您应该能够为您的确切
JDK 8
版本找到启用了
JVMCI的
发行版,甚至可以构建自己的发行版。
值得一提的是,交换JIT编译器不需要对现有应用程序和/或服务进行任何更改,因为它们
有望以现成的方式完全兼容
。 尽管如此,
GraalVM!= JVM
还是最好执行全面的测试套件,以识别可能的回归或其他问题。 但是首先,您怎么甚至知道JVM实际上正在使用
GraalVM编译器
? 收集证明的方法有很多,但是使用
JDK Mission Control
或
VisualVM
可能是最简单且在视觉上令人愉悦的方法。 在检查正在运行的JVM进程时,您应该能够在其堆栈跟踪中发现其他
JVMCI
编译器线程和
GraalVM编译器
。
GraalVM
与
OpenJDK
集成的未来如何?
计划尚不明确,
但主要更改已纳入即将发布的
OpenJDK 16
版本中。
jaotc
工具不见了,有关将
Graal
集成到
OpenJDK中
的进一步工作已移至
Project Metropolis
。 毕竟值得研究
GraalVM
发行版。
4.使用GraalVM发行版
在撰写本文时,
20.3.0
是最新的
GraalVM
社区版本。 它具有两种风格:
OpenJDK
8和11。第一种基于
OpenJDK
版本
1.8.272
而第二种基于
OpenJDK
版本
11.0.9
。 您选择的平台的相应发行版可以直接从项目的
官方Github页面下载
。
除了
JVMCI
支持和
libgraal之外
,
GraalVM编译器还可以
提前
编译
到本地共享库中,该发行版还附带:
-
VisualVM
包括支持的来宾语言的堆分析功能 -
GraalVM Updater
:命令行实用程序,用于安装和管理可选的
GraalVM
语言运行时和实用程序
在本教程的其余部分中,我们将在
OpenJDK
11中使用
GraalVM
20.3.0
。唯一需要更改的是将
JAVA_HOME
环境变量指向
GraalVM
发行版。
官方文档中
有许多指南可帮助您入门。
对于许多JVM应用程序和服务来说,这可能已经足够了,但是我们不会在这里停止。 让我们谈谈
Substrate VM
和Java / JVM应用程序的提前(
AOT
)编译为可执行映像或共享对象(统称
为本机映像
)。
5.本地化
产生本机可执行文件或共享库的能力可能是
GraalVM
最苛刻的功能。 幕后隐藏着什么? 让我们尝试找出答案。
基板虚拟机
如果
Substrate VM
没有响起,请不要担心,您不太可能听说过。 但是,这是提前(
AOT
)编译和打包JVM应用程序和服务的本机独立可执行文件的推动力。 但是您怎么可能会想知道呢?
为JVM编写的任何代码都需要运行JVM。 它不仅包括JIT,还包括垃圾收集器,线程调度和许多其他运行时组件。 当JVM应用程序或服务打包为本地可执行文件时,就不再需要JVM。 但是仍然需要由JVM提供的所有相同的运行时组件。 这正是
Substrate VM的
工作方式:它成为本机可执行文件的一部分,并充当这些必要服务的提供者。
太酷了,关于本机可执行文件如何成为真实的东西,至少存在一个谜。 现在该看看我们如何构建本机映像。
本机图像生成器
GraalVM
生态系统附带了一个名为
native-image
的工具,也称为
native image builder
。 它不是分发的一部分,而是可选组件。
本
机映像构建器
或
本机映像
是一种实用程序,可以处理应用程序的所有类及其依赖项,包括来自JDK的类。 它静态分析这些数据,以确定在应用程序执行期间可以访问哪些类和方法。 然后,它会提前将可访问的代码和数据编译为特定操作系统和体系结构的本机可执行文件。 这整个过程称为构建映像(或映像生成时间),以清楚地将其与Java源代码编译为字节码区分开。
它可以与
gu
(aka
GraalVM Updater
)一起安装,这是一个命令行实用程序,用于安装和管理可选的
GraalVM
组件。
$ gu install native-image
本
机映像
不仅支持Java,还支持所有基于JVM的语言。 在您太兴奋之前,值得注意的是,并非所有应用程序或服务都可以打包为本地映像(可执行文件或共享库)。 您应了解许多
限制
,对于某些类型的本机应用程序/服务来说,不可行。
官方文档
非常详细地介绍了
本地图像
,其中有很多内容是公平的。 我们不会复制/粘贴所有内容,而是突出显示最需要关注的主题和领域。
对于本机映像,不再涉及JIT。 所有代码都预编译为取决于目标平台的机器指令。 但是,有趣的是,本机映像生成过程分为两个阶段:构建时间和运行时间。 这是本
机映像
可执行文件的命令行参数分为两个不同的组的原因之一:
-
带有
–H
前缀(托管选项):配置本机映像构建 -
带有
–R
前缀(运行时选项):初始值是在本机映像构建期间设置的,但可以在运行时覆盖(通常使用
-XX
前缀)。
强烈建议在构建阶段执行尽可能多的初始化,以减少启动时间。 默认情况下,类在映像运行时初始化,但是可以通过
--initialize-at-build-time
或
--initialize-at-run-time
命令行选项来调整此行为。 要跟踪哪些类已初始化以及为什么请传递方便的
-H:+PrintClassInitialization
命令行选项
.
即使是本机可执行文件,您的应用程序或服务仍会消耗内存。 合适的堆设置将根据系统配置和使用的GC
自动确定,
但是
仍然可以
在运行时使用
-Xmx
,
-Xms
和
-Xmn
命令行选项
提供您自己的配置
。
$ <your native executable> -Xmx32m -Xms16m
优选地,本
机映像
可执行文件使您可以选择在构建时分别使用
-R:MaxHeapSize
,
-R:MinHeapSize
和
-R:MaxNewSize
命令行选项来预先设置相同的堆设置。
$ native-image -R:MaxHeapSize=32m -R:MinHeapSize=16m ...
提前考虑预期的内存需求非常重要,因为
GraalVM
Community Edition中用于本机映像的默认(也是唯一)垃圾收集器(
GC
)是
Serial GC
。 它针对低内存占用和较小堆大小进行了优化。 此外,还存在一些限制:请记住,不会调用
终结
器。 早已
不推荐使用
此特定功能,建议将其替换为弱引用或/和引用队列。
系统属性的处理需要自己提及。 您
可以使用
–D
前缀,以通常的方式将
它们
与本机映像一起
使用
,但要记住构建和运行阶段是分开的:
-
将
-D<key>=<value>
传递给
本机映像
可执行文件会影响在映像生成时看到的属性 -
将
-D<key>=<value>
传递给本机图像可执行文件会影响在图像运行时看到的属性
自
GraalVM
20.3.0
发行以来,
本机映像
已成为容器感知的:在Linux上,从
cgroup
配置中读取资源限制(如处理器数量和可用内存大小)。 也可以使用选项(
-XX:ActiveProcessorCount=N
)在命令行上覆盖处理器数量。 另外,对于容器化环境,非常希望包含与JVM具有相同的信号处理程序。
--install-exit-handlers
命令行选项提供了这一点。
另一方面,使用
native-image时
,默认情况下仅支持
URL协议
的子集,因此您可以以最小的占用空间开始,以后再添加功能。 要启用
HTTP
和/或
HTTPS
协议,必须将
--enable-http
和/或
--enable-https
命令行参数传递给
本机映像
可执行文件。 另外,您可能更喜欢使用
--enable-url-protocols
命令行选项,该选项接受要启用的URL协议的细粒度列表。
默认情况下,仅捆绑核心安全服务的相同理由是在
本机图像生成
器中对
Java密码体系结构
(
JCA
)支持的背后。 必须通过包括
--enable-all-security-services
命令行选项(在启用
HTTPS
URL协议支持时默认启用)来启用其他
JCA
安全服务。
关于日志的最后一句话:开箱即用,
native-image仅
支持使用
java.util.logging
API的
日志记录功能
。
处理
动态代理
,
反射
和
资源
可能会使大多数开发人员感到最头疼。 幸运的是,使用
本机映像构建
的
辅助配置
可以大大减少将现有应用程序和服务成功打包为本机映像所需的工作。 它基本上依赖于
Java代理
,该
Java代理
在运行时进行自省,然后转储所有必需的配置。
本机独立可执行文件可能是您将最常生成的本机映像类型,但是您也可以通过向本
机映像生成
器提供
--shared
命令行参数来打包
共享库
(如果需要)。
工装
JVM平台上的大多数项目都使用
Apache Maven
,
Gradle
或
SBT
作为构建编排工具。 由于插件生态系统的存在,作为构建过程一部分的本地映像的生成实际上得到了所有人的很好支持。
以下是对
Apache Maven
构建定义的摘录,该定义将生成本机可执行文件以及常规JAR工件。
<plugin>
<groupId>org.graalvm.nativeimage</groupId>
<artifactId>native-image-maven-plugin</artifactId>
<version>20.3.0</version>
<executions>
<execution>
<goals>
<goal>native-image</goal>
</goals>
<phase>package</phase>
</execution>
</executions>
<configuration>
<mainClass>...</mainClass>
<imageName>...</imageName>
<buildArgs>...</buildArgs>
</configuration>
</plugin>
对于许多现有项目,您可能需要超越简单的配置,并利用高级技术,例如在映像构建过程中替换整个类或方法。
GraalVM
提供了一种通过引入
Substrate VM
API来实现此
目的
的方法。
<dependency>
<groupId>org.graalvm.nativeimage</groupId>
<artifactId>svm</artifactId>
<version>20.3.0</version>
<scope>provided</scope>
</dependency>
要利用现有应用程序和服务中的
GraalVM
多语言功能,可以使用正式的
GraalVM SDK
,这是
GraalVM
组件的API集合。
<dependency>
<groupId>org.graalvm.sdk</groupId>
<artifactId>graal-sdk</artifactId>
<version>20.3.0</version>
<scope>provided</scope>
</dependency>
除了领导者之外,您选择的构建工具和/或框架很可能已经在本地图像和
GraalVM
方面提供了相当流畅的体验。 但是无论如何,您总是可以直接回到命令行工具。
调试和故障排除
由于本
机映像
生成器生成本机二进制文件,因此后者不包含
Java虚拟机工具接口
(
JVMTI
)实现,并且标准的JVM调试技术也无法使用。 诸如
gdb
等本机调试器是了解运行时本机映像内部运行情况的唯一选择。 出于相同的原因,根本不支持用于处理JVM字节码的工具。
除了运行时之外,您还可以对映像构建阶段进行故障排除。 添加
--debug-attach[=<port>]
命令行选项并在映像生成期间附加调试器就足够了。
要获得有关垃圾回收的一些见解,可以在执行本机映像时添加以下命令行选项:
-
-XX:+PrintGC
:为每个垃圾回收打印基本信息 -
-XX:+VerboseGC
:打印更多垃圾收集详细信息
您可能会问堆转储呢? 使用
OpenJDK
或
JDK Mission Control
或
VisualVM
附带的标准命令行工具无法触发堆转储的创建。 但是您可以使用
GraalVM SDK
并以编程方式从应用程序或服务内部实现
堆转储生成
。
6.本机还是JIT?
一旦学习了将Java应用程序和服务打包为本地映像的方法,就会得到启发:为什么有人应该回去使用JVM? 与往常一样,这个问题的答案是“取决于”,我们将总结两者的利弊。
本机映像(SubstrateVM)
将JVM应用程序和服务作为本机可执行文件运行的好处是巨大的,但缺点和局限性也是如此。
在优点方面:
- 独立的本机可执行文件
- 启动时间非常快
- 较小的内存占用
- 可执行文件较小
- 更可预测的性能
在不利方面:
- 没有JIT编译器,因此峰值性能可能不是最佳的
- 只有简单的垃圾收集器(对于社区版)
- 打包原始图像并不总是那么容易
- 图像生成速度非常慢(但是时间有所改善)
-
尚不支持
JDK Flight Recorder
(
JFR
)之类的工具 - 需要使用本机调试器
那么,什么样的应用程序和服务将从打包为本地映像中受益最大? 以下陈述适用的陈述:
- 启动时间很重要
- 内存占用量很重要
- 中小型堆(最多几GB)
- 提前知道所有代码
- 最佳性能不是主要问题
如果您正在考虑
FaaS
和
无服务器
-这些可能是最好的例子。
具有GraalVM编译器的JVM
相反,让我们枚举在
Java HotSpot VM上
但与
GraalVM编译器
一起运行JVM应用程序的
优缺点
。
在优点方面:
- 这只是普通的JVM
- 出色的峰值性能(由于JIT优化)
- 大或小堆大小
- 提供各种垃圾收集器
- 很多熟悉的工具
在不利方面:
-
一个JVM的通常的足迹(可以用收缩
JLINK
) - JVM启动时间
- 可能需要比C2更长的时间才能达到最佳性能
明智的结论是,幸运的是,您最好以传统方式(带有
GraalVM编译器的
JVM)运行应用程序和服务,除非有明显的成功之路。
那GraalVM编译器呢?
我希望在
本机
运行应用程序和服务与
JVM / JIT
之间的权衡得到一些澄清。 但是,我们不应忘记对
GraalVM编译器
适用相同注意事项的事实,因为它可以用作
libgraal
(作为预编译的本机映像)或
jargraal
(动态执行的字节码)。
尽管
libgraal
是默认和推荐的操作模式,但是请记住,会产生一些后果:
-
由于这是本机图像,因此可能无法达到与
jargraal
相同的峰值性能 - 共享库的大小非常明显
从另一面看,
紫花苜蓿
并非全是玫瑰,还暴露出许多缺陷:
- 成为字节码意味着它确实会干扰应用程序字节码,包括堆,代码缓存,优化等。
- 可能需要一些时间才能达到最佳操作模式
可以使用
-XX:+CITime
命令行选项来解锁对编译速度的更直接的度量。 遵循建议并坚持
自由行
可能是一个明智的决定,但是了解您要进行的交易总是很有益的。
7. GraalVM和其他JVM
到目前为止,我们一直将
OpenJDK
和
Java HotSpot VM
称为
GraalVM
集成的平台。 关于其他Java VM,您可以将
GraalVM编译器
与
Eclipse OpenJ9
一起使用吗? 不幸的是,由于
Eclipse OpenJ9
尚未实现
JVMCI
(目前插入
GraalVM编译器
的必要先决条件),目前
似乎
尚无法实现。 有关可能的
JVMCI
支持的计划尚不确定。
8. GraalVM VisualVM
在本教程的这一部分中多次提到了
VisualVM
工具。 这是获得有关JVM上运行的应用程序和服务的直观见解的好方法。 它曾经是JDK发行版的一部分,但
在Oracle JDK 9
及更高版本中已被
删除
。
从那时起,
VisualVM
已
移至
GraalVM,
并在两个发行版中可用:独立(Java
VisualVM
)和与
GraalVM
发行版捆绑在一起。 至于后者,
VisualVM的
增加了对专项扶持
GraalVM
通晓多国语言的功能,比如在客人的语言水平分析方面的应用:JavaScript中,Python和Ruby和R语言
的支持
。
9.您的IDE中的GraalVM
如果将
GraalVM
发行版下载到您的IDE,则感觉(和看起来)像是常规的
OpenJDK
发行版,这并不奇怪。 对于
Apache Netbeans
,
IntelliJ Idea
或
Eclipse
,同样适用于开发,运行和调试JVM应用程序和服务的经验。
此外,
GraalVM Extension
(在技术预览阶段)可以方便地提供与
Visual Studio Code
的集成。
创建
GraalVM
VS Code Extension的主要目标是在
VS Code中
启用多语言环境,使其适合并方便地从集成开发环境中使用
GraalVM
,以允许
VS Code
用户编辑和调试以任何方式编写的应用程序。
GraalVM
支持的动态语言(JS,Ruby,R和Python)。
从个人经验来看,近年来,
Visual Studio Code
在Java和Scala开发人员中赢得了很多关注。 它已成为许多人的默认IDE,而专用
GraalVM Extension
的存在正与其他特权叠加。
10.案例研究
现在是时候看看JVM生态系统中一些广受欢迎的项目,并按照它们的路线发展,尤其是支持
GraalVM
和
本机映像
。 这些是在多年活跃发展背后建立起来的知名品牌,这些品牌诞生于
GraalVM
尚未出现的时候。 请毫不犹豫地将它们用作您自己的应用程序和服务的蓝图。
净额
Netty
是在JVM上构建高性能网络应用程序的基础框架之一。 近年来,它的受欢迎程度猛增,部分原因是React式编程范例的广泛采用。
Netty
是一个异步事件驱动的网络应用程序框架,用于快速开发可维护的高性能协议服务器和客户端。
尽管
Netty
是
GraalVM的
早期支持者
之一,
而
这些
支持者
又反过来(或至少简化了)上游项目追随领导者的方式。
弹簧
在JVM上,
Spring平台
曾经是并且仍然是用于构建各种应用程序和服务的主要技术堆栈。 它非常成熟,已经在生产中经过了实战测试,其
产品组合
非常令人印象深刻,而且很难找到与
Spring
没有集成的或多或少受欢迎的项目。 同时,这是一个优点和缺点,尤其是当核心技术实施决策与
当前GraalVM限制
冲突时。
到目前为止,
Spring
项目对
本机映像
的支持被标记为试验性的,并托管在
单独的存储库下
。 很大程度上是通过利用
Substrate VM
API提供的高级替换功能来实现的。 希望在Spring Framework 6见光时,实验标签将被删除,从而使基于
Spring
的应用程序和服务的
本机图像
支持在黄金时间准备就绪。
11.未来之路
总结我们的讨论的最佳方法,以触及旨在消除许多现有限制(尤其是与
本机图像
生成相关的限制)的当前和将来的发展。
-
对
Java平台模块系统
(
JPMS
)的支持:
https
:
//github.com/oracle/graal/issues/1962
-
支持方法句柄和invokedynamic字节码:
https
:
//github.com/oracle/graal/issues/2761
-
改进对资源包和语言环境的支持:
https
:
//github.com/oracle/graal/issues/2982
GraalVM
的变更步伐
是非同
寻常的,并且很快就可以在下一版本中看到至少其中一些解决方案。 除此之外,值得关注以下正在进行的工作。
-
支持Java序列化:
https
:
//github.com/oracle/graal/pull/2730
-
支持动态类加载:
https
:
//github.com/oracle/graal/pull/2442
-
添加对Java本机图像的JDK Flight Recorder支持:
https
:
//github.com/oracle/graal/pull/3070
这些只是从数百个问题和请求中挑选出来的。 总是需要帮助,请随时加入社区并开始贡献自己的力量!
12.下一步是什么
在本教程的下一部分中,我们将讨论
云计算
,它对JVM应用程序和服务提出的挑战,
GraalVM
如何解决这些问题并讨论新一代的云原生库和框架。
jvm优化