- 直接記憶體申請空間其實是比較消耗性能,需要本地方法通過系統調用完成
- 直接記憶體在IO讀寫上的性能要優于堆記憶體,是以直接記憶體特别适合申請以後進行多次讀寫
- 堆外記憶體優勢在 IO 操作上,對于網絡 IO,使用 Socket 發送資料時,能夠節省堆記憶體到堆外記憶體的資料拷貝
- 零拷貝
- 複制很大的檔案
- 頻繁的IO操作,例如網絡并發場景
- 不受JVM堆的大小限制
- 會受到本機總記憶體(包括RAM及SWAP區或者分頁檔案)的大小及處理器尋址空間的限制,可能會抛出OutOfMemoryError異常
- 直接記憶體的最大大小可以通過-XX:MaxDirectMemorySize來設定,預設是64M
- ByteBuffer.allocateDirect()
- 由于申請記憶體前可能會調用 System.gc(),是以謹慎設定 -XX:+DisableExplicitGC 這個選項,這個參數作用是禁止代碼中顯示觸發的 Full GC
- 回收
- 自動回收
- GC 時會掃描 DirectByteBuffer 對象是否有有效引用指向該對象,如沒有,在回收 DirectByteBuffer 對象的同時且會回收其占用的堆外記憶體
- 虛引用(Phantom Reference)
- 也稱為幽靈引用或者幻影引用
- 一個對象是否有虛引用的存在,完全不會對其生存時間構成影響,也無法通過虛引用來取得一個對象執行個體。
- 為一個對象設定虛引用關聯的唯一目的就是能在這個對象被收集器回收時收到一個系統通知
- GC過程中如果發現某個對象除了隻有PhantomReference引用它之外,并沒有其他的地方引用它了,那将會把這個引用放到java.lang.ref.Reference.pending隊列裡,ReferenceHandler這個守護線程會處理pending隊列裡,執行一些後置處理,這裡是調用Cleaner的clean方法
- 而DirectByteBuffer構造方法内建立了一個Cleaner對象, Cleaner繼承了PhantomReference,其referent為DirectByteBuffer,也是通過Cleaner調用unsafe.freeMemory(address)來釋放直接記憶體
- 手動回收
- DirectByteBuffer 實作了 DirectBuffer 接口,這個接口有 cleaner 方法可以擷取 cleaner 對象。
public static void clean(final ByteBuffer byteBuffer) { if (byteBuffer.isDirect()) { ((DirectBuffer)byteBuffer).cleaner().clean(); } }
- Netty 中的堆外記憶體池就是使用反射來實作手動回收方式進行回收的。
- 當堆空間非常富餘,直接記憶體占用雖然很高,但并不會自發引起 GC——哪怕直接記憶體已經用滿。 如果不觸發 GC,直接記憶體可能就會溢出。是以,隻能人為去觸發 GC,進而回收直接記憶體。
- DirectByteBuffer 這個類是 JDK 提供使用堆外記憶體的一種途徑,當然常見的業務開發一般不會接觸到,即使涉及到也可能是架構(如 Netty、RPC 等)使用的,對架構使用者來說也是透明的