JVM高级篇总结笔记

目录

  1. GraalVM深度解析
  2. 新一代垃圾回收器
  3. Java工具核心技术

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模式适用场景:

  1. 公有云函数计算(按CPU/内存使用量计费)
  2. Serverless应用架构
  3. 对启动速度要求极高的场景

面临的挑战及解决方案:

挑战 解决方案
跨平台问题 使用Docker容器化平台在线编译
编译时间长、资源消耗大 使用阿里云等镜像服务器
框架兼容性问题(反射、动态代理) 使用SpringBoot3等已适配GraalVM的框架版本

SpringBoot3整合GraalVM步骤:

  1. 使用https://start.spring.io/生成项目,添加"GraalVM Native Support"依赖
  2. 编写业务代码(注意:移除@PostConstructor注解)
  3. 执行命令:mvn -Pnative clean native:compile
  4. 运行生成的本地镜像(大小约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

实战案例:内存快照分析

  1. 编译时添加监控支持
xml 复制代码
<plugin>
    <groupId>org.graalvm.buildtools</groupId>
    <artifactId>native-maven-plugin</artifactId>
    <configuration>
        <buildArgs>
            <arg>--enable-monitoring=heapdump,jfr</arg>
        </buildArgs>
    </configuration>
</plugin>
  1. 运行时生成快照
bash 复制代码
# 获取进程ID
ps -ef | grep native

# 发送信号生成快照
kill -SIGUSR1 <进程ID>

# 生成文件:svm-heapdump.hprof
  1. 使用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时间基本无影响
  • 适合小对象场景,停顿时间极短

环境搭建:

  1. 下载OpenJDK with Shenandoah

  2. 配置环境变量

bash 复制代码
export JAVA_HOME=/usr/local/jvm/openjdk-shenandoah
export PATH=$JAVA_HOME/bin:$PATH
  1. 验证安装
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)
  1. 运行参数
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]);
    }
}

测试步骤:

  1. 启动程序并添加不同GC参数
  2. 使用Apache Benchmark压测:ab -n 10000 -c 100 http://localhost:8882/fullgc/1
  3. 生成GC日志并使用GcEasy分析
  4. 对比结果:

结论:

  • 内存充足时: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. 查看内存使用情况
  2. 生成堆内存快照
  3. 打印线程栈信息
  4. 打印类加载器
  5. 打印类的源码
  6. 打印方法执行参数和耗时
功能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领域的三大前沿技术:

  1. GraalVM:通过AOT编译实现极速启动和低资源消耗,适合Serverless和云原生场景
  2. 新一代GC:Shenandoah和ZGC实现亚毫秒级停顿,满足低延迟业务需求
  3. 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采集需考虑对业务性能的影响,异步上报是关键
相关推荐
Ttang231 小时前
【AI篇3】在Java项目中调用大模型API
java·人工智能·microsoft·ai·api
豐儀麟阁贵1 小时前
9.4字符串操作
java·linux·服务器·开发语言
武子康1 小时前
Java-181 OSS 实战指南:Bucket/外链/防盗链/计费与常见坑
java·大数据·分布式·oss·云存储·fastdfs·ali
摇滚侠1 小时前
2025最新 SpringCloud 教程,Gateway-过滤器-globalFilter,笔记60
笔记·spring cloud·gateway
聆风吟º1 小时前
【Spring Boot 报错已解决】告别“Whitelabel Error Page”:Spring Boot 404报错的排查指南
java·spring boot·后端
w10463672p1 小时前
java解析CSV文件(一)——Java使用Apache.Commons.CSV解析CSV文件应用实践
java·apache·springboot·csv
weixin_307779131 小时前
Jenkins Gson API插件:统一JSON处理的基础库
java·运维·开发语言·架构·jenkins
im_AMBER1 小时前
Leetcode 69 正整数和负整数的最大计数
数据结构·笔记·学习·算法·leetcode
摇滚侠1 小时前
2025最新 SpringCloud 教程,Gateway-过滤器-默认过滤器,笔记59
笔记·spring cloud·gateway