DirectByteBuffer 這個(gè)類(lèi)是 JDK 提供使用堆外內(nèi)存的一種途徑,當(dāng)然常見(jiàn)的業(yè)務(wù)開(kāi)發(fā)一般不會(huì)接觸到,即使涉及到也可能是框架(如 Netty、RPC 等)使用的,對(duì)框架使用者來(lái)說(shuō)也是透明的。
創(chuàng)新互聯(lián)建站是專業(yè)的泗水網(wǎng)站建設(shè)公司,泗水接單;提供網(wǎng)站建設(shè)、網(wǎng)站制作,網(wǎng)頁(yè)設(shè)計(jì),網(wǎng)站設(shè)計(jì),建網(wǎng)站,PHP網(wǎng)站建設(shè)等專業(yè)做網(wǎng)站服務(wù);采用PHP框架,可快速的進(jìn)行泗水網(wǎng)站開(kāi)發(fā)網(wǎng)頁(yè)制作和功能擴(kuò)展;專業(yè)做搜索引擎喜愛(ài)的網(wǎng)站,專業(yè)的做網(wǎng)站團(tuán)隊(duì),希望更多企業(yè)前來(lái)合作!堆外內(nèi)存優(yōu)勢(shì)在 IO 操作上,對(duì)于網(wǎng)絡(luò) IO,使用 Socket 發(fā)送數(shù)據(jù)時(shí),能夠節(jié)省堆內(nèi)存到堆外內(nèi)存的數(shù)據(jù)拷貝,所以性能更高。看過(guò) Netty 源碼的同學(xué)應(yīng)該了解,Netty 使用堆外內(nèi)存池來(lái)實(shí)現(xiàn)零拷貝技術(shù)。對(duì)于磁盤(pán) IO 時(shí),也可以使用內(nèi)存映射,來(lái)提升性能。另外,更重要的幾乎不用考慮堆內(nèi)存煩人的 GC 問(wèn)題。
我們直接來(lái)看代碼,首先向 Bits 類(lèi)申請(qǐng)額度,Bits 類(lèi)內(nèi)部維護(hù)著當(dāng)前已經(jīng)使用的堆外內(nèi)存值,會(huì) check 當(dāng)前申請(qǐng)的大小與已經(jīng)使用的內(nèi)存大小是否超過(guò)總的堆外內(nèi)存大?。J(rèn)大小與堆內(nèi)存差不多,其實(shí)是有細(xì)微區(qū)別的,拿 CMS GC 來(lái)舉例,它的大小是新生代的大值 - 一個(gè) survivor 的大小 + 老生代的大值),可以使用 -XX:MaxDirectMemorySize 參數(shù)指定堆外內(nèi)存大大小。
如果 check 不通過(guò),會(huì)主動(dòng)執(zhí)行 System.gc(),然后 sleep 100 毫秒,再進(jìn)行 check,如果內(nèi)存還是不足,就拋出 OOM Error。
如果 check 通過(guò),就會(huì)調(diào)用 unsafe.allocateMemory 真正分配內(nèi)存,返回內(nèi)存地址,然后再將內(nèi)存清 0。題外話,這個(gè) unsafe 命名看著是不是很?chē)樔?,這個(gè) unsafe 不是說(shuō)不安全,而是 JDK 內(nèi)部使用的類(lèi),不推薦外部使用,所以叫 unsafe,Netty 源碼內(nèi)部也有類(lèi)似命名。
由于申請(qǐng)內(nèi)存前可能會(huì)調(diào)用 System.gc(),所以謹(jǐn)慎設(shè)置 -XX:+DisableExplicitGC 這個(gè)選項(xiàng),這個(gè)參數(shù)作用是禁止代碼中顯示觸發(fā)的 Full GC。
cleaner = Cleaner.create(this, new Deallocator (base, size, cap));
看到這段代碼從成員的命名上就應(yīng)該知道,是用來(lái)回收堆外內(nèi)存的。確實(shí),但是它是如何工作的呢?接下來(lái)我們看看 Cleaner 類(lèi)。
Cleaner 類(lèi),內(nèi)部維護(hù)了一個(gè) Cleaner 對(duì)象的鏈表,通過(guò) create(Object, Runnable) 方法創(chuàng)建 cleaner 對(duì)象,調(diào)用自身的 add 方法,將其加入到鏈表中。更重要的是提供了 clean 方法,clean 方法首先將對(duì)象自身從鏈表中刪除,保證只調(diào)用一次,然后執(zhí)行 this.thunk 的 run 方法,thunk 就是由創(chuàng)建時(shí)傳入的 Runnable 參數(shù),也就是說(shuō) clean 只負(fù)責(zé)觸發(fā) Runnable 的 run 方法,至于 Runnable 做什么任務(wù)它不關(guān)心。
那 DirectByteBuffer 傳進(jìn)來(lái)的 Runnable 是什么呢?
Deallocator 類(lèi)的對(duì)象就是 DirectByteBuffer 中的 cleaner 傳進(jìn)來(lái)的 Runnable 參數(shù)類(lèi),我們直接看 run 方法 unsafe.freeMemory 釋放內(nèi)存,然后更新 Bits 里已使用的內(nèi)存數(shù)據(jù)。
接下來(lái)我們關(guān)注各個(gè)環(huán)節(jié)是如何串起來(lái)的?這里主要講兩種回收方式:一種是自動(dòng)回收,一種是手動(dòng)回收。
Java 是不用用戶去管理內(nèi)存的,所以 Java 對(duì)堆外內(nèi)存 默認(rèn)是自動(dòng)回收的。它是 由 GC 模塊負(fù)責(zé)的,在 GC 時(shí)會(huì)掃描 DirectByteBuffer 對(duì)象是否有有效引用指向該對(duì)象,如沒(méi)有,在回收 DirectByteBuffer 對(duì)象的同時(shí)且會(huì)回收其占用的堆外內(nèi)存。但是 JVM 如何釋放其占用的堆外內(nèi)存呢?如何跟 Cleaner 關(guān)聯(lián)起來(lái)呢?
這得從 Cleaner 繼承了 PhantomReference(虛引用) 說(shuō)起。說(shuō)到 Reference,還有 SoftReference、WeakReference、FinalReference 他們作用各不相同,這里就不展開(kāi)說(shuō)了。
簡(jiǎn)單介紹 PhantomReference,首先虛引用是不會(huì)影響 JVM 去回收其指向的對(duì)象,當(dāng) GC 某個(gè)對(duì)象時(shí),如果有此對(duì)象上還有虛引用對(duì)其引用,會(huì)將 PhantomReference 對(duì)象插入 ReferenceQueue 隊(duì)列。
PhantomReference插入到哪個(gè)隊(duì)列呢?看 PhantomReference 類(lèi)代碼,其繼承自 Reference,Reference 對(duì)象有個(gè) ReferenceQueue 成員,這個(gè)也就是 PhantomReference 對(duì)象插入的 ReferenceQueue 隊(duì)列,此成員如果不由外部傳入就是 ReferenceQueue.NULL。如果需要通過(guò) queue 拿到 PhantomReference 對(duì)象,這個(gè) ReferenceQueue 對(duì)象還是必須由外部傳入。
Reference 類(lèi)內(nèi)部 static 靜態(tài)塊會(huì)啟動(dòng) ReferenceHandler 線程,線程優(yōu)先級(jí)很高,這個(gè)線程是用來(lái)處理 JVM 在 GC 過(guò)程中交接過(guò)來(lái)的 reference。想必經(jīng)常用 jstack 命令,看線程堆棧的同學(xué)應(yīng)該見(jiàn)到過(guò)這個(gè)線程。
我們來(lái)看看 ReferenceHandler 是如何處理的?直接看 run 方法,首先是個(gè)死循環(huán),一直在那不停的干活,synchronized 塊內(nèi)的這段主要是交接 JVM 扔過(guò)來(lái)的 reference(就是 pending),再往下看,很明顯,調(diào)用了 cleaner 的 clean 方法。調(diào)完之后直接 continue 結(jié)束此次循環(huán),這個(gè) reference 并沒(méi)有進(jìn)入 queue,也就是說(shuō) Cleaner 虛引用是不放入 ReferenceQueue。
這塊有點(diǎn)想不通,既然不放入 ReferenceQueue,為什么 Cleaner 類(lèi)還是初始化了這個(gè) ReferenceQueue。
手動(dòng)回收,就是由開(kāi)發(fā)手動(dòng)調(diào)用 DirectByteBuffer 的 cleaner 的 clean 方法來(lái)釋放空間。由于 cleaner 是 private 反問(wèn)權(quán)限,所以自然想到使用反射來(lái)實(shí)現(xiàn)。
還有另一種方法,DirectByteBuffer 實(shí)現(xiàn)了 DirectBuffer 接口,這個(gè)接口有 cleaner 方法可以獲取 cleaner 對(duì)象。
Netty 中的堆外內(nèi)存池就是使用反射來(lái)實(shí)現(xiàn)手動(dòng)回收方式進(jìn)行回收的。
很多時(shí)候,我們?cè)趯W(xué)習(xí)一門(mén)技術(shù)時(shí)候往往限于眼前而很難突破,也就是通俗意義上的思維固化,這個(gè)時(shí)候你就需要用另一種思維來(lái)打破現(xiàn)狀。學(xué)習(xí)的目的在于擴(kuò)寬自己的視野、發(fā)散自己的思維,以更完備的視角去迎接人生的抉擇。
另外有需要云服務(wù)器可以了解下創(chuàng)新互聯(lián)scvps.cn,海內(nèi)外云服務(wù)器15元起步,三天無(wú)理由+7*72小時(shí)售后在線,公司持有idc許可證,提供“云服務(wù)器、裸金屬服務(wù)器、高防服務(wù)器、香港服務(wù)器、美國(guó)服務(wù)器、虛擬主機(jī)、免備案服務(wù)器”等云主機(jī)租用服務(wù)以及企業(yè)上云的綜合解決方案,具有“安全穩(wěn)定、簡(jiǎn)單易用、服務(wù)可用性高、性價(jià)比高”等特點(diǎn)與優(yōu)勢(shì),專為企業(yè)上云打造定制,能夠滿足用戶豐富、多元化的應(yīng)用場(chǎng)景需求。