JVM(5) GraalVM
2024-11-14 11:10:03 # Language # Java

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 类名 制作本地镜像

image-20241113162630777

3. 应用场景

GraalVM的AOT模式虽然在启动速度、内存和CPU开销上非常有优势,但是使用这种技术会带来几个问题:

  1. 跨平台问题,在不同平台下运行需要编译多次。编译平台的依赖库等环境要与运行平台保持一致。
  2. 使用框架之后,编译本地镜像的时间比较长,同时也需要消耗大量的CPU和内存。
  3. AOT 编译器在编译时,需要知道运行时所有可访问的所有类。但是Java中有一些技术可以在运行时创建类,例如反射、动态代理等。这些技术在很多框架比如Spring中大量使用,所以框架需要对AOT编译器进行适配解决类似的问题。

解决方案:

  1. 使用公有云的Docker等容器化平台进行在线编译,确保编译环境和运行环境是一致的,同时解决了编译资源问题
  2. 使用SpringBoot3等整合了GraalVM AOT模式的框架版本

3.1 SpringBoot3搭建GraalVM

  1. 使用 https://start.spring.io/spring 提供的在线生成器构建项目。
  2. 编写业务代码。
  3. 执行 mvn -Pnative clean native:compile 命令生成本地镜像。
  4. 运行本地镜像。

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 参数打印垃圾回收详细信息

获取内存快照文件

  1. 编译程序时,添加 --enable-monitoring=heapdump,参数添加到pom文件的对应插件中。
  2. 运行中使用 kill -SIGUSR1 进程ID 命令,创建内存快照文件。
  3. 使用MAT分析内存快照文件。

获取运行时数据

JDK Flight Recorder (JFR)是一个内置于 JVM 中的工具,可以收集正在运行中的 Java 应用程序的诊断和分析数据,比如线程、异常等内容。GraalVM本地镜像也支持使用JFR生成运行时数据,导出的数据可以使用VisualVM分析。

  1. 编译程序时,添加 --enable-monitoring=jfr,参数添加到pom文件的对应插件中。
  2. 运行程序,添加 -XX:StartFlightRecording="filename=recording.jfr,duration=10s"参数。
  3. 使用VisualVM分析JFR记录文件。