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 是什么状态呢?经验,YGC 5秒一次左右,每次不超过50毫秒,FGC 最好没有,CMS GC 一天一次左右。

  而 GC 的优化有2个维度,一是频率,二是时长

  看YGC,首先看频率,如果 YGC 超过5秒一次,甚至更长,说明系统内存过大,应该缩小容量,如果频率很高,说明 Eden 区过小,可以将 Eden 区增大,但整个新生代的容量应该在堆的 30% - 40%之间,eden,from 和 to 的比例应该在 8:1:1左右,这个比例可根据对象晋升的大小进行调整。

  如果 YGC 时间过长呢?YGC 有2个过程,一个是扫描,一个是复制,通常扫描速度很快,复制速度相比而言要慢一些,如果每次都有大量对象要复制,就会将 STW 时间延长,还有一个情况就是 StringTable ,这个数据结构中存储着 String.intern 方法返回的常连池的引用,YGC 每次都会扫描这个数据结构(HashTable),如果这个数据结构很大,且没有经过 FGC,那么也会拉长 STW 时长,还有一种情况就是操作系统的虚拟内存,当 GC 时正巧操作系统正在交换内存,也会拉长 STW 时长。

  FGC 我们只能优化频率,无法优化时长,因为这个时长无法控制。如何优化频率呢?

  首先,FGC 的原因有几个,1 是 Old 区内存不够,2 是元数据区内存不够,3 是 System.gc(), 4 是 jmap 或者 jcmd,5 是CMS Promotion failed 或者 concurrent mode failure,6 JVM 基于悲观策略认为这次 YGC 后 Old 区无法容纳晋升的对象,因此取消 YGC,提前 FGC。

  通常优化的点是 Old 区内存不够导致 FGC。如果 FGC 后还有大量对象,说明 Old 区过小,应该扩大 Old 区,如果 FGC 后效果很好,说明 Old 区存在大量短命的对象,优化的点应该是让这些对象在新生代就被 YGC 掉,通常的做法是增大新生代,如果有大而短命的对象,通过参数设置对象的大小,不要让这些对象进入 Old 区,还需要检查晋升年龄是否过小。如果 YGC 后,有大量对象因为无法进入 Survivor 区从而提前晋升,这时应该增大 Survivor 区,但不宜太大。

参考:

  https://www.jianshu.com/p/bca5a49db4b7