内存很空却频繁gc_NonRegisteringDriver造成的内存频繁FullGc
某天上服務器看了下gc情況,發現狀況不對,啟動了才2天的服務器發生了360次fullgc,這個頻率肯定高了
說明
S0C、S1C、S0U、S1U:Survivor 0/1區容量(Capacity)和使用量(Used)
EC、EU:Eden區容量和使用量
OC、OU:年老代容量和使用量
PC、PU:永久代容量和使用量
YGC、YGT:年輕代GC次數和GC耗時
FGC、FGCT:Full GC次數和Full GC耗時
GCT:GC總耗時
jmap下載下內存看下
jmap -dump:format=b,file=文件名 [pid]
用eclipse的mat工具打開,很明顯的就能發現NonRegisteringDriver里的ConcurrentHashMap占用了大量內存
去NonRegisteringDriver的源碼看看,發現該類只有一個ConcurrentHashMap,也就是connectionPhantomRefs,里面存放了ConnectionImpl的虛引用
繼續往下找,發現在這個位置會放入該map
看下調用鏈路,猜測是獲取數據庫鏈接的時候丟了個jdbcConnection的虛引用進這個map
繼續找找看看這個存放了虛引用的map在哪會被使用,發現?AbandonedConnectionCleanupThread里起了個線程去遍歷虛引用的隊列,在虛引用被回收后會進入這個隊列,在這邊獲取后進行鏈接的cleanup操作
至此已經清楚ConnectionPhantomReference的作用就是在gc回收之后,能讓代碼從隊列里獲取實例再進行相關資源的回收
那么問題來了:
1:為什么會造成那么頻繁的fullgc
2:connectionPhantomRefs這個map的作用是什么
1.1:java的堆內存對象的ygc次數過多后將會進入老年代
一、對象何時進入老年代
(1)當對象首次創建時, 會放在新生代的eden區, 若沒有GC的介入,會一直在eden區, GC后,是可能進入survivor區或者年老代
(2)當對象年齡達到一定的大小 ,就會離開年輕代, 進入老年代。 而對象的年齡是由GC的次數決定的
-XX:MaxTenuringThreshold=n 新生代的對象最多經歷n次GC, 就能晉升到老年代, 但不是必要條件
-XX:TargetSurvivorRatio=n 用于設置Survivor區的目標使用率,即當survivor區GC后使用率超過這個值, 就可能會使用較小的年齡作為晉升年齡
(3)除年齡外, 對象體積也會影響對象的晉升的, 若對象體積太大, 新生代無法容納這個對象
-XX:PretenureSizeThreshold 即對象的大小大于此值, 就會繞過新生代, 直接在老年代分配, 此參數只對串行回收器以及ParNew回收有效, 而對ParallelGC回收器無效
1.2:代碼使用的為HikariCP連接池,而HikariCP有幾個參數
idleTimeout (連接空閑超時時間,默認 10分鐘)
maxLifetime(連接最大生存時間,默認 30分鐘)
maxPoolSize (連接池最大連接數)
minIdle(最小空閑連接數,默認等于 maxPoolSize)
可以看出當數據庫連接空閑時間超過了 idleTimeout,那么將會關閉,直到數量為 minIdle。還有當數據庫連接存活時間達到了 maxLifetime ,那么連接也會關閉,然后再創建新的連接,而每次創建新的連接都將重復上述步驟
1.3:而我的系統默認配置了500個連接數(minIdle=maxPoolSize=500),而查看阿里云的監控可以得知活躍的連接數基本就十幾個,導致jdbc連接瘋狂的在關閉打開,而在10分鐘之內經過多次ygc之后,ConnectionImpl都進入了老年代,而老年代的gc間隔很長,就導致connectionPhantomRefs里堆積了一大堆虛引用的對象,而觸發一次gc后會釋放
2:為了驗證這個map的作用,我建了個類
public classTest {
public static ReferenceQueue refQueue = new ReferenceQueue();
public static Map map = new HashMap<>();
public PhantomReferencephanRef;
public voidtest() throws InterruptedException {
Object obj = newObject();
phanRef = new PhantomReference(obj, refQueue);
// map.put(phanRef, phanRef);
}
}
然后在另外一個類里面執行以下方法
public static voidmain(String[] args) throws InterruptedException {
Test test = newTest();
test.test();
//PhantomReference s = drugTest.phanRef;
//System.out.println(s);
test = null;
System.gc();
System.gc();
System.gc();
System.out.println(test.refQueue.poll());
//System.out.println(s);
//s = null;
System.out.println(test.refQueue.poll());
}
當map.put(phanRef, phanRef);這行代碼被注釋時,輸出
null
null
當map.put(phanRef, phanRef);這行代碼存在時,輸出
nulljava.lang.ref.PhantomReference@4d591d15
當map.put(phanRef, phanRef);這行代碼存在時,并且取消PhantomReference s = drugTest.phanRef; s = null;的注釋時輸出
nulljava.lang.ref.PhantomReference@4d591d15
從這個實驗可以看出,當phanRef沒有被其他地方引用時,當他所屬的實例類被回收后他并不會進入到弱引用的隊列中(可能和gc機制有關吧)
解決方案:
1:調整HikariCP連接池的參數
2:調整jvm堆內存的參數
3:手動刪除connectionPhantomRefs這個ConcurrentHashMap中的數據
4:手動調system.gc(我覺得這個方法很扯淡,手動掉還不如讓jvm自己判斷調呢,反正不會內存溢出)
參考文章
總結
以上是生活随笔為你收集整理的内存很空却频繁gc_NonRegisteringDriver造成的内存频繁FullGc的全部內容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: mysql可以关联视图_Mysql 五:
- 下一篇: 在mysql中什么情况下不能指定字符集_
