性能监控的命令行工具?
操作系统层面:
- 我用过top来查看cpu和内存的使用情况
- 使用过vmstat查看过虚拟内存的统计信息
- 使用过iostat查看过系统的io情况
- 使用过netstat查看过系统的网络信息
JDK自带的命令层面,我使用过:
- jmap -heap pid查看堆内存摘要,包括新生代、老年代和元空间
- jmap -histo pid查看对象分布统计
- jmap -dump生成堆转储文件
了解过哪些可视化的性能监控工具?
- JConsole:JDK自带的监控工具,查看应用程序的运行状态,包括内存使用,线程状态,类加载,GC等
- VisualVM:故障处理工具,集成多个JDK命令行工具的功能
- Java Mission Control:内存分析,线程分析,历史数据
使用过哪些第三方的工具?
- MAT:是一个堆内存分析工具,排查内存泄露问题(从堆转储文件中分析),检查对象间的引用关系
- GChisto:GC日志分析统计工具
- JProfiler:性能分析工具,CPU,内存、线程实时分析
- arthas:阿里巴巴的开源工具,用于线上应用诊断,可以进行JVM信息查看,监控
- async-profiler:低开销的性能分析工具
JVM常见的参数配置
配置堆内存大小:
- -Xms:初始堆大小
- -Xmx:最大堆大小
- -XX:NewSize=n:设置年轻代大小
- -XX:NewRatio=n:设置年轻代与老年代大小比值
- -XX:SurvivorRatio=n:设置年轻代与Survivor大小比值
配置GC收集器参数:
- -XX:+UseSerialGC:配置串行GC器
- -XX:+UseParallelGC:配置并行GC器
- -XX:+UseParallelOldGC:配置并行老年代GC器
配置并行收集的参数:
- -XX:+MaxGCPauseMillis=n:设置最大垃圾回收停顿时间
- -XX:+GCTimeRadio=n:设置垃圾回收时间占程序运行时间的比例
- -XX:+ParallelGCThreads=n:设置并行收集器的线程数
打印GC回收过程日志的参数:
- -XX:+PrintGC:输出GC日志
- -XX:+PrintGCDetails:输出GC日志详情
- -XX:+PrintGCTimeStamps:输出GC日志的时间戳
- -Xloggc:filename:日志文件的输出路径
做过JVM调优吗?
做过,调优的对象包括堆内存,垃圾收集器和JVM运行时参数等
如果堆内存设置过小,可能会导致频繁的垃圾回收。所以在项目中,启动 JVM 的时候配置了 -Xms
和 -Xmx
参数,让堆内存最大可用内存为 2G(我用的丐版服务器)。
在项目运行期间,我会使用 JVisualVM 定期观察和分析 GC 日志,如果发现频繁的 Full GC,我会特意关注一下老年代的使用情况。
接着,通过分析 Heap dump 寻找内存泄漏的源头,看看是否有未关闭的资源,长生命周期的大对象等。
之后进行代码优化,比如说减少大对象的创建、优化数据结构的使用方式、减少不必要的对象持有等。
CPU占用过高怎么排查?
首先,使用 top 命令查看 CPU 占用情况,找到占用 CPU 较高的进程 ID。
top
接着,使用 jstack 命令保存对应进程的线程堆栈信息。
jstack -l <pid> > thread-dump.txt
然后再使用 top -H - p 命令查看进程中线程的占用情况,找到占用 CPU 较高的线程 ID。
top -H -p <pid>
接着在 jstack 的输出中搜索这个十六进制的线程 ID,找到对应的堆栈信息。
"Thread-5" #21 prio=5 os_prio=0 tid=0x00007f812c018800 nid=0x1a85 runnable [0x00007f811c000000]
java.lang.Thread.State: RUNNABLE
at com.example.MyClass.myMethod(MyClass.java:123)
at ...
最后,根据堆栈信息定位到具体的业务方法,查看是否有死循环、频繁的垃圾回收、资源竞争导致的上下文频繁切换等问题。
内存飙高问题怎么排查?
第一,先观察垃圾回收的情况,可以通过 jstat -gc PID 1000
查看 GC 次数和时间。
第二步,通过 jmap 命令 dump 出堆内存信息。
第三步,使用可视化工具分析 dump 文件,比如说 VisualVM,找到占用内存高的对象,再找到创建该对象的业务代码位置,从代码和业务场景中定位具体问题。
频繁minor gc怎么解决?
频繁的 Minor GC 通常意味着新生代中的对象频繁地被垃圾回收,可能是因为新生代空间设置的过小,或者是因为程序中存在大量的短生命周期对象(如临时变量)。
可以使用 GC 日志进行分析,查看 GC 的频率和耗时,找到频繁 GC 的原因。
-XX:+PrintGCDetails -Xloggc:gc.log
如果是因为新生代空间不足,可以通过 -Xmn
增加新生代的大小,减缓新生代的填满速度。
java -Xmn256m your-app.jar
频繁full gc怎么办?
频繁的 Full GC 通常意味着老年代中的对象频繁地被垃圾回收,可能是因为老年代空间设置的过小,或者是因为程序中存在大量的长生命周期对象。
查看 GC 的频率和堆内存的使用情况:
# 查看堆内存各区域的使用率以及GC情况
jstat -gcutil -h20 pid 1000
# 查看堆内存中的存活对象,并按空间排序
jmap -histo pid | head -n20
# dump堆内存文件,VisualVM
jmap -dump:format=b,file=heap pid
假如是因为大对象直接分配到老年代导致的 Full GC 频繁,可以通过 -XX:PretenureSizeThreshold
参数设置大对象直接进入老年代的阈值。或者将大对象拆分成小对象,减少大对象的创建。比如说分页。
假如是因为内存泄漏导致的频繁 Full GC,可以通过分析堆内存 dump 文件找到内存泄漏的对象,再找到内存泄漏的代码位置,优化引用释放机制,及时释放资源,比如说 ThreadLocal、数据库连接、IO 资源等。
假如是因为 GC 参数配置不合理导致的频繁 Full GC,可以通过调整 GC 参数来优化 GC 行为。或者直接更换更适合的 GC 收集器,如 G1、ZGC 等。