前言
这篇继续记录java新版本-java25学习笔记,主题是 JFR 和 GC 日志。
Java 25 评估时,我觉得一定要做一次运行时体检。
所谓体检,不是看应用能不能启动,也不是跑几个接口返回 200。而是用 JFR、GC 日志、指标和压测数据看看:服务在新 JDK 下的内存、线程、GC、锁、热点方法和外部调用是否正常。
JDK 升级最怕的是“看起来没问题”。很多问题只有在流量上来、数据量变大、某个定时任务触发后才会暴露。
Java 25 里 JFR 更值得学
JFR 不是 Java 25 才有,但 Java 25 继续增强了 JFR 能力。
这次可以重点关注几类 JEP:
JEP 509:JFR CPU-Time Profiling,实验特性。JEP 518:JFR Cooperative Sampling。JEP 520:JFR Method Timing & Tracing。
如果和几个 LTS 版本对比,Java 8 时代很多团队还更依赖外部 profiler;Java 11、17、21 之后 JFR 已经越来越适合作为 JVM 体检工具;到了 Java 25,学习 JFR 基本可以算学习现代 Java 运行时的一部分。
为什么要看 JFR
JFR 是 Java Flight Recorder,它能记录 JVM 内部发生的很多事件。
比如方法采样、对象分配、GC、锁等待、线程状态、文件 IO、Socket IO。它不像普通日志那样需要我们提前在业务代码里埋点,也不像单次线程栈那样只能看到一个瞬间。
升级 JDK 后,JFR 很适合做对比:
1.同一个接口热点方法是否变化。 2.对象分配是否变多。 3.GC 频率是否变化。 4.锁等待是否增加。 5.线程状态是否异常。 6.启动阶段是否有明显慢点。
这些都能帮助我们判断新运行时是否稳定。
一个 Java 25 JFR 小练习
先写一个容易产生 CPU 和对象分配的小程序。
新建jfr-demo.java:
import java.util.ArrayList;
void main() {
var result = new ArrayList<String>();
for (int i = 0; i < 200_000; i++) {
result.add("java25-" + i);
}
long count = result.stream()
.filter(item -> item.endsWith("25"))
.count();
System.out.println("matched = " + count);
}
可以直接带 JFR 参数运行:
java -XX:StartFlightRecording=filename=java25-demo.jfr,duration=20s,settings=profile jfr-demo.java
也可以先启动应用,再用jcmd录制。
录制时间不要太长
做体检时,JFR 不需要一上来录几个小时。
如果是接口压测,可以录 5 到 10 分钟。如果是启动分析,可以只录启动阶段。如果是定时任务,可以覆盖任务执行窗口。
示例命令:
jcmd <pid> JFR.start name=java25-check settings=profile duration=10m filename=java25-check.jfr
录制时要记录场景:压测流量多少、接口是什么、数据量多少、JDK 版本、JVM 参数。否则拿到文件后很难解释。
JFR 文件不是越大越好。场景明确,分析才有效。
先看 CPU 热点
打开 JFR 后,我一般先看 CPU 热点。
如果升级前后同一个接口热点方法变化很大,就要关注。可能是新 JDK 下某个库走了不同路径,也可能是压测场景不一致。
热点方法不一定都是问题。比如 JSON 序列化、SQL 映射、业务计算,本来就会消耗 CPU。关键是看是否合理。
如果看到大量时间花在反射、字符串拼接、日志格式化、对象转换,就值得回代码里看看。
JFR 的好处是它给的是证据,不是感觉。
再看对象分配
GC 压力很多时候来自对象分配。
接口本身不一定慢,但每次请求创建大量临时对象,流量上来后 GC 就会频繁。升级 JDK 时,如果某些库版本也变了,对象分配模式可能会变化。
我会看:
1.哪些类分配最多。 2.分配发生在哪些调用栈。 3.是否有大对象。 4.是否有意外的缓存失效。 5.是否有重复创建的临时集合。
很多优化不需要很复杂。比如避免循环里重复创建格式化器、减少不必要的 DTO 转换、控制日志拼接,都可能降低分配。
GC 日志仍然要开
有了 JFR,不代表 GC 日志没用了。
GC 日志适合看长期趋势和具体停顿。JFR 更适合结合事件做分析。两者互补。
Java 统一日志以后,可以这样配置 GC 日志:
-Xlog:gc*:file=/logs/gc.log:time,uptime,level,tags:filecount=10,filesize=100m
具体路径和大小按项目规范来。关键是不要等出问题时才发现没开 GC 日志。
看 GC 时,我会关注:
1.GC 频率。 2.单次停顿时间。 3.堆使用曲线。 4.老年代增长。 5.晋升失败或 Full GC。 6.分配速率。
如果升级后 GC 频率明显增加,要结合对象分配看原因。
压测要看分位数
运行时体检不能只看平均值。
JDK 升级后,最容易受影响的是尾部延迟。平均耗时可能差不多,但 P95、P99 变差,用户体验就会变差。
所以压测报告至少要看:
1.QPS。 2.平均响应时间。 3.P95。 4.P99。 5.错误率。 6.CPU。 7.内存。 8.GC 停顿。 9.下游资源。
如果 P99 抖动明显,就要看 JFR 里对应时间段是否有 GC、锁等待、下游慢调用或线程池排队。
启动阶段也要体检
很多服务升级 JDK 后,运行接口没问题,但启动慢了。
启动慢会影响发布、扩缩容和故障恢复。尤其是在 Kubernetes 环境里,启动时间和 readiness 检查关系很大。
JFR 可以录启动阶段,看看时间花在类加载、Bean 初始化、配置读取、数据库连接、缓存预热还是其他地方。
如果启动阶段做了很多远程调用或大数据加载,也应该重新评估。运行时升级时,正好顺手清理启动流程。
体检结果要留下来
做完 JFR 和 GC 分析,不要只在群里说一句“看起来可以”。
最好留下简单报告:
1.测试服务和版本。 2.JDK 版本。 3.JVM 参数。 4.测试场景。 5.关键指标对比。 6.发现的问题。 7.是否建议灰度。
这份报告不需要很长,但以后回头看很有价值。特别是多服务逐步升级时,它能帮助团队复用经验。
小结
Java 25 学习和评估不能只看编译和启动。运行时体检很重要。
JFR 能帮助我们看 CPU、分配、锁、线程和 IO,GC 日志能帮助我们看内存和停顿趋势。再结合压测分位数和业务指标,才能判断服务是否真的适合进入灰度。
升级 JDK 是一次很好的机会,把 JVM 观测和性能基线也一起补上。这样不管是 Java 25,还是以后新的 LTS,团队都会更从容。
If you enjoyed this, leave a comment~