记一次元空间内存爆满导致idea卡顿问题

743 阅读4分钟

现象

在每次使用idea一段时间之后(打开了很多项目),便开始出现莫名其妙卡顿,每输入一下就要卡顿一段时间,但是用任务管理器检查当前主机CPU和内存并未占用多少,也就并不是主机本身资源不够的问题。idea show memory indicator在右下角显示当前堆内存在30-60%左右徘徊。但是有个现象在卡顿之后堆内存会将降到10%左右,猜测是触发了Full GC时间过长导致卡顿问题。

配置

公司给的一台是联想工作站,Idea配置如下

类型内容
系统Windows 10 专业工作站版
CPUIntel(R) Xeon(R) CPU E5-2630 v4 @ 2.20GHz 2.20 GHz (2 个处理器)
内存32.0 GB (31.7 GB 可用)
Idea版本2021.2(Ultimate Edition)
Runtime version11.0.11+9-b1504.13 amd64
VMOpenJDK 64-Bit Server By JetBrains s.r.o

JVM参数

GC停顿时间过长,第一时间浮现到脑海的方式就是检查JVM参数,看是否有没有配置错,除去-Xms4G-Xmx6G是我安装Idea后第一次配置的堆内存大小之后其他都是idea默认的生成的配置。可以看到使用的垃圾回收器是G1,G1基于Region的堆内存布局,它优先回收最有回收价值的Region,也就是支持增量回收,所以堆内存配大一点并不会影响停顿时间。还有-XX:MaxMetaspaceSize=640m这个参数是当时用VMare开了3台虚拟机,将内存都分配虚拟时Idea抛出元空间OOM时所配置的,限制了元空间大小。

-Xms4G
-Xmx6G
-XX:ReservedCodeCacheSize=512m
-XX:+UseG1GC

-XX:SoftRefLRUPolicyMSPerMB=50
-XX:CICompilerCount=2
-XX:+HeapDumpOnOutOfMemoryError
-XX:-OmitStackTraceInFastThrow
-ea
-Dsun.io.useCanonCaches=false
-Djdk.http.auth.tunneling.disabledSchemes=""
-Djdk.attach.allowAttachSelf=true
-Djdk.module.illegalAccess.silent=true
-Dkotlinx.coroutines.debug=off

-XX:MaxMetaspaceSize=640m

JConsole进行监控

发现Metaspace总是维持在90%以上,并且老年代GC消耗的总时间过长。

jconsole.png

查看GC日志

由于idea的运行环境时jdk11所以使用jvm参数-Xlog(jdk9开始提供)来打印日志。

#格式
-Xlog Usage: -Xlog[:[selections][:[output][:[decorators][:output-options]]]]

#将gc日志输出到E:\jvm\gc\idea-gc.log
-Xlog:gc*:E:\jvm\gc\idea-gc.log

发现日志中存在大量的由于元空间阈值触发的full gc,消耗时间达到了568ms可以感觉到很明显的卡顿了。

[1330.321s][info][gc,task                  ] GC(151) Using 28 workers of 28 for full compaction
[1330.321s][info][gc                       ] GC(152) Concurrent Cycle
[1330.321s][info][gc,marking               ] GC(152) Concurrent Clear Claimed Marks
[1330.322s][info][gc,start                 ] GC(151) Pause Full (Metadata GC Threshold)
[1330.323s][info][gc,marking               ] GC(152) Concurrent Clear Claimed Marks 1.240ms
[1330.323s][info][gc,marking               ] GC(152) Concurrent Scan Root Regions
[1330.323s][info][gc,marking               ] GC(152) Concurrent Scan Root Regions 0.106ms
[1330.323s][info][gc,marking               ] GC(152) Concurrent Mark (1330.323s)
[1330.323s][info][gc,marking               ] GC(152) Concurrent Mark From Roots
[1330.323s][info][gc,task                  ] GC(152) Using 7 workers of 7 for marking
[1330.329s][info][gc,phases,start          ] GC(151) Phase 1: Mark live objects
[1330.513s][info][gc,stringtable           ] GC(151) Cleaned string and symbol table, strings: 210592 processed, 38 removed, symbols: 1245054 processed, 287 removed
[1330.513s][info][gc,phases                ] GC(151) Phase 1: Mark live objects 183.619ms
[1330.513s][info][gc,phases,start          ] GC(151) Phase 2: Prepare for compaction
[1330.549s][info][gc,phases                ] GC(151) Phase 2: Prepare for compaction 36.260ms
[1330.549s][info][gc,phases,start          ] GC(151) Phase 3: Adjust pointers
[1330.675s][info][gc,phases                ] GC(151) Phase 3: Adjust pointers 125.481ms
[1330.675s][info][gc,phases,start          ] GC(151) Phase 4: Compact heap
[1330.726s][info][gc,phases                ] GC(151) Phase 4: Compact heap 50.860ms
[1330.890s][info][gc,heap                  ] GC(151) Eden regions: 0->0(1228)
[1330.890s][info][gc,heap                  ] GC(151) Survivor regions: 29->0(154)
[1330.890s][info][gc,heap                  ] GC(151) Old regions: 313->320
[1330.890s][info][gc,heap                  ] GC(151) Humongous regions: 27->25
[1330.890s][info][gc,metaspace             ] GC(151) Metaspace: 584875K->584871K(1134592K)
[1330.890s][info][gc                       ] GC(151) Pause Full (Metadata GC Threshold) 712M->664M(4096M) 568.505ms
[1330.891s][info][gc,cpu                   ] GC(151) User=9.23s Sys=0.16s Real=0.57s

原因

-XX:ReservedCodeCacheSize=512m参数指定了JIT编译代码缓存大小如果超过将不会使用JIT即时编译,由解释器解释执行。那么随着热点代码的编译,元空间总会占用512m而-XX:MaxMetaspaceSize=640m设置了超过640m就会触发full gc,而剩下的空间不足够idea使用,所以频繁触发Full GC,导致时不时地卡顿一下。

解决方式

1、不指定-XX:MaxMetaspaceSize

这个参数是元空间大小超过设置值时会触发full gc。如果不指定元空间的大小,默认情况下,元空间最大的大小是系统内存的大小,元空间一直扩大,虚拟机可能会消耗完所有的可用系统内存,如果元空间内存不够时抛出OOM异常

2、指定-XX:MaxMetaspaceSize合适的大小

元空间大小在580-700m左右,所以设置了1024m后基本上没有出现卡顿问题。

总结

JDK8之后,永久代完全移除由元空间代替其作为方法区的实现。-XX:MaxMetaspaceSize可以指定最大元空间大小超过阈值会触发Full GC-XX:MetaspaceSize指定的是初始化元空间大小。