JVM 性能诊断
JVM参数
JVM 参数数量极多(官方文档中可配置参数超过千个),且不同 JDK 版本(如 JDK8/JDK11/JDK17)的参数支持存在差异(部分参数被废弃、新增或重命名)
参数的分类
- 标准参数:以
-
开头,所有 JDK 版本通用,稳定且向后兼容(不会轻易废弃),主要用于基础配置。java -help - 非标准参数:以
-X
开头,特定 JDK 版本支持,可能在后续版本中调整,用于内存、GC 等核心配置。通过java -X
查看非标准参数 - 高级参数:以
-XX:
开头,用于细粒度控制 JVM 内部行为(如 GC 算法、编译阈值),通过java -XX:+PrintFlagsFinal
当前 JDK 版本支持的所有-XX:
开头的高级参数(包括默认值、是否可修改)。 - 设置系统属性:以-D开头,在 Java 代码中,通过
System.getProperty()
方法读取,非jvm参数
核心 JVM 参数
内存配置参数:
垃圾回收(GC)参数:指定GC、GC 调优参数
JIT 编译参数:
监控与诊断参数(问题排查):
常见问题
OOM(Out of Memory,内存溢出)
- 内存泄漏:程序申请的内存使用完毕后,无法被垃圾回收器(GC)回收,未关闭的资源句柄、静态集合类滥用、对象生命周期过长、匿名内部类 / 闭包引用:匿名内部类会隐式引用外部类对象。
- JVM内存配置过低:堆内存(Heap)栈内存(Stack)配置不足
- 高并发 / 峰值流量冲击
频繁 GC(垃圾回收)
GC 执行次数异常增多、单次 GC 耗时过长(或总耗时占比过高),最终可能导致应用响应延迟、吞吐量下降,甚至触发 OOM(内存溢出)。日志中出现 GC overhead limit exceeded
。
JDK 自带工具(轻量、无依赖)
jps
查看当前运行的 Java 进程 ID(PID)
jps -l
(显示进程 PID 和主类名)
jstat
实时监控 JVM 内存、GC 统计信息;
jstat -gcutil 12345 1000 10
(每 1 秒输出 1 次,共 10 次,12345 为 PID)
options
-class :类加载相关(-class
),监控类加载、卸载数量及耗时
-compiler:编译相关,查看 JIT 编译统计:编译成功/失败方法
-gc:堆内存各区域 GC 统计
-gccapacity :堆内存各区域容量变化
-gccause
-gcmetacapacity
-gcnew
-gcnewcapacity
-gcold
-gcoldcapacity
-gcutil :堆内存使用率统计
-printcompilation:查看当前被 JIT 编译的方法信息:
jmap
生成堆内存快照(dump 文件)、查看内存分布
jstack -l 12345 > thread.dump
(生成线程快照并保存到文件)
定位死锁、线程阻塞、热点方法
jinfo
查看 / 修改 JVM 运行时参数
jinfo -flags 12345
(查看 JVM 启动参数)jinfo -sysprops 12345
(查看系统属性)
确认 JVM 参数是否正确配置
MAT(Memory Analyzer Tool)
堆快照分析(内存泄漏、大对象、对象引用链)
自动检测内存泄漏点、生成分析报告
APM工具
(Application Performance Monitoring,应用性能监控)有全链路APM、前端APM、后端APM、基础设施APM 等。
全链路 APM
是目前最主流的类型,支持跨服务、跨层级的性能追踪(从用户请求发起→前端→API 网关→微服务→数据库→缓存→基础设施),核心是通过 “分布式追踪” 技术串联整个请求链路。
SkyWalking:1. 支持 Java、Go、Python 等多语言;2. 轻量级(Agent 低侵入);3. 内置链路追踪、指标分析、日志联动;4. 支持 K8s、云原生环境;适用场景:微服务架构、云原生应用、多语言混合项目
后端 APM 工具(聚焦服务 / 数据库性能)
Prometheus + Grafana:1. 开源组合,Prometheus 负责指标采集(时序数据),Grafana 负责可视化;2. 支持自定义指标、告警规则;3. 云原生生态核心工具;适用场景:基础设施监控、后端服务指标监控(如接口 QPS、延迟)
基础
匿名内部类
匿名内部类是定义在外部类的方法体、代码块或表达式中的局部内部类,没有显式的类名,其声明和实例化必须在外部类的内部完成。
匿名内部类可以直接访问外部类的所有成员(包括私有成员private
)
访问外部类的局部变量时,变量必须是final
或 “事实上的 final”(Java 8+)
非静态匿名内部类(大多数情况)依赖于外部类的实例,持有外部类实例的引用。如果外部类实例被销毁,匿名内部类实例也会失去有效的外部引用。
静态匿名内部类(极少使用,需定义在静态上下文中):不依赖外部类实例,只能访问外部类的静态成员
匿名内部类必须继承一个类或实现一个接口
// 匿名内部类继承父类
public class OuterClass {public void test() {Object obj = new Object() { // 继承Object类@Overridepublic String toString() {return "匿名内部类的toString()";}};System.out.println(obj.toString()); // 输出:匿名内部类的toString()}
}
JIT 的工作原理
JVM 启动时,首先通过解释器执行字节码(启动速度快)。同时,JVM 内置的热点探测器(HotSpot Detector)会监控代码执行情况,识别出两类 “热点代码”:
- 被多次调用的方法(方法调用次数达到阈值)。
- 被多次执行的循环体(循环执行次数达到阈值)。
当代码被判定为热点后,JIT 编译器会将其编译为本地机器码,并优化(如消除冗余计算、循环展开等),之后该代码的执行会直接使用编译后的机器码。
HotSpot 是最常用的 JVM 实现(如 Oracle JDK、OpenJDK),它内置了两种 JIT 编译器,C1(Client Compiler)和C2(Server Compiler),在 64 位 JDK 中,默认启用分层编译(Tiered Compilation):
- 先通过 C1 快速编译热点代码,保证程序尽快进入高效运行状态。
- 对执行频率极高的代码,再由 C2 进行深度优化,进一步提升性能。
优点
- 提升执行效率:热点代码编译为机器码后,执行速度可比解释执行快 10-100 倍。
- 动态优化:能根据运行时数据(如实际输入、分支执行频率)进行针对性优化,这是静态编译(如 C/C++)难以实现的。
- 平衡启动速度与运行性能:解释器保证快速启动,JIT 渐进式优化实现长期高效运行。
缺点
- 编译开销:JIT 编译过程会消耗 CPU 和内存资源,可能导致程序运行初期出现短暂卡顿(尤其是 C2 深度优化时)。
- 内存占用增加:编译后的机器码需要缓存,会占用额外内存。