在Java应用的生产环境中,内存问题是最常见也是最棘手的故障之一。本文将系统性地介绍JVM内存问题的排查方法、常用命令和工具,帮助开发者快速定位并解决内存相关问题。碰到问题,首先要做的就是基于数据进行定位问题,比如:程序运行日志、异常堆栈信息、GC日志记录、线程快照文件、堆内存快照文件等。同时,数据的收集又离不开监控工具的辅助,所以当 JVM 在线上运行过程中出现问题后,自然避免不了使用一些 JDK 自带以及第三方提供的工具,如:jps、jstat、jstack、jmap、jhat、hprof、jinfo、arthas等。 ## 二、JVM内存结构回顾 在排查问题之前,先简要回顾JVM内存结构: - **堆内存(Heap)**:存放对象实例 - 年轻代(Young Generation):Eden + Survivor - 老年代(Old Generation) - **方法区(Method Area)**:存放类信息、常量、静态变量 - **程序计数器(PC Register)** - **虚拟机栈(VM Stack)** - **本地方法栈(Native Method Stack)** - **直接内存(Direct Memory)** ## 三、常用命令行工具 ### 3.1 jps - 查看Java进程 jps工具主要作用是用查看机器上运行的Java进程,类似于Linux系统的ps -aux | gerp java 命令。jps工具也支持查看其他机器Java进程,命令格式如下: ```bash jps [options] [hostid] ``` **常用参数:** - `-l`:显示主类的完整包名或jar完整路径 - `-v`:显示JVM启动参数 - `-m`:显示传递给main方法的参数 **示例:** ```bash jps -lv # 输出:12345 com.example.Application -Xmx2g -Xms2g ``` ### 3.2 jstat - 查看JVM统计信息 jstat全称“Java Virtual Machine statistics monitoring tool”,该工具可以利用JVM内建的指令对Java程序的资源以及性能进行实时的命令行监控,监控范围包含:对空间的各数据区、垃圾回收状况以及类的加在与卸载状态。 ```bash jstat [option] [interval] [count] [option]: 监控参数选项。 -t:在输出结果中加上Timestamp列,显示系统运行的时间。 -h:可以在周期性数据输出的时候,指定间隔多少行数据后输出一次表头。 vmid:virtual Machine Ip虚拟 ID,也就是指定一个要监控的 Java 进程 ID interva1:每次执行的间隔时间,默认单位为ms。 count:用于指定输出多少条数据,默认情况下会一直输出。 执行命令jstat -option后,可以看到存在很多选项,如下: -class:输出类加载classLoad相关的信息。 -compiler:显示与 JIT 即时编译相关的信息。 -gc:显示与 GC 相关的信息。 -gccapacity:显示每个分代空间的容量以及使用情况。 -gcmetacapacity:输出元数据空间相关的信息。 -gcnew:显示新生代空间相关的信息。 -gcnewcapacity:显示新生代空间的容量大小以及使用情况。 -gcold:输出年老代空间的信息。 -gcoldcapacity:输出年老代空间的容量大小以及使用情况。 -gcutil:显示垃圾回收信息。 ``` **常用选项:** | 选项 | 说明 | |------|------| | `-gc` | 显示GC相关的堆信息 | | `-gcutil` | 显示GC统计信息(百分比) | | `-gccause` | 显示GC原因 | | `-gcnew` | 显示新生代信息 | | `-gcold` | 显示老年代信息 | | `-class` | 显示类加载信息 | **示例:** ```bash # 每1000ms输出一次GC信息,共输出10次 jstat -gc 12345 1000 10 # 输出示例: # S0C S1C S0U S1U EC EU OC OU MC MU # 10240 10240 0 3072 81920 45000 163840 120000 51200 48000 ``` **重要字段解释:** - `S0C/S1C`:Survivor0/1区容量(KB) - `S0U/S1U`:Survivor0/1区使用量(KB) - `EC`:Eden区容量 - `EU`:Eden区使用量 - `OC`:老年代容量 - `OU`:老年代使用量 - `MC`:元空间容量 - `MU`:元空间使用量 - `YGC`:Young GC次数 - `YGCT`:Young GC耗时 - `FGC`:Full GC次数 - `FGCT`:Full GC耗时 ### 3.3 jmap - 内存映射工具 jmap是一个多功能的工具,主要是用于查看堆空间的使用情况,通常会配合jhat工具一起使用,可以用于生成Java堆的Dump文件。但除此之外,也可以查看finalize队列、元数据空间的详细信息,Java 堆中对象统计信息,如每个分区的使用率、当前装配的 GC 收集器等 ```bash jmap [option] [root@clearwind ~]# jmap -h Usage: jmap -clstats to connect to running process and print class loader statistics jmap -finalizerinfo to connect to running process and print information on objects awaiting finalization jmap -histo[:[]] to connect to running process and print histogram of java object heap jmap -dump: to connect to running process and dump java heap jmap -? -h --help to print this help message dump-options: live dump only live objects (takes precedence if both "live" and "all" are specified) all dump all objects in the heap (default if one of "live" or "all" is not specified) format=b binary format file= dump heap to gz= If specified, the heap dump is written in gzipped format using the given compression level. 1 (recommended) is the fastest, 9 the strongest compression. Example: jmap -dump:live,format=b,file=heap.bin histo-options: live count only live objects (takes precedence if both "live" and "all" are specified) all count all objects in the heap (default if one of "live" or "all" is not specified) file= dump data to parallel= Number of parallel threads to use for heap inspection: 0 (the default) means let the VM determine the number of threads to use 1 means use one thread (disable parallelism). For any other value the VM will try to use the specified number of threads, but might use fewer. Example: jmap -histo:live,file=/tmp/histo.data ``` **常用选项:** | 选项 | 说明 | |------|------| | `-heap` | 显示堆详细信息 | | `-histo[:live]` | 显示堆中对象统计信息 | | `-dump:` | 生成堆转储文件 | | `-finalizerinfo` | 显示在F-Queue中等待执行finalizer方法的对象 | **示例:** ```bash # 查看堆信息 jmap -heap 12345 # 查看存活对象统计(会触发Full GC) jmap -histo:live 12345 | head -20 # 生成堆转储文件 jmap -dump:live,format=b,file=/tmp/heap.hprof 12345 # 或使用更安全的方式(不会STW太久) jmap -dump:format=b,file=/tmp/heap.hprof 12345 ``` jmap是一个多功能的Java诊断工具,主要用于分析和监控Java应用程序的内存使用情况,特别是针对堆空间。它与jhat等工具协同工作,能够生成和分析堆转储文件,帮助开发者识别内存泄漏、监控对象分配以及优化内存管理。以下是jmap的关键特性和使用方法的总结: **主要功能** 生成堆转储:通过-dump选项,可以创建Java堆的快照,可以选择导出所有对象或仅导出存活对象,支持多种输出格式和压缩选项。 类加载器统计:使用-clstats选项,展示运行时进程中类加载器的统计信息,包括已加载的类数量等。 待终结对象信息:通过-finalizerinfo,列出等待终结(即将被垃圾回收)的对象,有助于理解对象生命周期管理。 堆内存对象统计:-histo选项提供堆中对象的统计直方图,包括对象数量和所占内存大小,可选参数包括统计存活对象或所有对象,并支持输出到文件。 **使用示例** 生成存活对象的堆转储:jmap -dump:live,format=b,file=Dump.hprof [pid] 查看类加载器统计:jmap -clstats [pid] 对象统计直方图:jmap -histo [pid] 或 jmap -histo:live [pid] 以区分统计存活对象或所有对象 jmap工具实际使用方式:jmap -clstats [pid]或jmap -dump:live,format=b,file=Dump.phrof [pid]等。 **堆快照导出命令解析:** live:导出堆中存活对象快照;format:指定输出格式;file:指定输出的文件名及其格式(.dat、.phrof等格式)。 性能影响:执行jmap,尤其是生成堆转储操作,可能会引起目标应用暂停,对性能产生显著影响,应避免在生产环境频繁使用。 Attach机制:大部分 JDK 提供的工具与 JVM 通信方式都是通过的Attach机制实现的,该机制可以针对目标 JVM 进程进行一些操作,比如获取内存Dump、线程Dump、类信息统计、动态加载Agent、动态设置 JVM 参数、打印 JVM 参数、获取系统属性等。有兴趣可以去深入研究一下,具体源码位置位于:com.sun.tools.attach包,里面存在一系列Attach机制相关的代码。 ### 3.4 jstack - 线程堆栈分析 ```bash jstack [option] ``` **常用参数:** - `-l`:显示锁的附加信息 - `-m`:显示native方法的堆栈 - `-F`:强制dump,当jstack无响应时使用 **示例:** ```bash # 导出线程堆栈 jstack -l 12345 > /tmp/thread.dump # 分析死锁 jstack 12345 | grep -A 10 "deadlock" ``` ### 3.5 jinfo - 查看和修改JVM参数 ```bash jinfo [option] ``` **常用选项:** - `-flags`:显示所有JVM参数 - `-flag `:显示指定参数值 - `-flag [+|-]`:启用或禁用某个参数 - `-flag =`:设置参数值 **示例:** ```bash # 查看所有参数 jinfo -flags 12345 # 查看最大堆内存 jinfo -flag MaxHeapSize 12345 # 动态开启GC日志详情 jinfo -flag +PrintGCDetails 12345 ``` ### 3.6 jcmd - 多功能诊断工具 ```bash jcmd ``` **常用命令:** ```bash # 查看可用命令 jcmd 12345 help # 查看JVM启动参数 jcmd 12345 VM.flags # 查看系统属性 jcmd 12345 VM.system_properties # 生成堆转储(推荐) jcmd 12345 GC.heap_dump /tmp/heap.hprof # 执行GC jcmd 12345 GC.run # 查看类统计 jcmd 12345 GC.class_histogram ``` ## 四、可视化分析工具 ### 4.1 JVisualVM **功能:** - 实时监控CPU、内存、线程 - 查看堆dump文件 - 生成和分析线程dump - 性能分析(Profiling) **使用方式:** ```bash # 直接启动(JDK自带) jvisualvm ``` **核心功能:** 1. **监视选项卡**:查看实时CPU、堆、类、线程数据 2. **线程选项卡**:分析线程状态、检测死锁 3. **抽样器/分析器**:CPU和内存性能分析 4. **堆Dump**:离线分析堆内存 ### 4.2 MAT (Memory Analyzer Tool) **Eclipse出品的堆分析神器** **核心功能:** - **Leak Suspects**:自动识别内存泄漏疑点 - **Histogram**:类实例统计 - **Dominator Tree**:支配树分析 - **OQL**:对象查询语言 **常用分析路径:** ``` 1. 打开heap.hprof文件 2. 查看Leak Suspects Report 3. 使用Histogram查看占用内存最多的类 4. 通过右键 -> List objects -> with incoming references 查看引用链 5. 使用Path to GC Roots 排除弱引用,找到强引用链 ``` **OQL示例:** ```sql -- 查找所有String对象 SELECT * FROM java.lang.String -- 查找大于1MB的对象 SELECT * FROM java.lang.Object WHERE @retainedHeapSize > 1048576 -- 查找特定包下的类实例 SELECT * FROM "com.example.MyClass" ``` ### 4.3 JProfiler **商业工具,功能强大** **特色功能:** - 实时内存监控 - CPU性能分析 - 线程分析和死锁检测 - 数据库连接分析 - 内存泄漏检测 ### 4.4 Arthas(阿里开源) **在线诊断神器,无需重启应用** **安装启动:** ```bash # 下载 wget https://arthas.aliyun.com/arthas-boot.jar # 启动 java -jar arthas-boot.jar ``` **常用命令:** ```bash # 查看JVM信息 dashboard # 查看内存详情 memory # 堆转储 heapdump /tmp/heap.hprof # 查看最繁忙的线程 thread -n 3 # 监控方法执行 watch com.example.Service method '{params, returnObj, throwExp}' # 反编译 jad com.example.MyClass # 查看类加载器 classloader ``` ## 五、内存问题排查流程 ### 5.1 发现问题 **告警信号:** - 收到`OutOfMemoryError`异常 - GC频繁,响应变慢 - 内存持续增长不回收 ### 5.2 快速定位 ```bash # Step 1: 找到进程ID jps -v # Step 2: 查看堆使用情况 jstat -gcutil 1000 # Step 3: 如果老年代持续增长 jmap -heap # Step 4: 查看对象分布 jmap -histo:live | head -30 ``` ### 5.3 深度分析 ```bash # 生成堆转储 jcmd GC.heap_dump /tmp/heap_$(date +%Y%m%d_%H%M%S).hprof # 使用MAT分析 # 1. 打开hprof文件 # 2. 查看Leak Suspects # 3. 分析Dominator Tree # 4. 找到GC Roots路径 ``` ### 5.4 线程问题排查 ```bash # 导出线程快照 jstack -l > thread_$(date +%Y%m%d_%H%M%S).txt # 找到CPU使用率最高的线程 top -H -p # 转换线程ID为16进制 printf "%x\n" # 在thread.txt中搜索对应的nid ``` ## 六、常见问题案例 ### 6.1 堆内存溢出 **现象:** `java.lang.OutOfMemoryError: Java heap space` **排查步骤:** ```bash # 1. 生成堆转储 jmap -dump:live,format=b,file=heap.hprof # 2. MAT分析 # - 查看Leak Suspects # - 分析大对象 # - 检查集合类是否持续增长 # 3. 常见原因 # - 缓存无界增长 # - 数据库结果集过大 # - 循环中创建大量对象 ``` ### 6.2 元空间溢出 **现象:** `java.lang.OutOfMemoryError: Metaspace` **排查步骤:** ```bash # 查看类加载情况 jstat -class # 可能原因 # - 动态代理类过多(CGLIB) # - JSP页面过多 # - 反射和动态类加载 # - Groovy等动态语言 # 解决方案 # -XX:MaxMetaspaceSize=512m # 检查类加载器泄漏 ``` ### 6.3 GC耗时过长 **排查步骤:** ```bash # 查看GC统计 jstat -gcutil 1000 # 分析GC日志(需添加JVM参数) # -XX:+PrintGCDetails # -XX:+PrintGCDateStamps # -Xloggc:/path/to/gc.log # 使用GCViewer或GCEasy分析gc.log ``` ## 七、JVM参数调优建议 ### 7.1 内存配置 ```bash # 堆内存 -Xms4g # 初始堆大小 -Xmx4g # 最大堆大小(建议与Xms相同) -Xmn2g # 年轻代大小 -XX:SurvivorRatio=8 # Eden:Survivor=8:1 # 元空间 -XX:MetaspaceSize=256m # 初始元空间 -XX:MaxMetaspaceSize=512m # 最大元空间 # 直接内存 -XX:MaxDirectMemorySize=1g # 最大堆外内存 ``` ### 7.2 GC选择 ```bash # G1 GC(推荐Java 8+) -XX:+UseG1GC -XX:MaxGCPauseMillis=200 # 最大GC暂停时间 -XX:G1HeapRegionSize=4m # Region大小 # ZGC(Java 11+,低延迟) -XX:+UseZGC -XX:ZCollectionInterval=120 # GC间隔 # CMS(已过时,不推荐) -XX:+UseConcMarkSweepGC ``` ### 7.3 GC日志 ```bash # Java 8 -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps -Xloggc:/path/to/gc-%t.log -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10 -XX:GCLogFileSize=100M # Java 9+ -Xlog:gc*:file=/path/to/gc.log:time,tags,level:filecount=10,filesize=100M ``` ### 7.4 OOM时自动Dump ```bash -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/path/to/dumps/ -XX:OnOutOfMemoryError="sh /path/to/alert.sh" ``` ## 八、最佳实践建议 1. **监控先行**:部署Prometheus + Grafana监控JVM指标 2. **日志完善**:配置详细的GC日志 3. **定期演练**:在测试环境模拟内存问题 4. **自动化**:OOM时自动dump和告警 5. **容量规划**:根据业务峰值合理分配内存 6. **代码审查**:关注集合使用、缓存管理、资源关闭 ## 九、总结 JVM内存问题排查是一项系统工程,需要: - 熟练掌握各种命令行工具 - 善用可视化分析工具 - 理解JVM内存模型和GC原理 - 建立完善的监控告警体系 希望本文能帮助您在遇到内存问题时快速定位和解决! --- **参考资料:** - [Oracle官方JVM文档](https://docs.oracle.com/javase/8/docs/technotes/tools/) - [Arthas使用手册](https://arthas.aliyun.com/doc/) - [Eclipse MAT文档](https://www.eclipse.org/mat/) Loading... 在Java应用的生产环境中,内存问题是最常见也是最棘手的故障之一。本文将系统性地介绍JVM内存问题的排查方法、常用命令和工具,帮助开发者快速定位并解决内存相关问题。碰到问题,首先要做的就是基于数据进行定位问题,比如:程序运行日志、异常堆栈信息、GC日志记录、线程快照文件、堆内存快照文件等。同时,数据的收集又离不开监控工具的辅助,所以当 JVM 在线上运行过程中出现问题后,自然避免不了使用一些 JDK 自带以及第三方提供的工具,如:jps、jstat、jstack、jmap、jhat、hprof、jinfo、arthas等。 ## 二、JVM内存结构回顾 在排查问题之前,先简要回顾JVM内存结构: - **堆内存(Heap)**:存放对象实例 - 年轻代(Young Generation):Eden + Survivor - 老年代(Old Generation) - **方法区(Method Area)**:存放类信息、常量、静态变量 - **程序计数器(PC Register)** - **虚拟机栈(VM Stack)** - **本地方法栈(Native Method Stack)** - **直接内存(Direct Memory)** ## 三、常用命令行工具 ### 3.1 jps - 查看Java进程 jps工具主要作用是用查看机器上运行的Java进程,类似于Linux系统的ps -aux | gerp java 命令。jps工具也支持查看其他机器Java进程,命令格式如下: ```bash jps [options] [hostid] ``` **常用参数:** - `-l`:显示主类的完整包名或jar完整路径 - `-v`:显示JVM启动参数 - `-m`:显示传递给main方法的参数 **示例:** ```bash jps -lv # 输出:12345 com.example.Application -Xmx2g -Xms2g ``` ### 3.2 jstat - 查看JVM统计信息 jstat全称“Java Virtual Machine statistics monitoring tool”,该工具可以利用JVM内建的指令对Java程序的资源以及性能进行实时的命令行监控,监控范围包含:对空间的各数据区、垃圾回收状况以及类的加在与卸载状态。 ```bash jstat [option] <pid> [interval] [count] [option]: 监控参数选项。 -t:在输出结果中加上Timestamp列,显示系统运行的时间。 -h:可以在周期性数据输出的时候,指定间隔多少行数据后输出一次表头。 vmid:virtual Machine Ip虚拟 ID,也就是指定一个要监控的 Java 进程 ID interva1:每次执行的间隔时间,默认单位为ms。 count:用于指定输出多少条数据,默认情况下会一直输出。 执行命令jstat -option后,可以看到存在很多选项,如下: -class:输出类加载classLoad相关的信息。 -compiler:显示与 JIT 即时编译相关的信息。 -gc:显示与 GC 相关的信息。 -gccapacity:显示每个分代空间的容量以及使用情况。 -gcmetacapacity:输出元数据空间相关的信息。 -gcnew:显示新生代空间相关的信息。 -gcnewcapacity:显示新生代空间的容量大小以及使用情况。 -gcold:输出年老代空间的信息。 -gcoldcapacity:输出年老代空间的容量大小以及使用情况。 -gcutil:显示垃圾回收信息。 ``` **常用选项:** | 选项 | 说明 | |------|------| | `-gc` | 显示GC相关的堆信息 | | `-gcutil` | 显示GC统计信息(百分比) | | `-gccause` | 显示GC原因 | | `-gcnew` | 显示新生代信息 | | `-gcold` | 显示老年代信息 | | `-class` | 显示类加载信息 | **示例:** ```bash # 每1000ms输出一次GC信息,共输出10次 jstat -gc 12345 1000 10 # 输出示例: # S0C S1C S0U S1U EC EU OC OU MC MU # 10240 10240 0 3072 81920 45000 163840 120000 51200 48000 ``` **重要字段解释:** - `S0C/S1C`:Survivor0/1区容量(KB) - `S0U/S1U`:Survivor0/1区使用量(KB) - `EC`:Eden区容量 - `EU`:Eden区使用量 - `OC`:老年代容量 - `OU`:老年代使用量 - `MC`:元空间容量 - `MU`:元空间使用量 - `YGC`:Young GC次数 - `YGCT`:Young GC耗时 - `FGC`:Full GC次数 - `FGCT`:Full GC耗时 ### 3.3 jmap - 内存映射工具 jmap是一个多功能的工具,主要是用于查看堆空间的使用情况,通常会配合jhat工具一起使用,可以用于生成Java堆的Dump文件。但除此之外,也可以查看finalize队列、元数据空间的详细信息,Java 堆中对象统计信息,如每个分区的使用率、当前装配的 GC 收集器等 ```bash jmap [option] <pid> [root@clearwind ~]# jmap -h Usage: jmap -clstats <pid> to connect to running process and print class loader statistics jmap -finalizerinfo <pid> to connect to running process and print information on objects awaiting finalization jmap -histo[:[<histo-options>]] <pid> to connect to running process and print histogram of java object heap jmap -dump:<dump-options> <pid> to connect to running process and dump java heap jmap -? -h --help to print this help message dump-options: live dump only live objects (takes precedence if both "live" and "all" are specified) all dump all objects in the heap (default if one of "live" or "all" is not specified) format=b binary format file=<file> dump heap to <file> gz=<number> If specified, the heap dump is written in gzipped format using the given compression level. 1 (recommended) is the fastest, 9 the strongest compression. Example: jmap -dump:live,format=b,file=heap.bin <pid> histo-options: live count only live objects (takes precedence if both "live" and "all" are specified) all count all objects in the heap (default if one of "live" or "all" is not specified) file=<file> dump data to <file> parallel=<number> Number of parallel threads to use for heap inspection: 0 (the default) means let the VM determine the number of threads to use 1 means use one thread (disable parallelism). For any other value the VM will try to use the specified number of threads, but might use fewer. Example: jmap -histo:live,file=/tmp/histo.data <pid> ``` **常用选项:** | 选项 | 说明 | |------|------| | `-heap` | 显示堆详细信息 | | `-histo[:live]` | 显示堆中对象统计信息 | | `-dump:<dump-options>` | 生成堆转储文件 | | `-finalizerinfo` | 显示在F-Queue中等待执行finalizer方法的对象 | **示例:** ```bash # 查看堆信息 jmap -heap 12345 # 查看存活对象统计(会触发Full GC) jmap -histo:live 12345 | head -20 # 生成堆转储文件 jmap -dump:live,format=b,file=/tmp/heap.hprof 12345 # 或使用更安全的方式(不会STW太久) jmap -dump:format=b,file=/tmp/heap.hprof 12345 ``` jmap是一个多功能的Java诊断工具,主要用于分析和监控Java应用程序的内存使用情况,特别是针对堆空间。它与jhat等工具协同工作,能够生成和分析堆转储文件,帮助开发者识别内存泄漏、监控对象分配以及优化内存管理。以下是jmap的关键特性和使用方法的总结: **主要功能** 生成堆转储:通过-dump选项,可以创建Java堆的快照,可以选择导出所有对象或仅导出存活对象,支持多种输出格式和压缩选项。 类加载器统计:使用-clstats选项,展示运行时进程中类加载器的统计信息,包括已加载的类数量等。 待终结对象信息:通过-finalizerinfo,列出等待终结(即将被垃圾回收)的对象,有助于理解对象生命周期管理。 堆内存对象统计:-histo选项提供堆中对象的统计直方图,包括对象数量和所占内存大小,可选参数包括统计存活对象或所有对象,并支持输出到文件。 **使用示例** 生成存活对象的堆转储:jmap -dump:live,format=b,file=Dump.hprof [pid] 查看类加载器统计:jmap -clstats [pid] 对象统计直方图:jmap -histo [pid] 或 jmap -histo:live [pid] 以区分统计存活对象或所有对象 jmap工具实际使用方式:jmap -clstats [pid]或jmap -dump:live,format=b,file=Dump.phrof [pid]等。 **堆快照导出命令解析:** live:导出堆中存活对象快照;format:指定输出格式;file:指定输出的文件名及其格式(.dat、.phrof等格式)。 性能影响:执行jmap,尤其是生成堆转储操作,可能会引起目标应用暂停,对性能产生显著影响,应避免在生产环境频繁使用。 Attach机制:大部分 JDK 提供的工具与 JVM 通信方式都是通过的Attach机制实现的,该机制可以针对目标 JVM 进程进行一些操作,比如获取内存Dump、线程Dump、类信息统计、动态加载Agent、动态设置 JVM 参数、打印 JVM 参数、获取系统属性等。有兴趣可以去深入研究一下,具体源码位置位于:com.sun.tools.attach包,里面存在一系列Attach机制相关的代码。 ### 3.4 jstack - 线程堆栈分析 ```bash jstack [option] <pid> ``` **常用参数:** - `-l`:显示锁的附加信息 - `-m`:显示native方法的堆栈 - `-F`:强制dump,当jstack无响应时使用 **示例:** ```bash # 导出线程堆栈 jstack -l 12345 > /tmp/thread.dump # 分析死锁 jstack 12345 | grep -A 10 "deadlock" ``` ### 3.5 jinfo - 查看和修改JVM参数 ```bash jinfo [option] <pid> ``` **常用选项:** - `-flags`:显示所有JVM参数 - `-flag <name>`:显示指定参数值 - `-flag [+|-]<name>`:启用或禁用某个参数 - `-flag <name>=<value>`:设置参数值 **示例:** ```bash # 查看所有参数 jinfo -flags 12345 # 查看最大堆内存 jinfo -flag MaxHeapSize 12345 # 动态开启GC日志详情 jinfo -flag +PrintGCDetails 12345 ``` ### 3.6 jcmd - 多功能诊断工具 ```bash jcmd <pid> <command> ``` **常用命令:** ```bash # 查看可用命令 jcmd 12345 help # 查看JVM启动参数 jcmd 12345 VM.flags # 查看系统属性 jcmd 12345 VM.system_properties # 生成堆转储(推荐) jcmd 12345 GC.heap_dump /tmp/heap.hprof # 执行GC jcmd 12345 GC.run # 查看类统计 jcmd 12345 GC.class_histogram ``` ## 四、可视化分析工具 ### 4.1 JVisualVM **功能:** - 实时监控CPU、内存、线程 - 查看堆dump文件 - 生成和分析线程dump - 性能分析(Profiling) **使用方式:** ```bash # 直接启动(JDK自带) jvisualvm ``` **核心功能:** 1. **监视选项卡**:查看实时CPU、堆、类、线程数据 2. **线程选项卡**:分析线程状态、检测死锁 3. **抽样器/分析器**:CPU和内存性能分析 4. **堆Dump**:离线分析堆内存 ### 4.2 MAT (Memory Analyzer Tool) **Eclipse出品的堆分析神器** **核心功能:** - **Leak Suspects**:自动识别内存泄漏疑点 - **Histogram**:类实例统计 - **Dominator Tree**:支配树分析 - **OQL**:对象查询语言 **常用分析路径:** ``` 1. 打开heap.hprof文件 2. 查看Leak Suspects Report 3. 使用Histogram查看占用内存最多的类 4. 通过右键 -> List objects -> with incoming references 查看引用链 5. 使用Path to GC Roots 排除弱引用,找到强引用链 ``` **OQL示例:** ```sql -- 查找所有String对象 SELECT * FROM java.lang.String -- 查找大于1MB的对象 SELECT * FROM java.lang.Object WHERE @retainedHeapSize > 1048576 -- 查找特定包下的类实例 SELECT * FROM "com.example.MyClass" ``` ### 4.3 JProfiler **商业工具,功能强大** **特色功能:** - 实时内存监控 - CPU性能分析 - 线程分析和死锁检测 - 数据库连接分析 - 内存泄漏检测 ### 4.4 Arthas(阿里开源) **在线诊断神器,无需重启应用** **安装启动:** ```bash # 下载 wget https://arthas.aliyun.com/arthas-boot.jar # 启动 java -jar arthas-boot.jar ``` **常用命令:** ```bash # 查看JVM信息 dashboard # 查看内存详情 memory # 堆转储 heapdump /tmp/heap.hprof # 查看最繁忙的线程 thread -n 3 # 监控方法执行 watch com.example.Service method '{params, returnObj, throwExp}' # 反编译 jad com.example.MyClass # 查看类加载器 classloader ``` ## 五、内存问题排查流程 ### 5.1 发现问题 **告警信号:** - 收到`OutOfMemoryError`异常 - GC频繁,响应变慢 - 内存持续增长不回收 ### 5.2 快速定位 ```bash # Step 1: 找到进程ID jps -v # Step 2: 查看堆使用情况 jstat -gcutil <pid> 1000 # Step 3: 如果老年代持续增长 jmap -heap <pid> # Step 4: 查看对象分布 jmap -histo:live <pid> | head -30 ``` ### 5.3 深度分析 ```bash # 生成堆转储 jcmd <pid> GC.heap_dump /tmp/heap_$(date +%Y%m%d_%H%M%S).hprof # 使用MAT分析 # 1. 打开hprof文件 # 2. 查看Leak Suspects # 3. 分析Dominator Tree # 4. 找到GC Roots路径 ``` ### 5.4 线程问题排查 ```bash # 导出线程快照 jstack -l <pid> > thread_$(date +%Y%m%d_%H%M%S).txt # 找到CPU使用率最高的线程 top -H -p <pid> # 转换线程ID为16进制 printf "%x\n" <thread_id> # 在thread.txt中搜索对应的nid ``` ## 六、常见问题案例 ### 6.1 堆内存溢出 **现象:** `java.lang.OutOfMemoryError: Java heap space` **排查步骤:** ```bash # 1. 生成堆转储 jmap -dump:live,format=b,file=heap.hprof <pid> # 2. MAT分析 # - 查看Leak Suspects # - 分析大对象 # - 检查集合类是否持续增长 # 3. 常见原因 # - 缓存无界增长 # - 数据库结果集过大 # - 循环中创建大量对象 ``` ### 6.2 元空间溢出 **现象:** `java.lang.OutOfMemoryError: Metaspace` **排查步骤:** ```bash # 查看类加载情况 jstat -class <pid> # 可能原因 # - 动态代理类过多(CGLIB) # - JSP页面过多 # - 反射和动态类加载 # - Groovy等动态语言 # 解决方案 # -XX:MaxMetaspaceSize=512m # 检查类加载器泄漏 ``` ### 6.3 GC耗时过长 **排查步骤:** ```bash # 查看GC统计 jstat -gcutil <pid> 1000 # 分析GC日志(需添加JVM参数) # -XX:+PrintGCDetails # -XX:+PrintGCDateStamps # -Xloggc:/path/to/gc.log # 使用GCViewer或GCEasy分析gc.log ``` ## 七、JVM参数调优建议 ### 7.1 内存配置 ```bash # 堆内存 -Xms4g # 初始堆大小 -Xmx4g # 最大堆大小(建议与Xms相同) -Xmn2g # 年轻代大小 -XX:SurvivorRatio=8 # Eden:Survivor=8:1 # 元空间 -XX:MetaspaceSize=256m # 初始元空间 -XX:MaxMetaspaceSize=512m # 最大元空间 # 直接内存 -XX:MaxDirectMemorySize=1g # 最大堆外内存 ``` ### 7.2 GC选择 ```bash # G1 GC(推荐Java 8+) -XX:+UseG1GC -XX:MaxGCPauseMillis=200 # 最大GC暂停时间 -XX:G1HeapRegionSize=4m # Region大小 # ZGC(Java 11+,低延迟) -XX:+UseZGC -XX:ZCollectionInterval=120 # GC间隔 # CMS(已过时,不推荐) -XX:+UseConcMarkSweepGC ``` ### 7.3 GC日志 ```bash # Java 8 -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps -Xloggc:/path/to/gc-%t.log -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10 -XX:GCLogFileSize=100M # Java 9+ -Xlog:gc*:file=/path/to/gc.log:time,tags,level:filecount=10,filesize=100M ``` ### 7.4 OOM时自动Dump ```bash -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/path/to/dumps/ -XX:OnOutOfMemoryError="sh /path/to/alert.sh" ``` ## 八、最佳实践建议 1. **监控先行**:部署Prometheus + Grafana监控JVM指标 2. **日志完善**:配置详细的GC日志 3. **定期演练**:在测试环境模拟内存问题 4. **自动化**:OOM时自动dump和告警 5. **容量规划**:根据业务峰值合理分配内存 6. **代码审查**:关注集合使用、缓存管理、资源关闭 ## 九、总结 JVM内存问题排查是一项系统工程,需要: - 熟练掌握各种命令行工具 - 善用可视化分析工具 - 理解JVM内存模型和GC原理 - 建立完善的监控告警体系 希望本文能帮助您在遇到内存问题时快速定位和解决! --- **参考资料:** - [Oracle官方JVM文档](https://docs.oracle.com/javase/8/docs/technotes/tools/) - [Arthas使用手册](https://arthas.aliyun.com/doc/) - [Eclipse MAT文档](https://www.eclipse.org/mat/) 最后修改:2025 年 12 月 20 日 © 允许规范转载 赞 如果觉得我的文章对你有用,请随意赞赏