1. 程式人生 > >Java堆外記憶體使用

Java堆外記憶體使用

JVM內部會把所有記憶體分成Java使用的堆記憶體和Native使用的記憶體,它們之間是不能共享的,就是說當你的Native記憶體用完了時,如果Java堆又有空閒記憶體,這時Native會重新向Jvm申請,而不是直接使用Java堆記憶體。

使用堆外記憶體,就是為了能直接分配和釋放記憶體,提高效率。JDK5.0之後,程式碼中能直接操作本地記憶體的方式有2種:使用未公開的Unsafe和NIO包下ByteBuffer。

使用ByteBuffer分配本地記憶體則非常簡單,直接ByteBuffer.allocateDirect(10 * 1024 * 1024)即可。

C語言的記憶體分配和釋放函式malloc/free,必須要一一對應,否則就會出現記憶體洩露或者是野指標的非法訪問。java中我們需要手動釋放獲取的堆外記憶體嗎?

我們一起來看看NIO中提供的ByteBuffer

我們將最大堆外記憶體設定成40M,執行這段程式碼會發現:程式可以一直執行下去,不會報OutOfMemoryError。如果使用了-verbose:gc -XX:+PrintGCDetails,會發現程式頻繁的進行垃圾回收活動。那麼DirectByteBuffer究竟是如何釋放堆外記憶體的?

我們修改下JVM的啟動引數,重新執行之前的程式碼:

 

與之前的JVM啟動引數相比,增加了-XX:+DisableExplicitGC,這個引數作用是禁止程式碼中顯示呼叫GC。程式碼如何顯示呼叫GC呢,通過System.gc()函式呼叫。如果加上了這個JVM啟動引數,那麼程式碼中呼叫System.gc()沒有任何效果,相當於是沒有這行程式碼一樣。

 

顯然堆記憶體(包括新生代和老年代)記憶體很充足,但是堆外記憶體溢位了。也就是說NIO直接記憶體的回收,需要依賴於System.gc()。如果我們的應用中使用了java nio中的direct memory,那麼使用-XX:+DisableExplicitGC一定要小心,存在潛在的記憶體洩露風險。

我們知道java程式碼無法強制JVM何時進行垃圾回收,也就是說垃圾回收這個動作的觸發,完全由JVM自己控制,它會挑選合適的時機回收堆記憶體中的無用java物件。程式碼中顯示呼叫System.gc(),只是建議JVM進行垃圾回收,但是到底會不會執行垃圾回收是不確定的,可能會進行垃圾回收,也可能不會。什麼時候才是合適的時機呢?一般來說是,系統比較空閒的時候(比如JVM中活動的執行緒很少的時候),還有就是記憶體不足,不得不進行垃圾回收。我們例子中的根本矛盾在於:堆記憶體由JVM自己管理,堆外記憶體必須要由我們自己釋放;堆記憶體的消耗速度遠遠小於堆外記憶體的消耗,但要命的是必須先釋放堆記憶體中的物件,才能釋放堆外記憶體,但是我們又不能強制JVM釋放堆記憶體。

Direct Memory的回收機制:Direct Memory是受GC控制的,例如ByteBuffer bb = ByteBuffer.allocateDirect(1024),這段程式碼的執行會在堆外佔用1k的記憶體,Java堆內只會佔用一個物件的指標引用的大小,堆外的這1k的空間只有當bb物件被回收時,才會被回收,這裡會發現一個明顯的不對稱現象,就是堆外可能佔用了很多,而堆內沒佔用多少,導致還沒觸發GC,那就很容易出現Direct Memory造成實體記憶體耗光。

Direct ByteBuffer分配出去的記憶體其實也是由GC負責回收的,而不像Unsafe是完全自行管理的,Hotspot在GC時會掃描Direct ByteBuffer物件是否有引用,如沒有則同時也會回收其佔用的堆外記憶體。

使用堆外記憶體與物件池都能減少GC的暫停時間,這是它們唯一的共同點。生命週期短的可變物件,建立開銷大,或者生命週期雖長但存在冗餘的可變物件都比較適合使用物件池。生命週期適中,或者複雜的物件則比較適合由GC來進行處理。然而,中長生命週期的可變物件就比較棘手了,堆外記憶體則正是它們的菜。

堆外記憶體的好處是:

(1)可以擴充套件至更大的記憶體空間。比如超過1TB甚至比主存還大的空間;

(2)理論上能減少GC暫停時間;

(3)可以在程序間共享,減少JVM間的物件複製,使得JVM的分割部署更容易實現;

(4)它的持久化儲存可以支援快速重啟,同時還能夠在測試環境中重現生產資料

站在系統設計的角度來看,使用堆外記憶體可以為你的設計提供更多可能。最重要的提升並不在於效能,而是決定性的

基於GC的回收

存在於堆內的DirectByteBuffer物件很小,只存著基地址和大小等幾個屬性,和一個Cleaner,但它代表著後面所分配的一大段記憶體,是所謂的冰山物件。通過前面說的Cleaner,堆內的DirectByteBuffer物件被GC時,它背後的堆外記憶體也會被回收。

關注微信公眾號和今日頭條,精彩文章持續更新中。。。。。