背景

在Java面试的时候,经常会被问到,如果线上服务器CPU/内存占用飙高,程序很慢或者卡死,你有什么解决方案或者排查思路?本文旨在对这些问题的排查思路做一个梳理,如不特指,以下服务器系统均指Linux.

CPU 飚高问题排查

思路:首先找到 CPU 飚高的那个 Java 进程,因为你的服务器会有多个 JVM 进程。然后找到那个进程中的 “问题线程”,最后根据线程堆栈信息找到问题代码。最后对代码进行排查。
排查步骤

  1. 通过 top 命令找到 CPU 消耗最高的进程,并记住进程 ID
  2. 再次通过 top -Hp [进程 ID] 找到 CPU 消耗最高的线程 ID,并记住线程 ID.
  3. 通过 JDK 提供的 jstack 工具 dump 线程堆栈信息到指定文件中。具体命令:jstack -l [进程 ID] >jstack.log。
  4. 由于刚刚的线程 ID 是十进制的,而堆栈信息中的线程 ID 是16进制的,因此我们需要将10进制的转换成16进制的,并用这个线程 ID 在堆栈中查找。使用 printf “%x\n” [十进制数字] ,可以将10进制转换成16进制。
  5. 通过刚刚转换的16进制数字从堆栈信息里找到对应的线程堆栈。就可以从该堆栈中看出端倪。
内存问题排查

思路:通常,内存的问题就是 GC 的问题,因为 Java 的内存由 GC 管理。有2种情况,一种是内存溢出了,一种是内存没有溢出,但 GC 不健康。

  • 内存溢出排查思路:内存溢出的情况可以通过加上 -XX:+HeapDumpOnOutOfMemoryError 参数,该参数作用是:在程序内存溢出时输出 dump 文件。有了 dump 文件,就可以通过 dump 分析工具进行分析了,比如常用的MAT,Jprofile,jvisualvm 等工具都可以分析,这些工具都能够看出到底是哪里溢出,哪里创建了大量的对象等等信息。
  • GC 的健康问题排查思路:GC分为YGC和FGC,我们要看FGC后,老年代对象的存活率,如果 FGC 后还有大量对象,说明 Old 区过小或者程序中有异常分配的大对象。如果 FGC 后效果很好,说明 Old 区存在了大量短命的对象,优化的点应该是让这些对象在新生代就被 YGC 掉,通常的做法是增大新生代,如果有大而短命的对象,通过参数设置对象的大小,不要让这些对象进入 Old 区,还需要检查晋升年龄是否过小。如果 YGC 后,有大量对象因为无法进入 Survivor 区从而提前晋升,这时应该增大 Survivor 区,但不宜太大。

下面来重点说说Old区FGC 后还有大量对象问题的排查思路

Old区FGC 后还有大量对象问题的排查思路

  1. 我们先用如下命令找出java进程号
ps -ef|grep java

说说Java程序CPU,内存异常的排查思路_堆栈
得到进程号为12945

  1. 通过jmap工具分析堆内存情况
jmap -heap 12945

说说Java程序CPU,内存异常的排查思路_服务器_02
我们发现Old区域内存占用已经99%了,那么是不是垃圾回收器没有工作呢?我们通过如下命令来看看GC线程的工作情况

jstat -gcutil 12945 1000 10

1000为统计的间隔,单位为毫秒,10为统计的次数,输出如下:
说说Java程序CPU,内存异常的排查思路_java_03
从输出中同样可以看到E(Eden)区与O(Old)区都已经被占满了。其他几个输出项的含义如下:

  • YGC: 从启动到采样时Young Generation GC的次数
  • YGCT: 从启动到采样时Young GenerationGC所用的时间 (s).
  • FGC: 从启动到采样时Old Generation GC的次数. FGCT: 从启动到采样时Old
  • Generation GC所用的时间 (s). GCT: 从启动到采样时GC所用的总时间 (s).

可以看到JVM一直在尝试回收老年代,但是一直没能将内存回收回来。

为什么垃圾收集器一直在运行但老年代没法被回收?
先把当前的堆情况导出来,然后使用工具进行分析。

jmap -dump:live,format=b,file=headInfo.hprof 12945

live这个参数表示我们需要抓取的是目前在生命周期内的内存对象,也就是说GC收不走的对象,在这种场景下,我们需要的就是这些内存的信息。
生成了hprof文件后,可以拉回到本地进行分析了。
剩下的详细过程可以看我的另一篇文章记一次服务器正常但tomcat无响应问题