JVM啟動(dòng)時(shí)分配的內(nèi)存赘来,稱(chēng)為堆內(nèi)存现喳,與之相對(duì)的,在代碼中還可以使用堆外內(nèi)存犬辰,比如Netty嗦篱,廣泛使用了堆外內(nèi)存,但是這部分的內(nèi)存并不歸JVM管理幌缝,GC算法并不會(huì)對(duì)它們進(jìn)行回收灸促,所以在使用堆外內(nèi)存時(shí),要格外小心涵卵,防止內(nèi)存一直得不到釋放浴栽,造成線(xiàn)上故障。
JDK的ByteBuffer類(lèi)提供了一個(gè)接口allocateDirect(int capacity)進(jìn)行堆外內(nèi)存的申請(qǐng)轿偎,底層通過(guò)unsafe.allocateMemory(size)實(shí)現(xiàn)典鸡,接下去看看在JVM層面是如何實(shí)現(xiàn)的。
可以發(fā)現(xiàn)坏晦,最底層是通過(guò)malloc方法申請(qǐng)的萝玷,但是這塊內(nèi)存需要進(jìn)行手動(dòng)釋放嫁乘,JVM并不會(huì)進(jìn)行回收,幸好Unsafe提供了另一個(gè)接口freeMemory可以對(duì)申請(qǐng)的堆外內(nèi)存進(jìn)行釋放球碉。
如果每次申請(qǐng)堆外內(nèi)存蜓斧,都需要在代碼中顯示的釋放,對(duì)于Java這門(mén)語(yǔ)言的設(shè)計(jì)來(lái)說(shuō)睁冬,顯然不夠合理挎春,既然JVM不會(huì)管理這些堆外內(nèi)存,它們是如何回收的呢豆拨?
JDK中使用DirectByteBuffer對(duì)象來(lái)表示堆外內(nèi)存搂蜓,每個(gè)DirectByteBuffer對(duì)象在初始化時(shí),都會(huì)創(chuàng)建一個(gè)對(duì)用的Cleaner對(duì)象辽装,這個(gè)Cleaner對(duì)象會(huì)在合適的時(shí)候執(zhí)行unsafe.freeMemory(address)帮碰,從而回收這塊堆外內(nèi)存。
當(dāng)初始化一塊堆外內(nèi)存時(shí)拾积,對(duì)象的引用關(guān)系如下:
其中first是Cleaner類(lèi)的靜態(tài)變量殉挽,Cleaner對(duì)象在初始化時(shí)會(huì)被添加到Clener鏈表中,和first形成引用關(guān)系拓巧,ReferenceQueue是用來(lái)保存需要回收的Cleaner對(duì)象斯碌。
如果該DirectByteBuffer對(duì)象在一次GC中被回收了
此時(shí),只有Cleaner對(duì)象唯一保存了堆外內(nèi)存的數(shù)據(jù)(開(kāi)始地址肛度、大小和容量)傻唾,在下一次FGC時(shí),把該Cleaner對(duì)象放入到ReferenceQueue中承耿,并觸發(fā)clean方法冠骄。
Cleaner對(duì)象的clean方法主要有兩個(gè)作用:
1、把自身從Clener鏈表刪除加袋,從而在下次GC時(shí)能夠被回收
2凛辣、釋放堆外內(nèi)存
如果JVM一直沒(méi)有執(zhí)行FGC的話(huà),無(wú)效的Cleaner對(duì)象就無(wú)法放入到ReferenceQueue中职烧,從而堆外內(nèi)存也一直得不到釋放扁誓,內(nèi)存豈不是會(huì)爆?
其實(shí)在初始化DirectByteBuffer對(duì)象時(shí)蚀之,如果當(dāng)前堆外內(nèi)存的條件很苛刻時(shí)蝗敢,會(huì)主動(dòng)調(diào)用System.gc()強(qiáng)制執(zhí)行FGC。
不過(guò)很多線(xiàn)上環(huán)境的JVM參數(shù)有-XX:+DisableExplicitGC足删,導(dǎo)致了System.gc()等于一個(gè)空函數(shù)寿谴,根本不會(huì)觸發(fā)FGC,這一點(diǎn)在使用Netty框架時(shí)需要注意是否會(huì)出問(wèn)題壹堰。
另外本人從事在線(xiàn)教育多年拭卿,將自己的資料整合建了一個(gè)QQ群,對(duì)于有興趣一起交流學(xué)習(xí)C/C++的可以加群:825414254贱纠,里面有大神會(huì)給予解答峻厚,也會(huì)有許多的資源可以供大家學(xué)習(xí)分享,歡迎大家前來(lái)一起學(xué)習(xí)進(jìn)步谆焊!