廣義的堆外記憶體
說到堆外記憶體,那大家肯定想到堆內記憶體,這也是我們大家接觸最多的,我們在jvm參數裡通常設定-Xmx來指定我們的堆的最大值,不過這還不是我們理解的Java堆,-Xmx的值是新生代和老生代的和的最大值,我們在jvm參數裡通常還會加一個參數-XX:MaxPermSize來指定持久代的最大值,那麼我們認識的Java堆的最大值其實是-Xmx和-XX:MaxPermSize的總和,在分代演算法下,新生代,老生代和持久代是連續的虛擬位址,因為它們是一起分配的,那麼剩下的都可以認為是堆外記憶體(廣義的)了,這些包括了jvm本身在運行過程中分配的記憶體,codecache,jni裡分配的記憶體,DirectByteBuffer分配的記憶體等等
狹義的堆外記憶體
而作為java開發人員,我們常說的堆外記憶體溢出了,其實是狹義的堆外記憶體,這個主要是指java.nio.DirectByteBuffer在建立的時候分配記憶體,我們這篇文章裡也主要是講狹義的堆外記憶體,因為它和我們平時碰到的問題比較密切
JDK/JVM裡DirectByteBuffer的實現
DirectByteBuffer通常用在通訊過程中做緩衝池,在mina,netty等nio架構中屢見不鮮
通過上面的代碼我們知道可以通過-XX:MaxDirectMemorySize來指定最大的堆外記憶體
DirectByteBuffer在建立的時候會通過Unsafe的native方法來直接使用malloc分配一塊記憶體,這塊記憶體是heap 之外的,那麼自然也不會對gc造成什麼影響(System.gc除外),因為gc耗時的操作主要是操作heap之內的對象,對這塊記憶體的操作也是直接通過 Unsafe的native方法來操作的,相當於DirectByteBuffer僅僅是一個殼,還有我們通訊過程中如果資料是在Heap裡的,最終也還是會copy一份到堆外,然後再進行發送,所以為什麼不直接使用堆外記憶體呢。對於需要頻繁操作的記憶體,並且僅僅是臨時存在一會的,都建議使用堆外記憶體,並且做成緩衝池,不斷迴圈利用這塊記憶體。
如果我們大面積使用堆外記憶體並且沒有限制,那遲早會導致記憶體溢出,畢竟程式是跑在一台資源受限的機器上,因為這塊記憶體的回收不是你直接能控制的