JDK8記FullGC時候Metaspace內存不會被垃圾回收


本文鏈接:https://blog.csdn.net/a15939557197/article/details/90635460
背景
前段時間有一個這樣的需求:第三方調用接口,30分鍾內調用120W次;

物理機(與線上配置一樣)上壓測,第一次壓了20w次,沒有出現問題;接着又壓了20w次,出現了內存溢出問題。

java.lang.OutOfMemoryError: Metaspace
JVM配置 
JAVA_OPT_MEM="
-server -Xms4096M -Xmx4096M -Xmn512M -XX:MetaspaceSize=512M -XX:MaxMetaspaceSize=512M
-XX:+UseParNewGC //新生區使用CMS(標記清理)算法進行垃圾回收
-XX:+UseConcMarkSweepGC //新生代使用並行收集器,老年代使用 CMS+串行收集器
...
...
"
 初始化最大元空間是512M,

  增加兩個參數,在Full GC前后分別對內存做一個heap dump

-XX:+HeapDumpBeforeFullGC
-XX:+HeapDumpAfterFullGC
觀察GC日志

{Heap before GC invocations=2799 (full 1):
par new generation total 471872K, used 58664K [0x00000006c0000000, 0x00000006e0000000, 0x00000006e0000000)
eden space 419456K, 9% used [0x00000006c0000000, 0x00000006c2679e10, 0x00000006d99a0000)
from space 52416K, 36% used [0x00000006dccd0000, 0x00000006ddfa0218, 0x00000006e0000000)
to space 52416K, 0% used [0x00000006d99a0000, 0x00000006d99a0000, 0x00000006dccd0000)
concurrent mark-sweep generation total 3670016K, used 131775K [0x00000006e0000000, 0x00000007c0000000, 0x00000007c0000000)
Metaspace used 258955K, capacity 262086K, committed 262144K, reserved 1265664K
class space used 45934K, capacity 46565K, committed 46592K, reserved 1048576K
2019-04-02T17:22:31.606+0800: 1359.334: [Full GC (Metadata GC Threshold) 2019-04-02T17:22:31.606+0800: 1359.334: [Heap Dump (before full gc): , 2.0610454 secs]2019-04-02T17:22:33.667+0800: 1361.395: [CMS2019-04-02T17:22:33.812+0800: 1361.540: [CMS-concurrent-mark: 0.163/2.264 secs] [Times: user=2.46 sys=0.25, real=2.26 secs]
(concurrent mode failure): 131775K->104031K(3670016K), 0.7241683 secs]2019-04-02T17:22:34.391+0800: 1362.119: [Heap Dump (after full gc): , 1.9020867 secs] 190439K->104031K(4141888K), [Metaspace: 258955K->258955K(1265664K)], 4.6899705 secs] [Times: user=4.35 sys=0.49, real=4.69 secs]
Heap after GC invocations=2800 (full 2):
par new generation total 471872K, used 0K [0x00000006c0000000, 0x00000006e0000000, 0x00000006e0000000)
eden space 419456K, 0% used [0x00000006c0000000, 0x00000006c0000000, 0x00000006d99a0000)
from space 52416K, 0% used [0x00000006dccd0000, 0x00000006dccd0000, 0x00000006e0000000)
to space 52416K, 0% used [0x00000006d99a0000, 0x00000006d99a0000, 0x00000006dccd0000)
concurrent mark-sweep generation total 3670016K, used 104031K [0x00000006e0000000, 0x00000007c0000000, 0x00000007c0000000)
Metaspace used 258416K, capacity 260922K, committed 262144K, reserved 1265664K
class space used 45826K, capacity 46371K, committed 46592K, reserved 1048576K
}
問題
1、FullGC的時候 Metaspace 怎么現在變成了1.2G,設置的不是512M嘛?

[Metaspace: 258955K->258955K(1265664K)]
查看相關資料和官方文檔后,發現Metaspace還有一個區間是Klass Metaspace,由參數-XX:CompressedClassSpaceSize進行控制,參考你假笨,笨神的博客 JVM源碼分析之Metaspace解密 ;JDK8的時候 Klass Metaspace默認是1G。

2、Metaspace 空間在GC前后為什么根本沒有被垃圾回收?

[Metaspace: 258955K->258955K(1265664K)]
從上面的GC日志分析,我們看到了Full GC前后,Metaspace的使用變化是從258955K->258955K,說明GC根本沒有進行回收垃圾;

Metaspace主要是類的一些元數據信息,主要源於類加載器,可以使用 jstat -class pid 查看類的加載和寫在情況;

還可以使用 jmap -histo:live pid  查看ClassLoader對象比較多的類型;

Metaspace中類的元數據信息只有在加載它的ClassLoader被釋放后才會發生寫在,如果ClassLoader對象一直存活,那么它所加載的類的元數據信息將不會被卸載。

 

參考笨神的博客 JVM源碼分析之JDK8下的僵屍(無法回收)類加載器 ,摘取重要的信息如下:

類加載器創建過多,帶來的一個問題是,在類加載器第一次加載類的時候,會在Metaspace里會給它分配內存塊,為了分配高效,每個類加載器用來存放類信息的內存塊都是獨立的,所以哪怕你這個類加載器只加載一個類,也會為之分配一塊空的內存給這個類加載器,其實是至少兩個內存塊,於是你有可能會發現Metaspace的內存使用率非常低,但是committed的內存已經達到了閾值,從而觸發了Full GC,如果這種只加載很少類的類加載器非常多,那造成的后果就是很多碎片化的內存。

處理辦法
修改代碼使用反射完成對象之間屬性的copy,改為set方法進行實現,解決了該問題;

總結
如果項目中有大流量的調用接口,一定要警惕大流量帶來的大量反射類加載器創建引起的GC不回收問題。
————————————————
版權聲明:本文為CSDN博主「沿途風景21」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/a15939557197/article/details/90635460


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM