深入理解JVM系列(第二十一章):JMap命令在JVM调优中的实战应用
1.Jmap命令
Jmap命令是为了将Java进程当前的内存转存为内存快照,常常用来分析内存溢出或者内存泄漏等问题,执行Jmap命令保存内存快照可以用作后续分析
- 分析此刻Java堆及方法区的详细信息
- 分析内存空间使用率
- 分析当前使用的收集器信息
2.Jamp命令使用
执行Jmap 看下有哪些命令可以使用
PS C:\Users\jzj> jmap -options
where <option> is one of:
<none> to print same info as Solaris pmap
-heap to print java heap summary
-histo[:live] to print histogram of java object heap; if the "live"
suboption is specified, only count live objects
-clstats to print class loader statistics
-finalizerinfo to print information on objects awaiting finalization
-dump:<dump-options> to dump java heap in hprof binary format
dump-options:
live dump only live objects; if not specified,
all objects in the heap are dumped.
format=b binary format
file=<file> dump heap to <file>
Example: jmap -dump:live,format=b,file=heap.bin <pid>
-F force. Use with -dump:<dump-options> <pid> or -histo
to force a heap dump or histogram when <pid> does not
respond. The "live" suboption is not supported
in this mode.
-h | -help to print this help message
-J<flag> to pass <flag> directly to the runtime system
3.启动程序
下面我们启动Springboot程序,实战以下打印Jamp命令
#JVM参数 启动jar包
java -XX:+UseG1GC -Xms100M -Xmx100M -XX:+PrintGC -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError -XX:SurvivorRatio=8 -jar .\demo-0.0.1-SNAPSHOT.jar
只有一个简单的Controller
package com.jzj.jvmtest.font;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@Slf4j
public class TestController {
@GetMapping("/ping")
private String ping() {
return "pong";
}
@GetMapping("/test")
private String test(Integer num) {
try {
byte[] b = null;
for (int i = 1; i <= num; i++) {
//设置 3M的对象
log.info("======== " + i + "次添加3M对象");
b = new byte[2 * 1024 * 1024];
Thread.sleep(3000);
}
} catch (Exception e) {
log.error(e.getMessage(), e);
}
return "success";
}
}
4.jmap实战
4.1 jmap -heap 打印Java堆栈信息
使用 jmap -dump 打印Java堆栈信息,必会
- 打印垃圾回收算法及相信信息
- 打印堆配置信息
- 打印堆空间使用信息,包括分代情况,,每个区域的容量信息、当区域已使用内存、可使用内存等等
- 分代信息中报刊细分的空间的内存使用信息比如S0/S1/Eden等
下面我们实际操作下看下打印信息, 首先先找到进程信息 jps,当前进程pid 9104
# 执行jps,找到进程信息
PS C:\Users\jzj> jps
9104 demo-0.0.1-SNAPSHOT.jar
13996
6476 Jps
执行Jmap -heap 9104
PS C:\Users\jzj> jmap -heap 9104
Attaching to process ID 9104, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.181-b13
using thread-local object allocation.
Garbage-First (G1) GC with 10 thread(s)
Heap Configuration:
MinHeapFreeRatio = 40
MaxHeapFreeRatio = 70
MaxHeapSize = 104857600 (100.0MB)
NewSize = 1363144 (1.2999954223632812MB)
MaxNewSize = 62914560 (60.0MB)
OldSize = 5452592 (5.1999969482421875MB)
NewRatio = 2
SurvivorRatio = 8
MetaspaceSize = 21807104 (20.796875MB)
CompressedClassSpaceSize = 1073741824 (1024.0MB)
MaxMetaspaceSize = 17592186044415 MB
G1HeapRegionSize = 1048576 (1.0MB)
Heap Usage:
G1 Heap:
regions = 100
capacity = 104857600 (100.0MB)
used = 44633600 (42.56591796875MB)
free = 60224000 (57.43408203125MB)
42.56591796875% used
G1 Young Generation:
Eden Space:
regions = 26
capacity = 57671680 (55.0MB)
used = 27262976 (26.0MB)
free = 30408704 (29.0MB)
47.27272727272727% used
Survivor Space:
regions = 8
capacity = 8388608 (8.0MB)
used = 8388608 (8.0MB)
free = 0 (0.0MB)
100.0% used
G1 Old Generation:
regions = 9
capacity = 38797312 (37.0MB)
used = 8982016 (8.56591796875MB)
free = 29815296 (28.43408203125MB)
23.15112964527027% used
12508 interned Strings occupying 1091120 bytes.
对打印信息分析
堆信息如下
- Garbage-First (G1) GC with 10 thread(s) 使用的是G1垃圾收集器
- MaxHeapSize = 104857600 (100.0MB) 最大堆容量100M,设置的是-Xms
- NewSize = 1363144 (1.2999954223632812MB) 新生代大小1M
- MaxNewSize = 62914560 (60.0MB) 最大新生代大小60M
- OldSize = 5452592 (5.1999969482421875MB) 老年代大小5M
- NewRatio = 2 新生代:老年代 1:2,也就是新生代1,老年代2,新生代占总比例 1/3
- SurvivorRatio = 8 S0:S1:Eden 1:1:8, 新生代Eden区占 8/10
- MetaspaceSize = 21807104 (20.796875MB) 元空间大小 20M
幸存区信息
- Survivor Space:regions = 8 Survivor分配了8个Region
- capacity = 8388608 (8.0MB) Survivor容量一共8M
- used = 8388608 (8.0MB) 已经使用了 8M
- free = 0 (0.0MB) 空闲的0M
- 100.0% used 使用率 100%
老年代信息
- G1 Old Generation: regions = 9 Old区分配了9个Region capacity = 38797312 (37.0MB) Old老年代容量一共37M used = 8982016 (8.56591796875MB) 已经使用 8.5M free = 29815296 (28.43408203125MB) 空闲 28.4M 23.15112964527027% used 老年代使用率 23%
4.2 jmap-dump[:live,]format=b,file=xxx 转存堆快照
生成此刻Java堆的快照信息,转存为dump文件,非常重要,必会
参数详细说明如下
- live 参数,可选参数,如果添加live,表明只转存堆中的存活对象,没指定live表示转存堆中所有对象
- format=b 表示以hprof 二进制的格式转存Java堆内存
- file=xx指定dump文件的文件名
- -F 强制模式,有可能此刻进程已经假死,强制模式
执行 jmap -dump:format=b,file=xxx111.bin 9104
#执行 dump文件二进制文件
PS C:\Users\jzj> jmap -dump:format=b,file=xxx111.bin 9104
Dumping heap to C:\Users\jzj\xxx111.bin ...
Heap dump file created
xxx111.bin文件已经被创建成功
执行dump存活对象 jmap -dump:live,format=b,file=xxx111.bin 9104
PS C:\Users\jzj> jmap -dump:live,format=b,file=xxx222.bin 9104
Dumping heap to C:\Users\jzj\xxx222.bin ...
Heap dump file created
xxx222.bin文件已经被创建成功
执行dump hprof文件jmap -dump:live,format=b,file=dump333.hprof 9104
hprof文件就是堆快照,我们后面要使用工具Jprofiler分析dump文件就是分析该文件
PS C:\Users\jzj> jmap -dump:live,format=b,file=dump333.hprof 9104
Dumping heap to C:\Users\jzj\dump333.hprof ...
Heap dump file created
dump333.hprof 文件被创建成功
强制执行 jmap -F -dump:live,format=b,file=dump444.hprof 9104
PS C:\Users\jzj> jmap -F -dump:live,format=b,file=dump444.hprof 9104
Attaching to process ID 9104, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.181-b13
Dumping heap to dump444.hprof ...
Heap dump file created
dump444.hprof 文件被创建成功
4.3 jmap -histo[:live] 显示Java堆统计信息
jmap -histo 显示堆统计信息,包括以下信息
- 进程中对象的数量
- 进程占用的内存大小
- 进程中加载的类信息
- 指定live参数指统计活动的对象
执行Jmap -histo 9104
PS C:\Users\jzj> jmap -histo 9104
num #instances #bytes class name
----------------------------------------------
1: 103267 18015376 [C
2: 8414 5769720 [I
3: 10004 5404056 [B
4: 76092 1826208 java.lang.String
5: 17108 1127392 [Ljava.lang.Object;
6: 9203 809864 java.lang.reflect.Method
7: 6191 683608 java.lang.Class
8: 19336 618752 java.util.concurrent.ConcurrentHashMap$Node
9: 6532 523200 [Ljava.util.WeakHashMap$Entry;
10: 8019 513216 java.net.URL
11: 24754 511760 [Ljava.lang.Class;
12: 6478 414592 org.springframework.boot.loader.jar.JarFileWrapper
13: 3887 330840 [Ljava.util.HashMap$Node;
14: 7991 319640 java.util.LinkedHashMap$Entry
15: 6529 313392 java.util.WeakHashMap
16: 7932 253824 java.util.concurrent.locks.AbstractQueuedSynchronizer$Node
17: 7767 248544 java.util.HashMap$Node
18: 7201 230432 java.lang.ref.ReferenceQueue
19: 129 208976 [Ljava.util.concurrent.ConcurrentHashMap$Node;
20: 6515 208480 java.util.zip.ZipCoder
等等等
执行 jmap -histo:live 9104 统计存活的对象信息
PS C:\Users\jzj> jmap -histo:live 9104
num #instances #bytes class name
----------------------------------------------
1: 30300 3043144 [C
2: 30008 720192 java.lang.String
3: 6191 683608 java.lang.Class
4: 18319 586208 java.util.concurrent.ConcurrentHashMap$Node
5: 6205 546040 java.lang.reflect.Method
6: 6501 367576 [Ljava.lang.Object;
7: 2628 319792 [B
8: 3117 288232 [I
9: 6449 257960 java.util.LinkedHashMap$Entry
10: 2902 250488 [Ljava.util.HashMap$Node;
11: 12433 198928 java.lang.Object
12: 6134 196288 java.util.HashMap$Node
13: 110 193312 [Ljava.util.concurrent.ConcurrentHashMap$Node;
14: 7775 175400 [Ljava.lang.Class;
15: 2997 167832 java.util.LinkedHashMap
4.4 jmap -clstats 统计类jia'cai
显示Java堆 元空间的类Class的统计信息,对于每个类加载器而言,它的名称、活跃度、地址、父类加载器信息,以及它所加载的类的数量和大小,这些信息全都会打印
主要类信息如下:
- class_loader:当Java虚拟机运行时,类加载器对象的地址
- classes:已加载类的数量信息
- bytes:该类加载器加载的所有类的元数据占用的字节数
- parent_loader:父类加载器对象的地址,如果没有显示null
- alive:类是否存活的标识,表示类加载器对象是否将要被垃圾回收,分为dead/live
- type:该类加载器的类名。
执行 jmap -clstats 9104
PS C:\Users\jzj> jmap -clstats 9104
Attaching to process ID 9104, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.181-b13
finding class loader instances ..done.
computing per loader stat ..done.
please wait.. computing liveness..............................................................................liveness analysis may be inaccurate ...
class_loader classes bytes parent_loader alive? type
<bootstrap> 1732 3015415 null live <internal>
0x00000000fa3cbdb8 1 880 0x00000000f9c0d1b0 dead sun/reflect/DelegatingClassLoader@0x000000010000a028
0x00000000fa3cd438 1 880 0x00000000f9c0d1b0 dead sun/reflect/DelegatingClassLoader@0x000000010000a028
0x00000000f9c0dd48 4 4551 null live sun/misc/Launcher$ExtClassLoader@0x000000010000fc78
0x00000000fa3c5330 1 1472 0x00000000f9c0d1b0 dead sun/reflect/DelegatingClassLoader@0x000000010000a028
0x00000000fa3cc3b0 1 1471 0x00000000f9c0d1b0 dead sun/reflect/DelegatingClassLoader@0x000000010000a028
0x00000000fa3cf9b0 1 1472 null dead sun/reflect/DelegatingClassLoader@0x000000010000a028
0x00000000fa3cbc28 1 880 0x00000000f9c0d1b0 dead sun/reflect/DelegatingClassLoader@0x000000010000a028
0x00000000fa3cd2a8 1 880 0x00000000f9c0d1b0 dead sun/reflect/DelegatingClassLoader@0x000000010000a028
0x00000000fa3c5120 1 1472 0x00000000f9c0d1b0 dead sun/reflect/DelegatingClassLoader@0x000000010000a028
0x00000000fa3cc090 1 880 0x00000000f9c0d1b0 dead sun/reflect/DelegatingClassLoader@0x000000010000a028
0x00000000fa3c5c08 1 1472 0x00000000f9c0d1b0 dead sun/reflect/DelegatingClassLoader@0x000000010000a028
0x00000000fa3cb908 1 1471 0x00000000f9c0d1b0 dead sun/reflect/DelegatingClassLoader@0x000000010000a028
0x00000000fa3cc608 1 880 0x00000000f9c0d1b0 dead sun/reflect/DelegatingClassLoader@0x000000010000a028
0x00000000fa3cbb60 1 880 0x00000000f9c0d1b0 dead sun/reflect/DelegatingClassLoader@0x000000010000a028
0x00000000fa3cd160 1 880 0x00000000f9c0d1b0 dead sun/reflect/DelegatingClassLoader@0x000000010000a028
0x00000000f9f1ad90 1 1472 null dead sun/reflect/DelegatingClassLoader@0x000000010000a028
0x00000000fa3c4ed8 1 1472 0x00000000f9c0d1b0 dead sun/reflect/DelegatingClassLoader@0x000000010000a028
0x00000000fa3cc158 1 1474 0x00000000f9c0d1b0 dead sun/reflect/DelegatingClassLoader@0x000000010000a028
0x00000000fa3c5dd0 1 880 0x00000000f9c0d1b0 dead sun/reflect/DelegatingClassLoader@0x000000010000a028
0x00000000fa3cb9d0 1 880 0x00000000f9c0d1b0 dead sun/reflect/DelegatingClassLoader@0x000000010000a028
0x00000000fa3ccf50 1 1471 0x00000000f9c0d1b0 dead sun/reflect/DelegatingClassLoader@0x000000010000a028
0x00000000f9c0d1b0 3176 5094748 0x00000000f9c0e888 live org/springframework/boot/loader/LaunchedURLClassLoader@0x0000000100060828
0x00000000fa53fea0 1 880 0x00000000f9c0d1b0 dead sun/reflect/DelegatingClassLoader@0x000000010000a028
0x00000000fa3c4d48 1 1472 null dead sun/reflect/DelegatingClassLoader@0x000000010000a028
0x00000000fa3cb240 1 1472 0x00000000f9c0d1b0 dead sun/reflect/DelegatingClassLoader@0x000000010000a028
0x00000000fa3cc540 1 880 0x00000000f9c0d1b0 dead sun/reflect/DelegatingClassLoader@0x000000010000a028
0x00000000fa3cfb40 1 1472 0x00000000f9c0d1b0 dead sun/reflect/DelegatingClassLoader@0x000000010000a028
total = 51 5001 8243960 N/A alive=6, dead=45 N/A
至此 ,我们已经学习了Jmap的命令,比较常用的就是 jmap-heap堆栈信息及 jmap-dump保存堆栈快照,一定要会用这两个,特别是 jmap-dump 这个命令是分析内存 dump文件的必经之路
下一篇: 如何运用jmap进行内存溢出检测与剖析
推荐阅读
-
深入理解JVM系列(第二十一章):JMap命令在JVM调优中的实战应用
-
玩转Java底层:JMX详解 - jconsole与自定义MBean监控工具的实际应用与区别" 在日常JVM调优中,我们熟知的jconsole工具通过JMX包装的bean以图形化形式展示管理数据,而像jstat和jmap这类内建监控工具则由JVM直接支持。本文将以jconsole为例,深入讲解其实质——基于JMX的MBean功能,包括可视化界面上的bean属性查看和操作调用。 MBeans在jconsole中的体现是那些可观察的组件属性和方法,如上图所示,通过名为"Verbose"的属性能看到其值为false,同时还能直接操作该bean的方法,例如"closeJerryMBean"。 尽管jconsole给我们提供了直观的可视化界面,但请注意,这里的MBean并非固定不变,开发者可根据JMX提供的接口将自己的自定义bean展示到jconsole。以下步骤展示了如何创建并注册一个名为"StudyJavaMBean"的自定义MBean: 1. 首先定义接口`StudyJavaMBean`,接口需遵循MBean规范,即后缀为"MBean"且包含getter方法代表属性,如`getApplicationName`,和无返回值的setter方法代表操作,如`closeJerryMBean`。 ```java public interface StudyJavaMBean { String getApplicationName(); void closeJerryMBean(); } ``` 2. 编写接口的实现类`StudyJavaMBeanImpl`,实现接口中的方法: ```java public class StudyJavaMBeanImpl implements StudyJavaMBean { @Override public String getApplicationName() { return "每天学Java"; } @Override public void closeJerryMBean() { System.out.println("关闭Jerry应用"); } } ``` 3. 在代码中注册自定义MBean,涉及的关键步骤包括: - 获取平台MBeanServer - 定义ObjectName,指定唯一的MBean标识符 - 注册MBean到服务器 - 启动RMI连接器服务,以便jconsole能够访问 ```java public void registerMBean() throws Exception { // ... 具体实现省略 ... } ``` 实际运行注册后的MBean,您将在jconsole中发现并查看自定义bean的属性和调用相关方法。然而,这种方式相较于传统的属性/日志查看和HTTP接口,实用性相对有限,可能存在潜在的安全风险。但不可否认的是,JMX及其MBean机制对于获取操作系统信息、内存状态等关键性能指标仍然具有重要价值。例如: 1. **获取操作系统信息**:通过JMX MBean,可以直接获取到诸如CPU使用率、操作系统版本等系统级信息,这对于资源管理和优化工作具有显著帮助。