1. 程式人生 > >JVM堆外記憶體回收原理

JVM堆外記憶體回收原理

今天有一個重新整理我三觀的一個觀念,開拓了我的思維,批判了我的腦回路.
今天我說天龍裡面覺得蕭峰為什麼出門自帶BGM,為什麼那麼叼.他說蕭幫主算啥,最喜歡段譽,連妹妹都不放過.我說好歹麼啥血緣關係吧,畢竟段譽是段延慶的兒子嘛,隔開三代了.他說好歹還是有關係的,畢竟都是人族嘛….我一口咖啡噴顯示器上.

說一下JVM堆外記憶體的回收原理吧.Java有堆內記憶體和堆外記憶體,堆內記憶體有GC,堆外記憶體的話GC就愛莫能助了畢竟不在自己的地盤上.
堆外記憶體想比較堆內記憶體的優勢就在IO操作上,能夠節省堆內記憶體到堆外記憶體的複製操作,效能更高.這在netty中有比較好的體現(參考netty原始碼).而且磁碟IO操作可以使用記憶體對映,效能比較高,而且不用受制於堆內記憶體的GC.

來 看看DirectByteBuffer的建構函式
DirectByteBuffer的構造
Bits.reserveMemory(size , cap);
這個方法向Bits類申請記憶體額度,Bits類內部維護了目前已經使用的堆外記憶體的值,會查驗(Check)當前申請大小(預設大小==堆記憶體大小),可以使用-XX:MaxDirectMemorySize來設定.
如果查驗Check不通過,會主動執行System.gc(),然後100毫秒後會再次Check,如果還是記憶體不足,就會OOM.
如果Check通過呼叫unsafe.allocateMemory(size)來分配記憶體,返回記憶體地址,然後記憶體清空.
因為申請記憶體之前會有可能呼叫GC,所以在設定的時候需要小心一些選項,比如-XX:+DisableExplicitGC,這個引數是禁止程式碼中顯式呼叫GC.
clear = Clearner.create(this , new Deallocator(base , size , cap));


這是用來回收堆外記憶體的,工作機制需要看一下Clearner類原始碼.
Clearner維護了一個Clearner物件的連結串列,通過create(Object,Runnable)建立Clearner物件,呼叫自身的add方法,將其加入到連結串列中.Clearner的clean方法將其物件自身從連結串列中刪除,確保只能呼叫一次,然後執行this.thunk的run方法(thunk是由建立時傳入的Runnable引數,clean只是除法Runnable的run方法,具體執行什麼並不關心)

Deallocator類是DirectByteBuffer的靜態內部類,Deallocator類的物件就是DirectByteBuffer傳入的Runnable引數類
Deallocate 靜態內部類


其中run()中的unsafe.freeMemory(address);見名知意,就是釋放記憶體.然後Bits.unreserveMemory(size, capacity);對Bits裡使用的記憶體資料進行更新.

記憶體釋放和回收有自動回收和手動回收兩種.

自動回收,通過GC回收記憶體.GC在工作時會掃描DirectByteBuffer物件是否有影響到GC的引用,如果沒有則回收DirectByteBuffer物件的同時回收其佔用的堆外記憶體.但是堆外記憶體的回收並不歸GC負責.因為Clearner繼承了PhantomReference(虛引用,不影響JVM是否回收某個物件的判斷,當GC某個物件的時候如果這個物件存在虛引用,會將這個對C加入ReferenceQueue),而PhantomReference又繼承自Reference類,其中run方法
Reference的Run方法
此方法中有一個死迴圈,synchronized中接收JVM傳遞過來的reference(這裡的pending),而後呼叫了Clearner的clean(),而這個reference並沒有被放入佇列中,所以Clearner的許引用不放入ReferenceQueue.

手動回收,直接呼叫DirectByteBuffer的cleaner的clean方法來釋放記憶體.因為Clearner類是private的,所以需要通過反射的方式來呼叫.因為DirectByteBuffer實現DirectBuffer介面,介面中有clearner方法可以獲取到clearner物件,然後自己慢慢想咯 →_→.