1 堆外內(nèi)存
JVM啟動(dòng)時(shí)分配的內(nèi)存,稱為堆內(nèi)存甘畅,與之相對(duì)的,在代碼中還可以使用堆外內(nèi)存往弓,不如Netty疏唾,廣泛使用了堆外內(nèi)存,但是這部分內(nèi)存不歸JVM管理函似,GC算法并不會(huì)對(duì)它們進(jìn)行回收槐脏,所以使用堆外內(nèi)存是需要格外小心,以防出現(xiàn)內(nèi)存泄露撇寞。
2 堆外內(nèi)存的申請(qǐng)和釋放
JDK中使用DirectByteBuffer對(duì)象來(lái)表示堆外內(nèi)存顿天,可以通過(guò)-XX:MaxDirectMemorySize來(lái)指定最大的堆外內(nèi)存,每個(gè)DirectByteBuffer對(duì)象在初始化時(shí)蔑担,都會(huì)創(chuàng)建一個(gè)對(duì)應(yīng)的Cleaner對(duì)象牌废,在Cleaner對(duì)象回收的時(shí)候回收這部分堆外內(nèi)存。初始化時(shí)引用關(guān)系如下:
其中first是Cleaner類的靜態(tài)變量钟沛,Cleaner對(duì)象在初始化時(shí)會(huì)被添加到Clener鏈表中畔规,和first形成引用關(guān)系,ReferenceQueue是用來(lái)保存需要回收的Cleaner對(duì)象恨统。
3 Cleaner如何與GC相關(guān)聯(lián)
JDK除了StrongReference、SoftReference和WeakReference之外三妈,還有一種PhantomReference是虛引用畜埋,Cleaner就是PhantomReference的子類。(針對(duì)這幾種引用畴蒲,后續(xù)專題講解)
當(dāng)GC時(shí)發(fā)現(xiàn)它除了PhantomReference外已不可達(dá)(持有它的DirectByteBuffer失效了)悠鞍,就會(huì)把它放進(jìn) Reference類pending list靜態(tài)變量里。然后另有一條ReferenceHandler線程模燥,名字叫 "Reference Handler"的咖祭,關(guān)注著這個(gè)pending list,如果看到有對(duì)象類型是Cleaner蔫骂,就會(huì)執(zhí)行它的clean()么翰,在最終的處理里會(huì)通過(guò)Unsafe的free接口來(lái)釋放DirectByteBuffer對(duì)應(yīng)的堆外內(nèi)存塊。
4 堆外內(nèi)存基于GC的回收
快速回顧一下堆內(nèi)的GC機(jī)制辽旋,當(dāng)新生代滿了浩嫌,就會(huì)發(fā)生young gc檐迟;如果此時(shí)對(duì)象還沒(méi)失效,就不會(huì)被回收码耐;撐過(guò)幾次young gc后追迟,對(duì)象被遷移到老生代;當(dāng)老生代也滿了骚腥,就會(huì)發(fā)生full gc敦间。
這里可以看到一種尷尬的情況,因?yàn)镈irectByteBuffer本身的個(gè)頭很小束铭,只要熬過(guò)了young gc廓块,即使已經(jīng)失效了也能在老生代里舒服的呆著,不容易把老生代撐爆觸發(fā)full gc纯露,如果沒(méi)有別的大塊頭進(jìn)入老生代觸發(fā)full gc剿骨,就一直在那耗著,占著一大片堆外內(nèi)存不釋放埠褪。
其實(shí)在初始化DirectByteBuffer對(duì)象時(shí)浓利,如果當(dāng)前堆外內(nèi)存的條件很苛刻時(shí),會(huì)主動(dòng)調(diào)用System.gc()強(qiáng)制執(zhí)行FGC钞速。
這時(shí)贷掖,就只能靠觸發(fā)system.gc()來(lái)救場(chǎng)了。如果還是無(wú)法釋放渴语,就可能會(huì)出現(xiàn)OOM苹威。
不過(guò)很多線上環(huán)境的JVM參數(shù)有-XX:+DisableExplicitGC,導(dǎo)致了System.gc()等于一個(gè)空函數(shù)驾凶,根本不會(huì)觸發(fā)FGC牙甫,這點(diǎn)需要特別關(guān)注。