JVM–调优–04–案例07–元空间变大导致FULL GC
1、现象
Full GC (Metadata GC Threshold) //metaspace空间不够,导致Full GC
Metaspace used 35337K, capacity 56242K, committed 56320K, reserved 1099776K
- used :已使用的空间大小
- capacity:当前已经分配且未释放的空间容量大小
- committed:当前已经分配的空间大小
- reserved:预留的空间大小
1.1、元空间内存碎片化指标
capacity和used之差较大的时候,说明碎片化严重
1.2、内存碎片化严重的场景
- 创建了较多的类加载器,同时使用率较低。
1.3、GC日志
{Heap before GC invocations=0 (full 0):
par new generation total 314560K, used 141123K [0x00000000c0000000, 0x00000000d5550000, 0x00000000d5550000)
eden space 279616K, 50% used [0x00000000c0000000, 0x00000000c89d0d00, 0x00000000d1110000)
from space 34944K, 0% used [0x00000000d1110000, 0x00000000d1110000, 0x00000000d3330000)
to space 34944K, 0% used [0x00000000d3330000, 0x00000000d3330000, 0x00000000d5550000)
concurrent mark-sweep generation total 699072K, used 0K [0x00000000d5550000, 0x0000000100000000, 0x0000000100000000)
Metaspace used 35337K, capacity 56242K, committed 56320K, reserved 1099776K
class space used 4734K, capacity 8172K, committed 8172K, reserved 1048576K
1.448: [Full GC (Metadata GC Threshold) 1.448: [CMS: 0K->10221K(699072K), 0.0487207 secs] 141123K->10221K(1013632K), [Metaspace: 35337K->35337K(1099776K)], 0.0488547 secs] [Times: user=0.09 sys=0.00, real=0.05 secs]
Heap after GC invocations=1 (full 1):
par new generation total 314560K, used 0K [0x00000000c0000000, 0x00000000d5550000, 0x00000000d5550000)
eden space 279616K, 0% used [0x00000000c0000000, 0x00000000c0000000, 0x00000000d1110000)
from space 34944K, 0% used [0x00000000d1110000, 0x00000000d1110000, 0x00000000d3330000)
to space 34944K, 0% used [0x00000000d3330000, 0x00000000d3330000, 0x00000000d5550000)
concurrent mark-sweep generation total 699072K, used 10221K [0x00000000d5550000, 0x0000000100000000, 0x0000000100000000)
Metaspace used 35337K, capacity 56242K, committed 56320K, reserved 1099776K
class space used 4734K, capacity 8172K, committed 8172K, reserved 1048576K
}
{Heap before GC invocations=1 (full 1):
par new generation total 314560K, used 0K [0x00000000c0000000, 0x00000000d5550000, 0x00000000d5550000)
eden space 279616K, 0% used [0x00000000c0000000, 0x00000000c0000000, 0x00000000d1110000)
from space 34944K, 0% used [0x00000000d1110000, 0x00000000d1110000, 0x00000000d3330000)
to space 34944K, 0% used [0x00000000d3330000, 0x00000000d3330000, 0x00000000d5550000)
concurrent mark-sweep generation total 699072K, used 10221K [0x00000000d5550000, 0x0000000100000000, 0x0000000100000000)
Metaspace used 35337K, capacity 56242K, committed 56320K, reserved 1099776K
class space used 4734K, capacity 8172K, committed 8172K, reserved 1048576K
1.497: [Full GC (Last ditch collection) 1.497: [CMS: 10221K->3565K(699072K), 0.0139783 secs] 10221K->3565K(1013632K), [Metaspace: 35337K->35337K(1099776K)], 0.0193983 secs] [Times: user=0.03 sys=0.00, real=0.02 secs]
Heap after GC invocations=2 (full 2):
par new generation total 314560K, used 0K [0x00000000c0000000, 0x00000000d5550000, 0x00000000d5550000)
eden space 279616K, 0% used [0x00000000c0000000, 0x00000000c0000000, 0x00000000d1110000)
from space 34944K, 0% used [0x00000000d1110000, 0x00000000d1110000, 0x00000000d3330000)
to space 34944K, 0% used [0x00000000d3330000, 0x00000000d3330000, 0x00000000d5550000)
concurrent mark-sweep generation total 699072K, used 3565K [0x00000000d5550000, 0x0000000100000000, 0x0000000100000000)
Metaspace used 17065K, capacity 22618K, committed 35840K, reserved 1079296K
class space used 1624K, capacity 2552K, committed 8172K, reserved 1048576K
}
2、分析
当元空间出现内存碎片化时,就要考虑是否创建了大量的类加载器。
通过 dump 堆存储文件发现存在大量DelegatingClassLoader
。
通过进一步分析,发现是由于反射导致创建大量DelegatingClassLoader
。
2.1、反射创建大量 类加载器
的原理
使用反射调用方法的时候,有个调用次数阀值(默认15次)
- 当
反射调用方法的次数
< 15:通过JNI调用
来实现方法的调用 - 当
反射调用方法的次数
>= 15:通过生成字节码
来实现方法的调用,称为膨胀(inflation)机制。使用字节码的时候,会为该方法生成一个类加载器(DelegatingClassLoader)。如果存在大量方法经常反射调用,则会导致创建大量类加载器
(DelegatingClassLoader)。
2.2、分析结论
反射调用导致创建大量DelegatingClassLoader
,占用了较大的元空间内存,同时存在内存碎片化现象,导致元空间利用率不高,从而较快达到阈值,触发 FGC。
3、解决
- 适当调大 metaspace 的空间大小。
- 优化不合理的反射调用。例如最常见的属性拷贝工具类 BeanUtils.copyProperties 可以使用 mapstruct 替换。
4、日志解析
5.218: [Full GC (Metadata GC Threshold) TLAB: gc thread: 0x000055fa2f07c800 [id: 13] desired_size: 22370KB slow allocs: 0 refill waste: 357928B alloc: 1.00000 156625KB refills: 7 waste 13.2% gc: 21204128B slow: 13328B fast: 0B
TLAB totals: thrds: 1 refills: 7 max: 7 slow allocs: 0 max 0 waste: 13.2% gc: 21204128B max: 21204128B slow: 13328B max: 13328B fast: 0B max: 0B
5.218: [Tenured: 0K->15985K(2796224K), 0.1108898 secs] 156625K->15985K(4054528K), [Metaspace: 20986K->20986K(1067008K)], 0.1110571 secs] [Times: user=0.09 sys=0.02, real=0.11 secs]
-
5.218: 这是GC事件发生的时间戳。
-
[Full GC (Metadata GC Threshold)]: 表示触发这次Full GC的原因是元数据GC阈值(Metadata GC Threshold)达到了。这通常是因为JVM的元空间(Metaspace)中的内存使用达到了一个特定的阈值,导致JVM需要进行Full GC来回收不再使用的元数据对象。
-
TLAB: gc thread: 0x000055fa2f07c800 [id: 13]: 这部分信息描述了与TLAB相关的GC线程。每个线程在JVM中都有一个TLAB,用于快速分配对象。
-
desired_size: 22370KB: 表示JVM期望的TLAB大小。
-
slow allocs: 0: 表示没有因为TLAB空间不足而进行的慢分配,慢分配就是在堆上分配。
-
refill waste: 357928B(349.53KB): 表示在重新填充TLAB时浪费的空间大小。
-
alloc: 1.00000: 这可能是TLAB的分配比例或相关参数。
-
156625KB refills: 7 waste 13.2%: 这表示总共有7次TLAB重新填充,浪费了大约13.2%的空间。
-
gc: 21204128B slow: 13328B fast: 0B: 这部分描述了由于TLAB操作导致的GC统计信息。
gc
是通过TLAB触发的GC回收的总字节数,slow
是慢路径回收的字节数,fast
是快路径回收的字节数(在这里为0,表示没有通过快路径回收任何内存)。 -
TLAB totals: 这部分总结了所有线程的TLAB统计信息。
-
thrds: 1: 表示有一个线程参与了TLAB操作。
-
refills, max, slow allocs, waste, gc, slow, fast: 这些是之前已经解释过的TLAB相关统计的汇总。
-
[Tenured: 0K->15985K(2796224K), 0.1108898 secs]: 这部分描述了老年代(Tenured Generation)在GC前后的内存使用情况以及GC所花费的时间。
-
156625K->15985K(4054528K): 这是整个堆在GC前后的内存使用情况。
-
[Metaspace: 20986K->20986K(1067008K)]: 这描述了元空间在GC前后的内存使用情况。
-
[Times: user=0.09 sys=0.02, real=0.11 secs]: 这是GC操作的时间统计,包括用户模式CPU时间、系统模式CPU时间和实际经过的时间。