1. GraalVM
GraalVM是Oracle官方推出的一款高性能JDK,使用它享受比OpenJDK或者OracleJDK更好的性能。
更低的CPU、内存使用率
更快的启动速度,无需预热即可获得最好的性能
更好的安全性、更小的可执行文件
支持多种框架Spring Boot、Micronaut、Helidon 和 Quarkus.
多家云平台支持。
通过Truffle框架运行JS、Python、Ruby等其他语言。
2. 运行模式
2.1 JIT模式
即时编译模式
JIT(Just-In-Time)模式的处理方式与Oracle JDK类似,满足两个特点:
- Write Once, Run Anywhere -> 一次编写,到处运行
- 预热之后,通过内置的Graal即时编译器优化热点代码,生成比Hotspot JIT更高性能的机器码
2.2 AOT模式
提前编译模式
AOT(Ahead-Of-Time)编译器通过源代码,为特定平台创建可执行文件。比如,在Windows下编译完成之后,会生成exe文件。通过这种方式,达到启动之后获得最高性能的目的。但是不具备跨平台特性,不同平台使用需要单独编译。
这种模式生成的文件称之为Native Image本地镜像。
注:这种模式同样需要先将源代码编译为class文件,再通过 native-image 类名
制作本地镜像
3. 应用场景
GraalVM的AOT模式虽然在启动速度、内存和CPU开销上非常有优势,但是使用这种技术会带来几个问题:
- 跨平台问题,在不同平台下运行需要编译多次。编译平台的依赖库等环境要与运行平台保持一致。
- 使用框架之后,编译本地镜像的时间比较长,同时也需要消耗大量的CPU和内存。
- AOT 编译器在编译时,需要知道运行时所有可访问的所有类。但是Java中有一些技术可以在运行时创建类,例如反射、动态代理等。这些技术在很多框架比如Spring中大量使用,所以框架需要对AOT编译器进行适配解决类似的问题。
解决方案:
- 使用公有云的Docker等容器化平台进行在线编译,确保编译环境和运行环境是一致的,同时解决了编译资源问题
- 使用SpringBoot3等整合了GraalVM AOT模式的框架版本
3.1 SpringBoot3搭建GraalVM
- 使用 https://start.spring.io/spring 提供的在线生成器构建项目。
- 编写业务代码。
- 执行
mvn -Pnative clean native:compile
命令生成本地镜像。 - 运行本地镜像。
3.2 Serverless架构-函数计算
随着虚拟化技术、云原生技术的愈发成熟,云服务商提供了一套称为Serverless无服务器化的架构。企业无需进行服务器的任何配置和部署,完全由云服务商提供。比较典型的有亚马逊AWS、阿里云等。
Serverless架构中第一种常见的服务是函数计算(Function asa Service),将一个应用拆分成多个函数,每个函数会以事件驱动的方式触发。典型代表有AWS的Lambda、阿里云的FC。
函数计算主要应用场景有如下几种:
- 小程序、API服务中的接口,此类接口的调用频率不高,使用常规的服务器架构容易产生资源浪费,使用Serverless就可以实现按需付费降低成本,同时支持自动伸缩能应对流量的突发情况。
- 大规模任务的处理,比如音视频文件转码、审核等,可以利用事件机制当文件上传之后,自动触发对应的任务。
函数计算的计费标准中包含CPU和内存使用量,所以使用GraalVM AOT模式编译出来的本地镜像可以节省更多的成本。
3.3 Serverless架构-Serverless应用
函数计算的服务资源比较受限,比如AWS的Lambda服务一般无法支持超过15分钟的函数执行,所以云服务商提供了另外一套方案: 基于容器的Serverless应用,无需手动配置K8s中的Pod、Service等内容,只需选择镜像就可自动生成应用服务。
同样,Serverless应用的计费标准中包含CPU和内存使用量,所以使用GraalVM AOT模式编译出来的本地镜像可以节省更多的成本。
4. 参数优化和故障诊断
4.1 内存参数
由于GraalVM是一款独立的JDK,所以大部分HotSpot中的虚拟机参数都不适用。
社区版只能使用串行垃圾回收器(Serial Gc),使用串行垃圾回收器的默认最大 Java 堆大小会设置为物理内存大小的 80%,调整方式为使用-Xmx最大堆大小
。如果希望在编译期就指定该大小,可以在编译时添加参数-R:MaxHeapSize=最大堆大小
G1垃圾回收器只能在企业版中使用,开启方式为添加--gc=G1
参数,有效降低垃圾回收的延迟
另外提供一个Epsilon Gc,开启方式:--gc=epsilon
,它不会产生任何的垃圾回收行为所以没有额外的内存、CPU开销。如果在公有云上运行的程序生命周期短暂不产生大量的对象,可以使用该垃圾回收器以节省最大的资源。
-XX:+PrintGC
-XX:+VerboseGC
参数打印垃圾回收详细信息
获取内存快照文件
- 编译程序时,添加
--enable-monitoring=heapdump
,参数添加到pom文件的对应插件中。 - 运行中使用
kill -SIGUSR1 进程ID
命令,创建内存快照文件。 - 使用MAT分析内存快照文件。
获取运行时数据
JDK Flight Recorder (JFR)是一个内置于 JVM 中的工具,可以收集正在运行中的 Java 应用程序的诊断和分析数据,比如线程、异常等内容。GraalVM本地镜像也支持使用JFR生成运行时数据,导出的数据可以使用VisualVM分析。
- 编译程序时,添加
--enable-monitoring=jfr
,参数添加到pom文件的对应插件中。 - 运行程序,添加
-XX:StartFlightRecording="filename=recording.jfr,duration=10s"
参数。 - 使用VisualVM分析JFR记录文件。