Mac 本地 CPU 飙升问题解决记录(Full GC)

1. 现象:CPU 使用率高达 600%

idea 打开项目,Mac 风扇狂转不止,通过 Mac 自带的 Activity Monitor 看到有个 Java 进程 CPU 使用率高达 600%,丝毫没有停止的迹象。

2. 分析:查找 CPU 使用率高的线程

按照经验来说,这时候的标准步骤如下:

  • 使用 top -Hp pid 查看 CPU 使用率高的线程
  • jstack pid 导出进程线程堆栈信息
  • 将 top 命令中的线程 id 转成 16 进制,去 jstack 导出的线程快照中查找对应线程

由于 Mac 上的 top 命令不太好使,于是使用 ps -M pid 来查看线程信息。但令人失望的是,结果中并没有线程 id,场面一时陷入僵局。不过在 ps 命令执行的结果中我看到了有 8 个占用 cpu 比较多的线程,如下图:

占用cpu高的线程

现在虽然知道是这 8 个线程导致的问题,但是看不到线程 id,终究无法确定这几个线程具体作用。

这时候突然想到之前有用过那种在线的线程堆栈分析网站,于是抱着试一试的心态,打开了 jstack.review,上传快照文件,在网站给出的报告中,眼尖的我发现了下面的信息:

gc线程

这里有 8 个 gc 线程,上面 ps 命令查出来的CPU占用率高的线程数也是 8,难道是巧合?所以我猜测这个 8 个占满 CPU 线程就是 gc 线程。

3. 确认:查看 GC 信息

为了进一步验证上面的猜测,通过 jstat -gcutil pid 可以看到当前进程的 gc 回收情况,如下:

gc回收情况

通过上图可以得知如下信息:

  • Eden 内存已满(E 列一直是 100%),新生代在观测期间没有发生 GC(YGC 列没变过)
  • 老年代一直在 GC,也就是 Full GC,而且回收没有任何效果(O 列一直是 99.92%)
  • 总之:JVM 一直在进行 Full GC

对于上述结论,简单推断有如下原因:

  • 堆内存设置过小,频繁创建对象,很快占满新生代,yong gc 回收速度赶不上对象分配速度,于是直接分配到老年代。所以上面图中,YGC 次数就没再变过。
  • 老年代空间也小,很快达到了 Full GC 触发阈值,进行回收。同样,Full GC 速度赶不上对象分配速度,这才导致回收并没有什么效果,还一直进行回收。

总结:堆内存设置过小导致频繁 Full GC ,占满 CPU 使用率。

4. 定位:找到进程位置

确定是 GC 线程捣的鬼,现在就是要确定是哪个进程创建的这些线程。还记得上文中 ps-M pid 命令执行的结果中有进程信息,最终找到两个关键信息:

  • /Library/Java/JavaVirtualMachines/jdk1.8.0_212.jdk/Contents/Home/bin/java -Xmx700m
  • org.jetbrains.jps.cmdline.BuildMain 127.0.0.1 65027 e23380ca-d91c-4b15-815c-f7cdc064847a /Users/imant/Library/Caches/JetBrains/IntelliJIdea2021.1/compile-server

第一个设置堆最大为 700m,基本也验证了上面的推断堆设置过小的问题。第二条信息能告诉我们,这是个 idea 相关的进程,而且是编译相关的。试着打开 idea 设置中的 Compiler 选项,发现了下面的设置:

对,就这个 700。看介绍是编译时进程所需要的堆内存,Google 后在 idea 官网得知下面信息:

The heap size allocated for running the IDE is not the same as the heap size for compiling your application. If you want to configure the heap size for the build process that compiles your code, open Settings/Preferences ⌘ ,, select Build, Execution, Deployment | Compiler, and specify the necessary amount of memory in the Build process heap size field.

意思就是运行 idea 需要的堆内存(Custom VM Options)和编译项目需要的堆内存(compiler 设置)不是一个东西。

5. 解决:调大堆内存

把内存设置到 2048,重启 idea,执行 jstat -gcutil pid 观察 GC 回收情况,如下:

可以看到 S0 S1 区域通过复制算法轮流占用,YGC 一直在增加,YG 后 Eden 区(E 列)的内存有明显的减少。FGC 的次数不再频繁,而且在进行 FG 回收后,老年代的内存也明显减少(O 列 94.71% > 66.88%),这才是一个健康的垃圾回收现象。
再看 Activity Monitor,项目启动前几秒CPU占用率会高,但很快就降下来,最后进程消失(猜测是 idea 本身的机制,项目编译完,将编译进程 kill 掉)。

posted @ 2022-07-08 18:04  Tailife  阅读(2830)  评论(0编辑  收藏  举报