|
|
马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
x
引言
在当今的软件开发环境中,Java应用程序因其稳定性和跨平台特性被广泛应用于企业级系统。然而,随着系统复杂度的增加,Java进程的监控和故障排查变得越来越重要。无论是内存泄漏、线程死锁,还是性能瓶颈,都需要我们掌握有效的工具和方法来快速定位和解决问题。本文将全面介绍Java进程检查的各种命令和工具,帮助你建立完整的Java应用监控体系,提升问题排查效率,确保应用稳定运行。
Java进程检查基础命令
jps - Java虚拟机进程状态工具
jps是最基础的Java进程检查工具,它可以列出当前系统中所有的Java进程及其ID。这个工具位于JDK的bin目录下,是排查Java进程问题的第一步。
基本用法:
输出示例:
- 12345 MyApplication
- 67890 Jps
复制代码
常用参数:
• -l:显示完整的主类名或jar包名
• -v:显示传递给JVM的参数
• -m:显示传递给main方法的参数
输出示例:
- 12345 /opt/app/MyApplication.jar -Xmx2g -Xms1g -Dspring.profiles.active=prod
- 67890 sun.tools.jps.Jps -Dapplication.home=/usr/lib/jvm/java-11-openjdk-amd64
复制代码
实际应用场景:当系统中有多个Java进程运行时,使用jps可以快速定位到需要监控的进程ID,为后续的深入分析做准备。
jstat - Java虚拟机统计监控工具
jstat是一个强大的命令行工具,用于监控Java虚拟机的各种运行状态数据,包括类加载、内存、垃圾收集、JIT编译等数据。
基本语法:
- jstat -<option> <pid> [<interval> [<count>]]
复制代码
常用选项:
• -class:显示类加载信息
• -gc:显示垃圾收集堆信息
• -gccapacity:显示堆内存容量及使用情况
• -gcutil:显示垃圾收集统计信息
• -gccause:显示垃圾收集统计信息(包括最近一次GC的原因)
• -compiler:显示JIT编译信息
示例1:监控GC情况
输出示例:
- S0C S1C ... GCT LGCT CGC FGC
- 512.0 512.0 ... 12.345 10.123 5 2
复制代码
示例2:查看GC统计摘要
输出示例:
- S0 S1 E O M CCS YGC YGCT FGC FGCT GCT
- 0.00 25.00 45.12 78.34 95.67 87.45 1234 45.678 5 6.789 52.467
复制代码
实际应用场景:通过jstat可以实时监控Java应用的内存使用情况和GC行为,帮助发现内存泄漏、GC频繁等问题。例如,如果发现老年代(O)使用率持续增长且Full GC(FGC)次数频繁,可能存在内存泄漏问题。
jinfo - Java配置信息工具
jinfo用于查看和调整Java虚拟机的各种参数,这对于了解JVM的运行配置和动态调整参数非常有用。
基本用法:
常用选项:
• -flags:显示JVM参数
• -sysprops:显示系统属性
• -flag <name>:显示指定参数的值
• -flag [+|-]<name>:启用或禁用指定参数
示例1:查看所有JVM参数
输出示例:
- Attaching to process ID 12345, please wait...
- Debugger attached successfully.
- Server compiler detected.
- JVM version is 11.0.11+9-Ubuntu-0ubuntu2.20.04
- Non-default VM flags: -XX:CICompilerCount=3 -XX:InitialHeapSize=1073741824 -XX:MaxHeapSize=2147483648 -XX:MaxNewSize=715653120 -XX:MinHeapDeltaBytes=524288 -XX:NewSize=357826560 -XX:OldSize=715653120 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseFastAccessorMethods -XX:+UseParallelGC
- Command line: -Xmx2g -Xms1g -Dspring.profiles.active=prod
复制代码
示例2:查看特定参数
- jinfo -flag MaxHeapSize 12345
复制代码
输出示例:
- -XX:MaxHeapSize=2147483648
复制代码
示例3:动态修改参数
- jinfo -flag +PrintGC 12345
复制代码
实际应用场景:jinfo在排查问题时非常有用,特别是当需要确认JVM启动参数或系统属性时。例如,当怀疑内存设置不合理时,可以使用jinfo查看实际的堆大小设置;或者需要动态开启GC日志以便进一步分析时,可以使用jinfo修改相关参数。
jmap - Java内存映射工具
jmap用于生成Java堆的内存快照(heap dump)或查看内存使用情况,是分析内存问题的重要工具。
常用选项:
• -heap:显示Java堆详细信息
• -histo:显示堆中对象的统计信息
• -dump:<options>:生成堆转储快照
• -finalizerinfo:显示等待终结的对象信息
示例1:查看堆内存使用情况
输出示例:
- Attaching to process ID 12345, please wait...
- Debugger attached successfully.
- Server compiler detected.
- JVM version is 11.0.11+9-Ubuntu-0ubuntu2.20.04
- using thread-local object allocation.
- Parallel GC with 4 thread(s)
- Heap Configuration:
- MinHeapFreeRatio = 40
- MaxHeapFreeRatio = 70
- MaxHeapSize = 2147483648 (2048.0MB)
- NewSize = 357826560 (341.25MB)
- MaxNewSize = 715653120 (682.5MB)
- OldSize = 715653120 (682.5MB)
- NewRatio = 2
- SurvivorRatio = 8
- MetaspaceSize = 21807104 (20.796875MB)
- CompressedClassSpaceSize = 1073741824 (1024.0MB)
- MaxMetaspaceSize = 17592186044415 MB
- G1HeapRegionSize = 0 (0.0MB)
- Heap Usage:
- PS Young Generation
- Eden Space:
- capacity = 268435456 (256.0MB)
- used = 134217728 (128.0MB)
- free = 134217728 (128.0MB)
- 50.0% used
- From Space:
- capacity = 44564480 (42.5MB)
- used = 0 (0.0MB)
- free = 44564480 (42.5MB)
- 0.0% used
- To Space:
- capacity = 44564480 (42.5MB)
- used = 0 (0.0MB)
- free = 44564480 (42.5MB)
- 0.0% used
- PS Old Generation
- capacity = 715653120 (682.5MB)
- used = 536870912 (512.0MB)
- free = 178782208 (170.5MB)
- 75.0% used
复制代码
示例2:查看对象统计信息
- jmap -histo 12345 | head -20
复制代码
输出示例:
- num #instances #bytes class name
- ----------------------------------------------
- 1: 123456 45678901 [C
- 2: 98765 34567890 java.lang.String
- 3: 54321 23456789 java.util.HashMap$Node
- 4: 43210 12345678 java.lang.Class
- 5: 32109 9876543 [Ljava.lang.Object;
- 6: 21098 8765432 java.util.concurrent.ConcurrentHashMap$Node
- 7: 10987 7654321 java.util.ArrayList
- 8: 9876 6543210 java.util.HashMap
- 9: 8765 5432109 [B
- 10: 7654 4321098 java.util.LinkedList$Node
复制代码
示例3:生成堆转储快照
- jmap -dump:format=b,file=heapdump.hprof 12345
复制代码
实际应用场景:jmap是分析内存问题的利器。当应用出现内存泄漏或OutOfMemoryError时,可以使用jmap生成堆转储文件,然后使用MAT等工具分析。通过-histo选项可以快速查看哪些对象占用了大量内存,帮助定位内存泄漏的源头。
jstack - Java堆栈跟踪工具
jstack用于生成Java线程的堆栈跟踪信息,对于分析线程问题(如死锁、CPU过高)非常有用。
基本用法:
常用选项:
• -l:显示关于锁的附加信息
• -m:显示Java和本地C/C++框架的混合堆栈
• -F:强制生成堆栈信息(当jstack不起作用时使用)
示例1:查看线程堆栈
- jstack -l 12345 > thread_dump.txt
复制代码
输出示例:
- 2023-05-20 14:30:45
- Full thread dump Java HotSpot(TM) 64-Bit Server VM (11.0.11+9-Ubuntu-0ubuntu2.20.04 mixed mode, sharing):
- "Attach Listener" #13 daemon prio=9 os_prio=0 cpu=0.00ms elapsed=1234.56s tid=0x00007f1234567890 nid=0x1234 waiting on condition [0x0000000000000000]
- java.lang.Thread.State: RUNNABLE
- "C1 CompilerThread3" #12 daemon prio=9 os_prio=0 cpu=123.45ms elapsed=1234.56s tid=0x00007f1234567890 nid=0x1235 waiting on condition [0x0000000000000000]
- java.lang.Thread.State: RUNNABLE
- "C2 CompilerThread2" #11 daemon prio=9 os_prio=0 cpu=234.56ms elapsed=1234.56s tid=0x00007f1234567890 nid=0x1236 waiting on condition [0x0000000000000000]
- java.lang.Thread.State: RUNNABLE
- ...
- "http-nio-8080-exec-1" #25 daemon prio=5 os_prio=0 cpu=345.67ms elapsed=123.45s tid=0x00007f1234567890 nid=0x123a waiting on condition [0x00007f1234567000]
- java.lang.Thread.State: WAITING (parking)
- at jdk.internal.misc.Unsafe.park(java.base@11.0.11/Native Method)
- - parking to wait for <0x0000000765432100> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
- at java.util.concurrent.locks.LockSupport.park(java.base@11.0.11/LockSupport.java:194)
- at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(java.base@11.0.11/AbstractQueuedSynchronizer.java:2081)
- at java.util.concurrent.LinkedBlockingQueue.take(java.base@11.0.11/LinkedBlockingQueue.java:433)
- at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:105)
- at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:32)
- at java.util.concurrent.ThreadPoolExecutor.getTask(java.base@11.0.11/ThreadPoolExecutor.java:1054)
- at java.util.concurrent.ThreadPoolExecutor.runWorker(java.base@11.0.11/ThreadPoolExecutor.java:1114)
- at java.util.concurrent.ThreadPoolExecutor$Worker.run(java.base@11.0.11/ThreadPoolExecutor.java:628)
- at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
- at java.lang.Thread.run(java.base@11.0.11/Thread.java:834)
- Locked ownable synchronizers:
- - <0x0000000765432100> (a java.util.concurrent.ThreadPoolExecutor$Worker)
- ...
复制代码
示例2:检测死锁
- jstack -l 12345 | grep -A 10 "Found one Java-level deadlock"
复制代码
输出示例:
- Found one Java-level deadlock:
- =============================
- "Thread-1":
- waiting to lock monitor 0x00007f1234567890 (object 0x0000000765432100, a java.lang.Object),
- which is held by "Thread-2"
- "Thread-2":
- waiting to lock monitor 0x00007f1234567890 (object 0x0000000765432101, a java.lang.Object),
- which is held by "Thread-1"
- Java stack information for the threads listed above:
- ===================================================
- "Thread-1":
- at com.example.DeadlockExample$Thread1.run(DeadlockExample.java:23)
- - waiting to lock <0x0000000765432100> (a java.lang.Object)
- - locked <0x0000000765432101> (a java.lang.Object)
- at java.lang.Thread.run(Thread.java:834)
- "Thread-2":
- at com.example.DeadlockExample$Thread2.run(DeadlockExample.java:39)
- - waiting to lock <0x0000000765432101> (a java.lang.Object)
- - locked <0x0000000765432100> (a java.lang.Object)
- at java.lang.Thread.run(Thread.java:834)
复制代码
实际应用场景:jstack是分析线程问题的关键工具。当应用出现CPU使用率过高、响应缓慢或无响应时,可以使用jstack生成线程堆栈,分析哪些线程占用了大量CPU或处于阻塞状态。特别是当怀疑存在死锁时,jstack可以明确显示死锁的线程和锁对象,帮助快速定位和解决问题。
Java进程检查高级工具
JConsole - Java监控与管理控制台
JConsole是JDK自带的一个图形化监控工具,可以实时监控Java应用程序的内存使用、线程状态、类加载情况等,并提供了一些基本的管理功能。
启动JConsole:
或者直接连接到指定进程:
主要功能:
1. 概述:显示Java虚拟机的基本信息,包括堆内存使用、线程数、类加载数和CPU使用率。
2. 内存:显示内存使用情况,包括堆内存和非堆内存的使用图表,可以查看各个内存池的使用情况。
3. 线程:显示线程数量和线程状态,可以检测死锁。
4. 类:显示类加载数量。
5. VM摘要:显示虚拟机的各种参数和摘要信息。
6. MBeans:浏览和管理应用程序的MBeans。
概述:显示Java虚拟机的基本信息,包括堆内存使用、线程数、类加载数和CPU使用率。
内存:显示内存使用情况,包括堆内存和非堆内存的使用图表,可以查看各个内存池的使用情况。
线程:显示线程数量和线程状态,可以检测死锁。
类:显示类加载数量。
VM摘要:显示虚拟机的各种参数和摘要信息。
MBeans:浏览和管理应用程序的MBeans。
实际应用场景:
JConsole适合用于实时监控Java应用程序的运行状态。例如,当应用出现内存问题时,可以通过内存标签页观察堆内存的使用趋势,判断是否存在内存泄漏;当应用响应缓慢时,可以通过线程标签页查看线程状态,分析是否存在线程阻塞或死锁。
示例:使用JConsole监控内存
1. 启动JConsole并连接到目标Java进程
2. 切换到”内存”标签页
3. 选择”堆内存”图表
4. 观察内存使用趋势,如果发现内存持续增长且不释放,可能存在内存泄漏
5. 可以点击”执行GC”按钮手动触发垃圾收集,观察内存是否正常释放
VisualVM - 多合一故障排除工具
VisualVM是一个功能更强大的图形化工具,它整合了多个JDK命令行工具的功能,提供了更丰富的分析和可视化能力。
启动VisualVM:
主要功能:
1. 概述:显示应用程序的基本信息,包括进程ID、主类、JVM参数和系统属性。
2. 监控:实时监控CPU、堆内存、类加载和线程活动。
3. 线程:显示线程活动和状态,支持线程Dump分析。
4. 抽样器:对CPU和内存进行性能分析,找出热点方法。
5. Visual GC:通过插件可视化垃圾收集过程(需要安装Visual GC插件)。
概述:显示应用程序的基本信息,包括进程ID、主类、JVM参数和系统属性。
监控:实时监控CPU、堆内存、类加载和线程活动。
线程:显示线程活动和状态,支持线程Dump分析。
抽样器:对CPU和内存进行性能分析,找出热点方法。
Visual GC:通过插件可视化垃圾收集过程(需要安装Visual GC插件)。
安装插件:
1. 启动VisualVM
2. 从菜单栏选择”工具” > “插件”
3. 在”可用插件”标签页中选择需要的插件(如Visual GC)
4. 点击”安装”按钮并按照提示完成安装
实际应用场景:
VisualVM适合用于深入分析Java应用程序的性能问题。例如,当应用CPU使用率过高时,可以使用抽样器功能找出消耗CPU最多的方法;当应用出现内存问题时,可以使用堆转储功能分析内存占用情况。
示例:使用VisualVM分析CPU性能问题
1. 启动VisualVM并连接到目标Java进程
2. 切换到”抽样器”标签页
3. 点击”CPU”按钮开始CPU抽样
4. 让应用运行一段时间,收集足够的数据
5. 停止抽样,查看热点方法列表
6. 分析消耗CPU最多的方法,优化相关代码
Java Mission Control - 高级监控和管理工具
Java Mission Control(JMC)是JDK自带的一个高级监控和管理工具,特别适合用于生产环境的低开销监控和问题分析。
启动JMC:
主要功能:
1. JVM浏览器:显示本地和远程的Java进程。
2. 飞行记录器(Flight Recorder):收集详细的运行时信息,用于事后分析。
3. JMX控制台:浏览和管理MBeans。
4. 自动分析结果:自动分析飞行记录器数据并提供问题诊断。
JVM浏览器:显示本地和远程的Java进程。
飞行记录器(Flight Recorder):收集详细的运行时信息,用于事后分析。
JMX控制台:浏览和管理MBeans。
自动分析结果:自动分析飞行记录器数据并提供问题诊断。
实际应用场景:
JMC特别适合用于生产环境的长期监控和问题分析。通过飞行记录器,可以收集详细的运行时数据,而不会对应用性能造成显著影响。当应用出现间歇性问题时,可以使用飞行记录器记录数据,然后通过JMC的分析功能找出问题根源。
示例:使用JMC分析内存问题
1. 启动JMC并连接到目标Java进程
2. 右键点击进程,选择”启动飞行记录”
3. 配置记录参数(如记录时间、内存事件等)
4. 点击”完成”开始记录
5. 等待记录完成或手动停止记录
6. 查看自动分析结果,关注内存相关的警告和建议
7. 浏览内存标签页,分析对象分配和垃圾收集情况
MAT - 内存分析工具
Eclipse Memory Analyzer Tool(MAT)是一个强大的Java堆转储分析工具,可以帮助找出内存泄漏和减少内存消耗。
启动MAT:
通常需要单独下载和安装MAT,然后通过以下方式启动:
主要功能:
1. 内存泄漏检测:自动检测可能的内存泄漏。
2. 支配树(Dominator Tree):显示对象之间的引用关系,帮助找出占用内存最多的对象。
3. 直方图:显示各类对象的实例数量和内存占用。
4. 重复类:查找由类加载器泄漏导致的重复类。
5. 线程概览:分析线程和相关对象的内存占用。
内存泄漏检测:自动检测可能的内存泄漏。
支配树(Dominator Tree):显示对象之间的引用关系,帮助找出占用内存最多的对象。
直方图:显示各类对象的实例数量和内存占用。
重复类:查找由类加载器泄漏导致的重复类。
线程概览:分析线程和相关对象的内存占用。
实际应用场景:
MAT是分析内存泄漏问题的利器。当应用出现OutOfMemoryError或内存使用过高时,可以使用jmap生成堆转储文件,然后用MAT分析找出内存泄漏的源头。
示例:使用MAT分析内存泄漏
1. 使用jmap生成堆转储文件:jmap -dump:format=b,file=heapdump.hprof <pid>
2. 启动MAT并打开堆转储文件
3. 运行内存泄漏自动检测:点击工具栏上的”泄漏嫌疑报告”按钮查看报告中的问题概述和详细信息
4. 点击工具栏上的”泄漏嫌疑报告”按钮
5. 查看报告中的问题概述和详细信息
6. 分析支配树:打开支配树视图按保留堆(Retained Heap)排序分析占用内存最多的对象及其引用链
7. 打开支配树视图
8. 按保留堆(Retained Heap)排序
9. 分析占用内存最多的对象及其引用链
10. 查看对象到GC根的路径:在直方图或支配树中右键点击可疑对象选择”Path to GC Roots”分析为什么这些对象没有被垃圾收集
11. 在直方图或支配树中右键点击可疑对象
12. 选择”Path to GC Roots”
13. 分析为什么这些对象没有被垃圾收集
使用jmap生成堆转储文件:
- jmap -dump:format=b,file=heapdump.hprof <pid>
复制代码
启动MAT并打开堆转储文件
运行内存泄漏自动检测:
• 点击工具栏上的”泄漏嫌疑报告”按钮
• 查看报告中的问题概述和详细信息
分析支配树:
• 打开支配树视图
• 按保留堆(Retained Heap)排序
• 分析占用内存最多的对象及其引用链
查看对象到GC根的路径:
• 在直方图或支配树中右键点击可疑对象
• 选择”Path to GC Roots”
• 分析为什么这些对象没有被垃圾收集
实际问题解决案例
案例一:内存泄漏排查
问题描述:一个Java Web应用运行一段时间后,响应变慢,最终出现OutOfMemoryError。
排查步骤:
1. 使用jps确认进程ID:jps -l输出:12345 /opt/app/webapp.jar
2. 使用jstat监控内存使用情况:jstat -gcutil 12345 5s观察到老年代(O)使用率持续增长,Full GC(FGC)次数频繁增加,但内存释放不明显,表明可能存在内存泄漏。
3. 使用jmap生成堆转储文件:jmap -dump:format=b,file=heapdump.hprof 12345
4. 使用MAT分析堆转储文件:打开heapdump.hprof运行泄漏嫌疑报告发现大量com.example.UserSession对象未被释放
5. 打开heapdump.hprof
6. 运行泄漏嫌疑报告
7. 发现大量com.example.UserSession对象未被释放
8. 分析代码:检查UserSession类的使用,发现用户会话在注销后没有被正确从全局会话管理器中移除。
9. - 修复问题:“`java
- // 修复前
- public void logout(String sessionId) {
- // 只从本地缓存移除,未从全局管理器移除
- localCache.remove(sessionId);
- }
复制代码
使用jps确认进程ID:
输出:
- 12345 /opt/app/webapp.jar
复制代码
使用jstat监控内存使用情况:
观察到老年代(O)使用率持续增长,Full GC(FGC)次数频繁增加,但内存释放不明显,表明可能存在内存泄漏。
使用jmap生成堆转储文件:
- jmap -dump:format=b,file=heapdump.hprof 12345
复制代码
使用MAT分析堆转储文件:
• 打开heapdump.hprof
• 运行泄漏嫌疑报告
• 发现大量com.example.UserSession对象未被释放
分析代码:检查UserSession类的使用,发现用户会话在注销后没有被正确从全局会话管理器中移除。
修复问题:“`java
// 修复前
public void logout(String sessionId) {
// 只从本地缓存移除,未从全局管理器移除
localCache.remove(sessionId);
}
// 修复后
public void logout(String sessionId) {
- // 从本地缓存和全局管理器都移除
- localCache.remove(sessionId);
- globalSessionManager.remove(sessionId);
复制代码
}
- 7. **验证修复:**
- 重新部署应用后,使用jstat监控内存使用情况,确认老年代使用率保持稳定,Full GC频率恢复正常。
- ### 案例二:CPU使用率过高问题排查
- **问题描述:**
- 一个Java应用在运行一段时间后,CPU使用率持续接近100%,导致应用响应缓慢。
- **排查步骤:**
- 1. **使用jps确认进程ID:**
- ```bash
- jps -l
复制代码
输出:
- 12345 /opt/app/data-processor.jar
复制代码
1. 使用top确认CPU使用情况:top -p 12345确认进程12345的CPU使用率确实接近100%。
2. 使用jstack生成线程堆栈:jstack -l 12345 > thread_dump.txt
3. - 分析线程堆栈:查看thread_dump.txt,发现大量线程处于RUNNABLE状态,执行相同的计算密集型方法:"pool-1-thread-10" #29 prio=5 os_prio=0 cpu=12345.67ms elapsed=234.56s tid=0x00007f1234567890 nid=0x123a runnable [0x00007f1234567000]
- java.lang.Thread.State: RUNNABLE
- at com.example.data.processor.Calculator.calculate(Calculator.java:45)
- at com.example.data.processor.DataProcessor.process(DataProcessor.java:123)
- at com.example.data.processor.DataProcessor$$Lambda$123/0x0000000800123456.run(Unknown Source)
- at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
- at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
- at java.lang.Thread.run(Thread.java:834)
复制代码 4. 使用VisualVM进行CPU抽样:启动VisualVM并连接到进程12345切换到”抽样器”标签页点击”CPU”按钮开始抽样运行一段时间后停止抽样查看热点方法,确认Calculator.calculate()方法消耗了最多的CPU时间
5. 启动VisualVM并连接到进程12345
6. 切换到”抽样器”标签页
7. 点击”CPU”按钮开始抽样
8. 运行一段时间后停止抽样
9. 查看热点方法,确认Calculator.calculate()方法消耗了最多的CPU时间
10. - 分析代码:检查Calculator.calculate()方法,发现存在一个低效的循环计算:// 问题代码
- public double calculate(Data data) {
- double result = 0;
- // 低效的嵌套循环
- for (int i = 0; i < data.size(); i++) {
- for (int j = 0; j < data.size(); j++) {
- result += Math.sqrt(data.get(i) * data.get(j));
- }
- }
- return result;
- }
复制代码 11. - 优化代码:// 优化后
- public double calculate(Data data) {
- double result = 0;
- // 预计算平方根,减少重复计算
- double[] sqrtValues = new double[data.size()];
- for (int i = 0; i < data.size(); i++) {
- sqrtValues[i] = Math.sqrt(data.get(i));
- }
- // 优化循环
- for (int i = 0; i < data.size(); i++) {
- double sqrtI = sqrtValues[i];
- for (int j = 0; j < data.size(); j++) {
- result += sqrtI * sqrtValues[j];
- }
- }
- return result;
- }
复制代码 12. 验证修复:重新部署应用后,使用top监控CPU使用情况,确认CPU使用率恢复正常水平。
使用top确认CPU使用情况:
确认进程12345的CPU使用率确实接近100%。
使用jstack生成线程堆栈:
- jstack -l 12345 > thread_dump.txt
复制代码
分析线程堆栈:查看thread_dump.txt,发现大量线程处于RUNNABLE状态,执行相同的计算密集型方法:
- "pool-1-thread-10" #29 prio=5 os_prio=0 cpu=12345.67ms elapsed=234.56s tid=0x00007f1234567890 nid=0x123a runnable [0x00007f1234567000]
- java.lang.Thread.State: RUNNABLE
- at com.example.data.processor.Calculator.calculate(Calculator.java:45)
- at com.example.data.processor.DataProcessor.process(DataProcessor.java:123)
- at com.example.data.processor.DataProcessor$$Lambda$123/0x0000000800123456.run(Unknown Source)
- at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
- at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
- at java.lang.Thread.run(Thread.java:834)
复制代码
使用VisualVM进行CPU抽样:
• 启动VisualVM并连接到进程12345
• 切换到”抽样器”标签页
• 点击”CPU”按钮开始抽样
• 运行一段时间后停止抽样
• 查看热点方法,确认Calculator.calculate()方法消耗了最多的CPU时间
分析代码:检查Calculator.calculate()方法,发现存在一个低效的循环计算:
- // 问题代码
- public double calculate(Data data) {
- double result = 0;
- // 低效的嵌套循环
- for (int i = 0; i < data.size(); i++) {
- for (int j = 0; j < data.size(); j++) {
- result += Math.sqrt(data.get(i) * data.get(j));
- }
- }
- return result;
- }
复制代码
优化代码:
- // 优化后
- public double calculate(Data data) {
- double result = 0;
- // 预计算平方根,减少重复计算
- double[] sqrtValues = new double[data.size()];
- for (int i = 0; i < data.size(); i++) {
- sqrtValues[i] = Math.sqrt(data.get(i));
- }
- // 优化循环
- for (int i = 0; i < data.size(); i++) {
- double sqrtI = sqrtValues[i];
- for (int j = 0; j < data.size(); j++) {
- result += sqrtI * sqrtValues[j];
- }
- }
- return result;
- }
复制代码
验证修复:重新部署应用后,使用top监控CPU使用情况,确认CPU使用率恢复正常水平。
案例三:线程死锁问题排查
问题描述:一个Java应用偶尔会停止响应,但不会崩溃,重启后恢复正常。
排查步骤:
1. 使用jps确认进程ID:jps -l输出:12345 /opt/app/order-system.jar
2. 使用jstack生成线程堆栈:jstack -l 12345 > thread_dump.txt
3. - 分析线程堆栈中的死锁信息:在thread_dump.txt中搜索”deadlock”或”Found one Java-level deadlock”:Found one Java-level deadlock:
- =============================
- "OrderProcessor-Thread-1":
- waiting to lock monitor 0x00007f1234567890 (object 0x0000000765432100, a java.lang.Object),
- which is held by "InventoryProcessor-Thread-2"
- "InventoryProcessor-Thread-2":
- waiting to lock monitor 0x00007f1234567890 (object 0x0000000765432101, a java.lang.Object),
- which is held by "OrderProcessor-Thread-1"
复制代码 4. - 查看死锁线程的详细堆栈:"OrderProcessor-Thread-1":
- at com.example.order.OrderProcessor.processOrder(OrderProcessor.java:56)
- - waiting to lock <0x0000000765432100> (a java.lang.Object)
- - locked <0x0000000765432101> (a java.lang.Object)
- at java.lang.Thread.run(Thread.java:834)
- "InventoryProcessor-Thread-2":
- at com.example.inventory.InventoryProcessor.updateInventory(InventoryProcessor.java:78)
- - waiting to lock <0x0000000765432101> (a java.lang.Object)
- - locked <0x0000000765432100> (a java.lang.Object)
- at java.lang.Thread.run(Thread.java:834)
复制代码 5. - 分析代码:检查OrderProcessor.processOrder()和InventoryProcessor.updateInventory()方法,发现它们以不同的顺序获取锁:
- “`java
- // OrderProcessor.java
- public void processOrder(Order order) {
- synchronized (inventoryLock) { // 先获取inventoryLock// 处理订单...
- synchronized (orderLock) { // 再获取orderLock
- // 更新订单状态...
- }}
- }
复制代码
使用jps确认进程ID:
输出:
- 12345 /opt/app/order-system.jar
复制代码
使用jstack生成线程堆栈:
- jstack -l 12345 > thread_dump.txt
复制代码
分析线程堆栈中的死锁信息:在thread_dump.txt中搜索”deadlock”或”Found one Java-level deadlock”:
- Found one Java-level deadlock:
- =============================
- "OrderProcessor-Thread-1":
- waiting to lock monitor 0x00007f1234567890 (object 0x0000000765432100, a java.lang.Object),
- which is held by "InventoryProcessor-Thread-2"
- "InventoryProcessor-Thread-2":
- waiting to lock monitor 0x00007f1234567890 (object 0x0000000765432101, a java.lang.Object),
- which is held by "OrderProcessor-Thread-1"
复制代码
查看死锁线程的详细堆栈:
- "OrderProcessor-Thread-1":
- at com.example.order.OrderProcessor.processOrder(OrderProcessor.java:56)
- - waiting to lock <0x0000000765432100> (a java.lang.Object)
- - locked <0x0000000765432101> (a java.lang.Object)
- at java.lang.Thread.run(Thread.java:834)
- "InventoryProcessor-Thread-2":
- at com.example.inventory.InventoryProcessor.updateInventory(InventoryProcessor.java:78)
- - waiting to lock <0x0000000765432101> (a java.lang.Object)
- - locked <0x0000000765432100> (a java.lang.Object)
- at java.lang.Thread.run(Thread.java:834)
复制代码
分析代码:检查OrderProcessor.processOrder()和InventoryProcessor.updateInventory()方法,发现它们以不同的顺序获取锁:
“`java
// OrderProcessor.java
public void processOrder(Order order) {
synchronized (inventoryLock) { // 先获取inventoryLock
- // 处理订单...
- synchronized (orderLock) { // 再获取orderLock
- // 更新订单状态...
- }
复制代码
}
}
// InventoryProcessor.java
public void updateInventory(Order order) {
- synchronized (orderLock) { // 先获取orderLock
- // 更新库存...
- synchronized (inventoryLock) { // 再获取inventoryLock
- // 记录库存变更...
- }
- }
复制代码
}
- 6. **修复死锁问题:**
- 确保所有线程以相同的顺序获取锁:
- ```java
- // OrderProcessor.java (保持不变)
- public void processOrder(Order order) {
- synchronized (inventoryLock) { // 先获取inventoryLock
- // 处理订单...
- synchronized (orderLock) { // 再获取orderLock
- // 更新订单状态...
- }
- }
- }
-
- // InventoryProcessor.java (修改锁获取顺序)
- public void updateInventory(Order order) {
- synchronized (inventoryLock) { // 先获取inventoryLock,与OrderProcessor保持一致
- // 更新库存...
- synchronized (orderLock) { // 再获取orderLock
- // 记录库存变更...
- }
- }
- }
复制代码
1. 验证修复:重新部署应用后,使用JConsole监控线程状态,确认不再出现死锁情况。
案例四:GC频繁问题排查
问题描述:一个Java应用频繁进行垃圾收集,导致应用响应时间不稳定。
排查步骤:
1. 使用jps确认进程ID:jps -l输出:12345 /opt/app/cache-service.jar
2. 使用jstat监控GC情况:jstat -gc 12345 1s 10观察到Young GC(YGC)和Full GC(FGC)发生频率很高,且GC时间(GCT)占比较大。
3. - 使用jinfo查看JVM参数:jinfo -flags 12345发现堆内存设置较小,且新生代与老年代比例不合理:-XX:NewSize=134217728
- -XX:MaxNewSize=134217728
- -XX:OldSize=402653184
- -XX:MaxHeapSize=536870912
复制代码 4. 使用jmap查看对象统计信息:jmap -histo 12345 | head -20发现大量短生命周期的对象(如java.lang.String、byte[]等)占用了大量内存。
5. - 分析代码:检查应用代码,发现存在大量不必要的字符串操作和对象创建:// 问题代码
- public String processData(Data data) {
- String result = "";
- for (Item item : data.getItems()) {
- // 每次循环都创建新的字符串对象
- result += item.toString();
- }
- return result;
- }
复制代码 6. - 优化代码和JVM参数:// 优化后代码
- public String processData(Data data) {
- StringBuilder result = new StringBuilder();
- for (Item item : data.getItems()) {
- // 使用StringBuilder减少字符串对象创建
- result.append(item.toString());
- }
- return result.toString();
- }
复制代码
使用jps确认进程ID:
输出:
- 12345 /opt/app/cache-service.jar
复制代码
使用jstat监控GC情况:
观察到Young GC(YGC)和Full GC(FGC)发生频率很高,且GC时间(GCT)占比较大。
使用jinfo查看JVM参数:
发现堆内存设置较小,且新生代与老年代比例不合理:
- -XX:NewSize=134217728
- -XX:MaxNewSize=134217728
- -XX:OldSize=402653184
- -XX:MaxHeapSize=536870912
复制代码
使用jmap查看对象统计信息:
- jmap -histo 12345 | head -20
复制代码
发现大量短生命周期的对象(如java.lang.String、byte[]等)占用了大量内存。
分析代码:检查应用代码,发现存在大量不必要的字符串操作和对象创建:
- // 问题代码
- public String processData(Data data) {
- String result = "";
- for (Item item : data.getItems()) {
- // 每次循环都创建新的字符串对象
- result += item.toString();
- }
- return result;
- }
复制代码
优化代码和JVM参数:
- // 优化后代码
- public String processData(Data data) {
- StringBuilder result = new StringBuilder();
- for (Item item : data.getItems()) {
- // 使用StringBuilder减少字符串对象创建
- result.append(item.toString());
- }
- return result.toString();
- }
复制代码
调整JVM参数,增加堆内存并优化新生代与老年代比例:
- -Xmx2g -Xms2g -XX:NewRatio=2 -XX:SurvivorRatio=8
复制代码
1. 验证修复:重新启动应用并应用新的JVM参数,使用jstat监控GC情况,确认GC频率显著降低,应用响应时间变得更加稳定。
Java进程监控最佳实践
1. 建立监控体系
监控指标:
• CPU使用率
• 内存使用情况(堆内存、非堆内存)
• GC频率和耗时
• 线程状态和数量
• 类加载情况
• 应用特定指标(如请求响应时间、错误率等)
监控工具组合:
• 使用JMX Exporter + Prometheus + Grafana建立可视化监控面板
• 使用ELK(Elasticsearch, Logstash, Kibana)收集和分析日志
• 使用APM工具(如SkyWalking、Pinpoint)进行应用性能监控
示例:使用JMX Exporter暴露JVM指标
1. 下载JMX Exporter:wget https://repo1.maven.org/maven2/io/prometheus/jmx/jmx_prometheus_javaagent/0.16.1/jmx_prometheus_javaagent-0.16.1.jar
2. - 创建配置文件jmx-exporter-config.yml:
- “`yaml
- lowercaseOutputName: true
- lowercaseOutputLabelNames: true
- rules:pattern: ‘java.lang(\w+)’
- name: jvm_memory_bytes_heap
- type: GAUGE
- labels:
- area: heappattern: ‘java.lang(\w+)’
- name: jvm_memory_bytes_nonheap
- type: GAUGE
- labels:
- area: nonheappattern: ‘java.lang<>(\w+)’
- name: jvm_gc_collection_seconds
- type: COUNTER
- labels:
- gc: $1”`
复制代码 3. - pattern: ‘java.lang(\w+)’
- name: jvm_memory_bytes_heap
- type: GAUGE
- labels:
- area: heap
复制代码 4. - pattern: ‘java.lang(\w+)’
- name: jvm_memory_bytes_nonheap
- type: GAUGE
- labels:
- area: nonheap
复制代码 5. - pattern: ‘java.lang<>(\w+)’
- name: jvm_gc_collection_seconds
- type: COUNTER
- labels:
- gc: $1
复制代码 6. 启动Java应用时添加JMX Exporter:java -javaagent:jmx_prometheus_javaagent-0.16.1.jar=8080:jmx-exporter-config.yml -jar myapp.jar
7. - 配置Prometheus抓取指标:
- “`yaml
- scrape_configs:job_name: ‘java-app’
- static_configs:targets: [‘localhost:8080’]”`
复制代码 8. - job_name: ‘java-app’
- static_configs:targets: [‘localhost:8080’]
复制代码 9. targets: [‘localhost:8080’]
10. 在Grafana中创建仪表盘,展示JVM关键指标。
下载JMX Exporter:
- wget https://repo1.maven.org/maven2/io/prometheus/jmx/jmx_prometheus_javaagent/0.16.1/jmx_prometheus_javaagent-0.16.1.jar
复制代码
创建配置文件jmx-exporter-config.yml:
“`yaml
lowercaseOutputName: true
lowercaseOutputLabelNames: true
rules:
• - pattern: ‘java.lang(\w+)’
- name: jvm_memory_bytes_heap
- type: GAUGE
- labels:
- area: heap
复制代码 • - pattern: ‘java.lang(\w+)’
- name: jvm_memory_bytes_nonheap
- type: GAUGE
- labels:
- area: nonheap
复制代码 • - pattern: ‘java.lang<>(\w+)’
- name: jvm_gc_collection_seconds
- type: COUNTER
- labels:
- gc: $1
复制代码
”`
启动Java应用时添加JMX Exporter:
- java -javaagent:jmx_prometheus_javaagent-0.16.1.jar=8080:jmx-exporter-config.yml -jar myapp.jar
复制代码
配置Prometheus抓取指标:
“`yaml
scrape_configs:
• - job_name: ‘java-app’
- static_configs:targets: [‘localhost:8080’]
复制代码 • targets: [‘localhost:8080’]
• targets: [‘localhost:8080’]
”`
在Grafana中创建仪表盘,展示JVM关键指标。
2. 设置合理的JVM参数
堆内存设置:
• 根据应用内存需求设置初始堆大小(-Xms)和最大堆大小(-Xmx),通常设置为相同值以避免运行时调整
• 生产环境建议堆大小不超过物理内存的50-70%,留足够空间给操作系统和其他进程
新生代与老年代比例:
• 使用-XX:NewRatio设置新生代与老年代的比例,默认为2(新生代占堆的1/3)
• 对于大量短生命周期对象的应用,可以增大新生代比例(如-XX:NewRatio=1)
GC选择:
• 对于小规模应用(堆大小小于4GB),可以使用Parallel GC(-XX:+UseParallelGC)
• 对于大规模应用或对延迟敏感的应用,可以使用G1 GC(-XX:+UseG1GC)
• 对于超大规模应用或对延迟极其敏感的应用,可以考虑ZGC(-XX:+UseZGC)或Shenandoah(-XX:+UseShenandoahGC)
示例:生产环境JVM参数配置
- # 堆内存设置
- -Xmx4g -Xms4g
- # GC设置
- -XX:+UseG1GC
- -XX:MaxGCPauseMillis=200
- -XX:InitiatingHeapOccupancyPercent=45
- # 元空间设置
- -XX:MetaspaceSize=256m
- -XX:MaxMetaspaceSize=512m
- # 日志设置
- -Xlog:gc*:file=gc.log:time,uptime,level,tags:filecount=5,filesize=10m
- # 其他优化参数
- -XX:+AlwaysPreTouch
- -XX:+UseStringDeduplication
复制代码
3. 定期进行健康检查
检查项:
• 内存使用趋势:检查是否存在内存泄漏
• GC行为:检查GC频率和耗时是否正常
• 线程状态:检查是否存在死锁或大量阻塞线程
• 类加载情况:检查是否存在类加载泄漏
• 应用性能:检查响应时间、吞吐量等指标
自动化检查脚本示例:
- #!/bin/bash
- # Java进程健康检查脚本
- APP_PID=$(jps -l | grep myapp.jar | awk '{print $1}')
- if [ -z "$APP_PID" ]; then
- echo "ERROR: Application not found!"
- exit 1
- fi
- # 检查CPU使用率
- CPU_USAGE=$(top -b -n 1 -p $APP_PID | tail -1 | awk '{print $9}')
- echo "CPU Usage: $CPU_USAGE%"
- if (( $(echo "$CPU_USAGE > 80" | bc -l) )); then
- echo "WARNING: High CPU usage detected!"
- fi
- # 检查内存使用情况
- MEMORY_INFO=$(jstat -gcutil $APP_PID | tail -1)
- OLD_USAGE=$(echo $MEMORY_INFO | awk '{print $4}')
- echo "Old Generation Usage: $OLD_USAGE%"
- if (( $(echo "$OLD_USAGE > 90" | bc -l) )); then
- echo "WARNING: High memory usage in Old Generation!"
- fi
- # 检查GC情况
- GC_INFO=$(jstat -gc $APP_PID | tail -1)
- YGC=$(echo $GC_INFO | awk '{print $13}')
- FGC=$(echo $GC_INFO | awk '{print $15}')
- echo "Young GC Count: $YGC, Full GC Count: $FGC"
- # 检查线程情况
- THREAD_COUNT=$(jstack $APP_PID | grep "java.lang.Thread.State:" | wc -l)
- BLOCKED_COUNT=$(jstack $APP_PID | grep "java.lang.Thread.State: BLOCKED" | wc -l)
- echo "Total Threads: $THREAD_COUNT, Blocked Threads: $BLOCKED_COUNT"
- if [ $BLOCKED_COUNT -gt 10 ]; then
- echo "WARNING: High number of blocked threads detected!"
- fi
- # 检查死锁
- DEADLOCK_COUNT=$(jstack $APP_PID | grep "Found one Java-level deadlock" | wc -l)
- if [ $DEADLOCK_COUNT -gt 0 ]; then
- echo "ERROR: Deadlock detected!"
- exit 1
- fi
- echo "Health check completed."
复制代码
4. 建立问题排查流程
问题排查步骤:
1. 问题识别:通过监控指标或用户反馈识别问题
2. 信息收集:收集相关日志、堆转储、线程转储等信息
3. 初步分析:使用基础工具(jps, jstat, jstack等)进行初步分析
4. 深入分析:使用高级工具(VisualVM, MAT等)进行深入分析
5. 问题定位:确定问题的根本原因
6. 解决方案:制定并实施解决方案
7. 效果验证:验证问题是否解决
8. 经验总结:记录问题解决过程,形成知识库
示例:问题排查流程图
- ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
- │ 问题识别 │────▶│ 信息收集 │────▶│ 初步分析 │
- └─────────────┘ └─────────────┘ └─────────────┘
- │ │
- │ ▼
- │ ┌─────────────┐
- │ │ 深入分析 │
- │ └─────────────┘
- │ │
- │ ▼
- │ ┌─────────────┐
- └───────────────────────────────▶│ 问题定位 │
- └─────────────┘
- │
- ▼
- ┌─────────────┐
- │ 解决方案 │
- └─────────────┘
- │
- ▼
- ┌─────────────┐
- │ 效果验证 │
- └─────────────┘
- │
- ▼
- ┌─────────────┐
- │ 经验总结 │
- └─────────────┘
复制代码
5. 持续优化和改进
优化方向:
• 代码优化:减少不必要的对象创建,优化算法和数据结构
• JVM参数调优:根据应用特性调整内存设置、GC策略等
• 架构优化:考虑缓存、异步处理、分布式架构等
• 监控完善:增加更多有价值的监控指标,提高告警准确性
持续集成示例:
- // Jenkinsfile示例
- pipeline {
- agent any
-
- stages {
- stage('Build') {
- steps {
- sh 'mvn clean package'
- }
- }
-
- stage('Performance Test') {
- steps {
- sh 'java -jar myapp.jar &'
- sh 'sleep 30'
- sh 'jmeter -n -t performance-test.jmx -l results.jtl'
- sh 'pkill -f myapp.jar'
- }
- }
-
- stage('Analysis') {
- steps {
- script {
- // 分析性能测试结果
- def avgResponseTime = sh(
- script: 'cat results.jtl | grep ",200," | awk -F "," \'{sum+=$2; count++} END {print sum/count}\'',
- returnStdout: true
- ).trim()
-
- if (avgResponseTime.toDouble() > 1000) {
- error("Average response time too high: ${avgResponseTime}ms")
- }
- }
- }
- }
-
- stage('Deploy') {
- steps {
- sh 'scp target/myapp.jar user@prod-server:/opt/app/'
- sh 'ssh user@prod-server "sudo systemctl restart myapp"'
- }
- }
- }
-
- post {
- always {
- // 收集应用性能数据
- sh 'ssh user@prod-server "jstat -gcutil \$(jps -l | grep myapp.jar | awk \'{print \$1}\') > gc-stats.log"'
- archiveArtifacts 'gc-stats.log'
- }
- }
- }
复制代码
总结
Java进程检查是保障Java应用稳定运行的关键环节。通过掌握本文介绍的各种命令和工具,你可以有效地监控Java应用的运行状态,快速定位和解决各种问题。
从基础的jps、jstat、jinfo、jmap和jstack命令,到高级的JConsole、VisualVM、Java Mission Control和MAT工具,每种工具都有其特定的应用场景和优势。在实际工作中,根据问题的性质选择合适的工具,可以大大提高问题排查的效率。
同时,建立完善的监控体系、设置合理的JVM参数、定期进行健康检查、建立规范的问题排查流程,以及持续优化和改进,是确保Java应用长期稳定运行的重要保障。
希望本文能够帮助你更好地掌握Java进程检查的技能,让你的应用监控更加得心应手,解决实际工作中的各种问题。记住,工具只是手段,真正重要的是理解Java虚拟机的工作原理,结合实际场景灵活运用各种工具,才能成为一名优秀的Java应用运维专家。 |
|