目录
1. GraalVM深度解析
1.1 什么是GraalVM
GraalVM 是Oracle官方推出的高性能JDK,相比OpenJDK/OracleJDK具有更好的性能表现。官方标语:Build faster, smaller, leaner applications。
核心优势:
- 更低的CPU和内存使用率
- 更快的启动速度,无需预热即可获得最佳性能
- 更好的安全性,更小的可执行文件
- 支持Spring Boot、Micronaut、Helidon、Quarkus等框架
- 多云平台支持
- 通过Truffle框架运行JS、Python、Ruby等多种语言
版本对比:
| 特性 | 社区版(Community) | 企业版(Enterprise) |
|---|---|---|
| 收费模式 | 免费 | 收费 |
| G1垃圾回收器优化 | ❌ | ✅ |
| PGO(Profile Guided Optimization) | ❌ | ✅ |
| 高级优化特性 | ❌ | ✅ |
| 高级优化参数 | ❌ | ✅ |
性能对比数据:
- CPU和内存使用率显著降低
- 启动速度和首次响应时间大幅提升(Quarkus+Native可达0.016秒 vs 传统4.3秒)
1.2 GraalVM的两种模式
JIT模式(Just-In-Time)
与Oracle JDK类似的特点:
- Write Once, Run Anywhere(一次编写,到处运行)
- 预热后通过Graal JIT编译器优化热点代码,生成高性能机器码
AOT模式(Ahead-Of-Time)
提前编译模式,为特定平台创建可执行文件:
- 优点:启动后获得最高性能
- 缺点:不具备跨平台特性,不同平台需单独编译
性能测试对比:
bash
# 测试结果显示GraalVM开启Graal编译器性能最佳
JDK8: 233.307 ms/op
JDK21: 240 ms/op
GraalVM+JIT: 243 ms/op
GraalVM-JIT: 未测试
1.3 应用场景与解决方案
AOT模式适用场景:
- 公有云函数计算(按CPU/内存使用量计费)
- Serverless应用架构
- 对启动速度要求极高的场景
面临的挑战及解决方案:
| 挑战 | 解决方案 |
|---|---|
| 跨平台问题 | 使用Docker容器化平台在线编译 |
| 编译时间长、资源消耗大 | 使用阿里云等镜像服务器 |
| 框架兼容性问题(反射、动态代理) | 使用SpringBoot3等已适配GraalVM的框架版本 |
SpringBoot3整合GraalVM步骤:
- 使用https://start.spring.io/生成项目,添加"GraalVM Native Support"依赖
- 编写业务代码(注意:移除@PostConstructor注解)
- 执行命令:
mvn -Pnative clean native:compile - 运行生成的本地镜像(大小约80MB,启动极快)
1.4 参数优化与故障诊断
社区版限制:
- 仅支持串行垃圾回收器(Serial GC)
- 默认最大堆大小为物理内存的80%,通过
-Xmx或-R:MaxHeapSize调整
企业版优化:
- 支持G1垃圾回收器:
--gc=G1 - Epsilon GC(无GC行为):
--gc=epsilon(适用于短暂运行、不产生大量对象的场景)
垃圾回收日志:
bash
# 添加参数
-XX:+PrintGC -XX:+VerboseGC
# 日志示例
[44.108s][gc] Old: 504.50M->604.00M
[44.108s][gc] Full GC (Collection on allocation) 604.50M->604.00M 16.436ms
实战案例:内存快照分析
- 编译时添加监控支持
xml
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
<configuration>
<buildArgs>
<arg>--enable-monitoring=heapdump,jfr</arg>
</buildArgs>
</configuration>
</plugin>
- 运行时生成快照
bash
# 获取进程ID
ps -ef | grep native
# 发送信号生成快照
kill -SIGUSR1 <进程ID>
# 生成文件:svm-heapdump.hprof
- 使用MAT分析:与普通JVM生成的hprof文件分析方法相同
实战案例:运行时数据获取(JFR)
JDK Flight Recorder支持在GraalVM本地镜像中使用:
- 编译时添加
--enable-monitoring=jfr - 运行时添加参数:
-XX:+FlightRecorder -XX:StartFlightRecording=duration=60s,filename=recording.jfr - 使用VisualVM或JMC分析生成的JFR文件
2. 新一代垃圾回收器
2.1 技术演进路线
技术演进:
STW → 并行回收 → 并发标记 → 并发清理 → 并发复制整理
年轻代:
- 复制算法(PS+PO、ParNew+CMS、G1、Shenandoah、ZGC)
老年代:
- 标记-整理(PS+PO)
- 并发标记-清理(CMS)
- 复制-整理(G1)
- 并行复制-整理(Shenandoah、ZGC)
不同GC设计目标对比:
- Parallel GC:吞吐量优先
- G1:平衡吞吐量和停顿时间
- Shenandoah:超低延迟
- ZGC:亚毫秒级停顿
2.2 Shenandoah GC
特点:
- 由Red Hat开发,OpenJDK特性
- 并发执行大部分GC工作,包括并发整理
- 堆大小对STW时间基本无影响
- 适合小对象场景,停顿时间极短
环境搭建:
-
下载OpenJDK with Shenandoah
- 地址:https://builds.shipilev.net/openjdk-jdk-shenandoah/
- 选择:
linux-x86_64-server-release-gcc12-glibc2.36.tar.xz
-
配置环境变量
bash
export JAVA_HOME=/usr/local/jvm/openjdk-shenandoah
export PATH=$JAVA_HOME/bin:$PATH
- 验证安装
bash
java -version
# openjdk version "22-testing" 2024-03-19
# OpenJDK Runtime Environment (build 22-testing-...)
# OpenJDK 64-Bit Server VM (build 22-testing..., mixed mode, sharing)
- 运行参数
bash
-XX:+UseShenandoahGC
-Xlog:gc # 打印GC日志
性能测试结果:
- 小对象(4KB):停顿时间极短,约0.5-1ms
- 大对象(4MB):效果不佳,停顿时间较长
2.3 ZGC(Z Garbage Collector)
核心特性:
- 亚毫秒级最大暂停时间:STW时间不超过1毫秒
- 可扩展性:支持从几百MB到16TB堆大小
- 堆大小不影响停顿时间
- JDK21支持分代:进一步提升性能
版本演进:
JDK11: 实验版本
JDK12-13: 并行类卸载支持
JDK14-15: Windows & macOS支持
JDK16-17: 亚毫秒级停顿
JDK21: 支持分代
环境配置:
bash
# 分代ZGC(JDK17+推荐)
-XX:+UseZGC -XX:+ZGenerational
# 非分代ZGC
-XX:+UseZGC
# 无需设置:
# -Xmn(自动设置年轻代大小)
# -XX:TenuringThreshold(自动晋升阈值)
# -XX:ConcGCThreads(JDK17+自动计算)
# 必须设置:
-Xmx<value> # 最大堆大小
-XX:SoftMaxHeapSize=<value> # 软限制(可选)
Huge Page大页优化:
bash
# 1. 计算页数(2MB/页)
# 例如18GB堆:18*1024/2 = 9216页
# 2. 配置大页池(需root权限)
echo 9216 > /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages
# 3. 启动参数添加
-XX:+UseLargePages
性能测试结果:
| GC类型 | 4KB对象 | 4MB对象 | 停顿时间 | 吞吐量 |
|---|---|---|---|---|
| G1 | 788ms | 1155ms | 较长 | 高 |
| Parallel | 970ms | 2481ms | 长 | 最高 |
| Shenandoah | 817ms | 816ms | 极短 | 较低 |
| ZGC | 828ms | 816ms | 亚毫秒 | 中等 |
| ZGC分代 | 890ms | 828ms | 亚毫秒 | 较好 |
2.4 实战案例:软引用缓存GC表现对比
测试场景:
- 大量软引用缓存导致内存不足
- 对比G1、Shenandoah、ZGC的回收表现
测试代码:
java
@RestController
@RequestMapping("/fullgc")
public class Demo2Controller {
private Cache<String, byte[]> cache = Caffeine.newBuilder()
.weakKeys()
.softValues()
.build();
private List<Object> objs = new ArrayList<>();
private static final int _1MB = 1024 * 1024;
@GetMapping("/1")
public void test() throws InterruptedException {
cache.put(RandomStringUtils.randomAlphabetic(8),
new byte[10 * _1MB]);
}
}
测试步骤:
- 启动程序并添加不同GC参数
- 使用Apache Benchmark压测:
ab -n 10000 -c 100 http://localhost:8882/fullgc/1 - 生成GC日志并使用GcEasy分析
- 对比结果:
结论:
- 内存充足时:ZGC表现最佳,停顿时间最短
- 内存紧张时:Shenandoah GC表现更好,并行回收时间更短,用户请求执行效率更高
3. Java工具核心技术
3.1 Java Agent技术
Java Agent是JDK提供的技术,用于编写Java工具类程序,可以在Java程序运行时执行Agent中的代码。
两种加载模式:
静态加载模式
- 触发时机:程序启动时立即执行
- 应用场景:APM等需要从一开始就监控的系统
- 实现方式 :编写
premain方法
java
public static void premain(String agentArgs, Instrumentation inst) {
System.out.println("Java Agent执行了...");
}
MANIFEST.MF配置:
Manifest-Version: 1.0
Premain-Class: com.itheima.jvm.javaagent.AgentDemo
Can-Redefine-Classes: true
Can-Retransform-Classes: true
启动命令:
bash
java -javaagent:./agent.jar -jar test.jar
动态加载模式
- 触发时机:程序运行中随时挂载
- 应用场景:Arthas等诊断工具
- 实现方式 :编写
agentmain方法
java
public static void agentmain(String agentArgs, Instrumentation inst) {
System.out.println("Attach模式执行了...");
}
动态挂载代码:
java
VirtualMachine vm = VirtualMachine.attach("24200"); // 进程ID
vm.loadAgent("agent.jar");
3.2 实战案例1:简化版Arthas
需求: 实现无侵入的Java诊断工具,具备以下功能:
- 查看内存使用情况
- 生成堆内存快照
- 打印线程栈信息
- 打印类加载器
- 打印类的源码
- 打印方法执行参数和耗时
功能1:查看内存使用情况
java
private static void memory() {
List<MemoryPoolMXBean> memoryPoolMXBeans =
ManagementFactory.getMemoryPoolMXBeans();
System.out.println("堆内存:");
getMemoryInfo(memoryPoolMXBeans, MemoryType.HEAP);
System.out.println("非堆内存:");
getMemoryInfo(memoryPoolMXBeans, MemoryType.NON_HEAP);
// 获取直接内存和内存映射
Class<?> bufferPoolMXBeanClass =
Class.forName("java.lang.management.BufferPoolMXBean");
List<BufferPoolMXBean> bufferPoolMXBeans =
ManagementFactory.getPlatformMXBeans(bufferPoolMXBeanClass);
}
功能2:生成堆内存快照
java
public static void heapDump() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd-HH-mm");
String filename = sdf.format(new Date()) + ".hprof";
HotSpotDiagnosticMXBean mxBean =
ManagementFactory.getPlatformMXBean(HotSpotDiagnosticMXBean.class);
mxBean.dumpHeap(filename, true); // true表示仅dump存活对象
}
功能3:打印线程栈信息
java
public static void printStackInfo() {
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
ThreadInfo[] infos = threadMXBean.dumpAllThreads(
threadMXBean.isObjectMonitorUsageSupported(),
threadMXBean.isSynchronizerUsageSupported()
);
for (ThreadInfo info : infos) {
System.out.println("name:" + info.getThreadName() +
" state:" + info.getThreadState());
StackTraceElement[] stackTrace = info.getStackTrace();
for (StackTraceElement element : stackTrace) {
System.out.println(element.toString());
}
}
}
功能4:打印类加载器
java
private static Set<ClassLoader> getAllClassLoader(Instrumentation inst) {
HashSet<ClassLoader> classLoaders = new HashSet<>();
Class<?>[] allLoadedClasses = inst.getAllLoadedClasses();
for (Class<?> clazz : allLoadedClasses) {
classLoaders.add(clazz.getClassLoader());
}
return classLoaders;
}
功能5:打印类的源码
java
public static void printClass(Instrumentation inst, String className) {
Class<?>[] allLoadedClasses = inst.getAllLoadedClasses();
for (Class<?> clazz : allLoadedClasses) {
if (clazz.getName().equals(className)) {
// 使用Instrumentation获取字节码
ClassFileTransformer transformer = new ClassFileTransformer() {
@Override
public byte[] transform(...) {
// 使用JD-Core反编译
ClassFileToJavaSourceDecompiler decompiler =
new ClassFileToJavaSourceDecompiler();
decompiler.decompile(loader, printer, className);
return classfileBuffer;
}
};
inst.addTransformer(transformer, true);
inst.retransformClasses(clazz);
inst.removeTransformer(transformer);
}
}
}
功能6:打印方法执行的参数和耗时
字节码增强技术选择:
- ASM:性能极高,但API复杂
- Byte Buddy:基于ASM的易用框架,API友好
Byte Buddy实现:
java
public class TimingAdvice {
@Advice.OnMethodEnter
static long enter(@Advice.AllArguments Object[] args) {
if (args != null) {
for (int i = 0; i < args.length; i++) {
System.out.println("Argument:" + i + " is " + args[i]);
}
}
return System.nanoTime(); // 返回开始时间
}
@Advice.OnMethodExit
static void exit(@Advice.Enter long startTime,
@Advice.Origin("#t") String className,
@Advice.Origin("#m") String methodName) {
long cost = System.nanoTime() - startTime;
System.out.println(methodName + "@" + className +
"耗时为:" + cost + "纳秒");
}
}
// Agent中配置
new AgentBuilder.Default()
.type(ElementMatchers.named("com.example.TargetClass"))
.transform((builder, type, cl, module, pd) ->
builder.visit(Advice.to(TimingAdvice.class)
.on(ElementMatchers.any()))
)
.installOn(inst);
打包方式:
使用maven-shade-plugin将依赖打入同一jar包并指定Main类
xml
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>1.4</version>
<configuration>
<finalName>itheima-attach-agent</finalName>
<transformers>
<transformer implementation="...">
<mainClass>com.itheima.jvm.javaagent.AttachMain</mainClass>
</transformer>
</transformers>
</configuration>
</plugin>
3.3 实战案例2:APM系统数据采集
需求: 编写简化版APM系统,无侵入性地采集Spring Boot应用Controller层方法的调用时间并写入文件。
技术选型:
- 静态加载模式:程序启动即开始采集
- Byte Buddy:字节码增强
- JMX:获取基础监控数据
参数传递机制:
java
// 启动时传递参数
java -javaagent:./agent.jar=agent.log=/tmp/perf.log -jar app.jar
// Agent中获取参数
public static void premain(String agentArgs, Instrumentation inst) {
// agentArgs: "agent.log=/tmp/perf.log"
Map<String, String> params = parseParams(agentArgs);
}
// Byte Buddy绑定参数
Advice.withCustomMapping()
.bind(AgentParam.class, agentArgs)
.to(TimingAdvice.class)
Advice实现:
java
public class TimingAdvice {
@Advice.OnMethodEnter
static long enter() {
return System.nanoTime();
}
@Advice.OnMethodExit
static void exit(@Advice.Enter long startTime,
@Advice.Origin("#t") String className,
@Advice.Origin("#m") String methodName,
@AgentParam("agent.log") String fileName) {
String log = String.format("%s@%s耗时:%dns%n",
methodName, className,
System.nanoTime() - startTime);
FileUtils.writeStringToFile(new File(fileName), log,
StandardCharsets.UTF_8, true);
}
}
// 自定义注解
@Retention(RetentionPolicy.RUNTIME)
public @interface AgentParam {
String value();
}
类匹配策略:
java
.type(ElementMatchers.isAnnotatedWith(
ElementMatchers.named("org.springframework.web.bind.annotation.RestController")
.or(ElementMatchers.named("org.springframework.stereotype.Controller"))
))
数据采集结果示例:
test@com.itheima.fullgcdemo.Demo2Controller耗时为:6327431纳秒
test@com.itheima.fullgcdemo.Demo2Controller耗时为:2310297纳秒
3.4 面试重点
Q1: Arthas使用了哪些Java技术?
Arthas主要使用了Java Agent技术,采用动态加载模式。
通过Instrumentation对象获取JMX监控指标,使用字节码增强技术(基于ASM/BCEL)
对目标类和方法进行增强,从而实现方法耗时、参数、返回值等信息的监控。
Q2: APM系统如何获取Java程序运行性能数据?
APM系统如SkyWalking使用Java Agent静态加载模式。
通过字节码增强技术拦截关键路径(如Controller、Service、DAO层),
采集方法执行耗时、SQL语句、RPC调用等信息。
数据通过本地队列或网络上报到Collector,最终存储到ES等存储系统并可视化展示。
技术栈总结:
Java Agent ←→ Instrumentation ←→ JMX监控
↓
Byte Buddy/ASM ←→ 字节码增强 ←→ 方法拦截
↓
Advice通知 ←→ 数据采集 ←→ 日志/网络上报
总结
本高级篇深入介绍了JVM领域的三大前沿技术:
- GraalVM:通过AOT编译实现极速启动和低资源消耗,适合Serverless和云原生场景
- 新一代GC:Shenandoah和ZGC实现亚毫秒级停顿,满足低延迟业务需求
- Java Agent:JVM工具的核心技术,实现无侵入监控和诊断
选型建议:
- 传统应用:JDK 17 + G1 GC
- 云原生/Serverless:GraalVM AOT + Native Image
- 超低延迟:ZGC(分代模式)
- 监控诊断:Java Agent + Byte Buddy组合
生产实践要点:
- GraalVM编译需确保环境一致性
- ZGC建议设置
-Xmx和-XX:SoftMaxHeapSize - Java Agent注意参数传递和类匹配性能
- APM采集需考虑对业务性能的影响,异步上报是关键