JVM常用工具:jstat、jmap、jstack
内存溢出
小结
jps找出进程号,jmap转储heap文件,jhat分析堆文件。抑或可以jvisual等直接监控分析堆。
模拟代码
// 内存泄漏示例:静态集合持有对象,无法被 GC 回收
public class OutMemoryDemo {// 静态集合:生命周期与 JVM 一致,对象无法被回收private static final List<byte[]> LEAK_LIST = new ArrayList<>();public static void main(String[] args) throws InterruptedException {System.out.println("开始内存泄漏...");int count = 0;while (true) {// 每次分配 1MB 内存(byte[1024 * 1024])LEAK_LIST.add(new byte[1024 * 1024]);count++;// 每 100 次打印一次内存占用if (count % 100 == 0) {System.out.printf("已分配 %d 个对象,总占用约 %d MB%n",count, count * 1);}// 短暂休眠,减缓内存增长速度(方便观察)Thread.sleep(50);}}
}
转储堆文件
jmap -dump:live,format=b,file=F:/Temp/heap_dump.hprof 28204
分析堆文件
jhat F:/Temp/heap_dump.hprof
由于1.8的bin下有jhat:访问localhost:7000
PS:这里的class[B
指的是Java的基础变量byte,对应的C是char,这个大家自然不陌生可以推断出其他的变量。
垃圾回收
Jstat
jstat(Java Virtual Machine Statistics Monitoring Tool) 是 JDK 自带的命令行工具,用于监控 JVM 运行时状态,包括类加载、GC、堆内存、编译等统计信息。
在IDEAL中的VM OPTIONS中加入(Run Configure):
-Xms64m -Xmx64m -XX:+PrintGCDetails JstatDemo
测试代码
事实上,可以采用直接设置List对象进行反复加入和清空来模拟取代设置堆内存大小启动参数和GC日志打印
。
public class JstatDemo {public static void main(String[] args) throws InterruptedException {System.out.println("开始测试 jstat...");int count=0;// 无限循环分配小对象(触发 GC)while (true) {// 分配 1MB 内存(byte 数组)byte[] data = new byte[5 * 1024 * 1024];// byte[] data = new byte[100 * 1024];System.out.println("已经分配内存:" + ++count + "0MB");// System.out.println("已经分配内存:" + ++count + "00KB");// 短暂休眠(让 jstat 能捕捉到内存变化)Thread.sleep(2000);}}
}
分析GC
jstat [option] pid [interval] [count]
jstat -gcutil 26072 1000 3
为什么是Old区呢,因为对象太大,将分配对象修改为100KB
:
补充参数常用选项:
OPTIONS
选项 | 说明 |
---|---|
-class | 监控类加载/卸载数量及耗时(Loaded/Unloaded/Time)。 |
-compiler | 显示 JIT 编译器编译的方法数量及耗时。 |
-gc | 堆内存各区域容量(Capacity)及实际使用量(Usage),包含 GC 次数/时间。 |
-gccapacity | 堆内存各区域的容量(最小/最大/当前)。 |
-gcutil | 以百分比显示堆内存使用率,并输出 GC 统计。 |
-gccause | 同 -gcutil ,额外显示最近一次和当前 GC 的原因(如 Allocation Failure)。 |
-gcnew | 新生代(Young Gen)内存统计。 |
-gcold | 老年代(Old Gen)内存统计。 |
-gcmetacapacity | 元空间(Metaspace)容量统计(JDK8+)。 |
-gc
输出字段
字段 | 含义 | 示例 |
---|---|---|
S0C | Survivor 0 区容量(KB) | 10240.0 |
S1C | Survivor 1 区容量 | 10240.0 |
S0U | Survivor 0 区已使用量 | 0.0 |
S1U | Survivor 1 区已使用量 | 1024.0 |
EC | Eden 区容量 | 81920.0 |
EU | Eden 区已使用量 | 40960.0 |
OC | 老年代容量 | 204800.0 |
OU | 老年代已使用量 | 102400.0 |
YGC | Young GC 次数 | 100 |
YGCT | Young GC 总耗时(秒) | 5.456 |
FGC | Full GC 次数 | 3 |
FGCT | Full GC 总耗时 | 1.234 |
GCT | GC 总耗时 | 6.690 |
-gcutil
输出字段(百分比形式)
字段 | 含义 | 示例 |
---|---|---|
S0 | Survivor 0 区使用率 | 50.00 |
S1 | Survivor 1 区使用率 | 0.00 |
E | Eden 区使用率 | 75.25 |
O | 老年代使用率 | 85.30 |
M | 元空间(Metaspace)使用率 | 95.80 |
CCS | 压缩类空间(Compressed Class Space)使用率 | 80.50 |
YGC | Young GC 次数 | 200 |
YGCT | Young GC 总耗时 | 10.20 |
FGC | Full GC 次数 | 5 |
FGCT | Full GC 总耗时 | 3.50 |
GCT | 总 GC 耗时 | 13.70 |
Jinfo
jinfo对应进程PID:
jinfo [pid]
注意
:使用jdk指令的时候,往往要注意对应java程序的版本尽可能的保持一致,否则可能出现:
Error attaching to process: Windbg Error: GetModuleParameters failed!
我这里采用1.8的jinfo分析jdk17运行程序的进程pid出现如上报错
可视化监控Jconsole/Jvisualvm
一般可视化的集成了常用的指令(但是服务器和环境不一定会让我们使用或连接得上,工作多年的同志们可以理解),方便展示图形(相对于堆的64MB),修改分配内存为5MB:
死锁
这一块儿包括上面,其实21年就已经有记录,不过没有总结归纳(就像有很多存货和系列其实没发布整理),就不重新再跑和复现了(可视化对应的应该是jvisualvm):
jstack -heap pid