使用Java語言的同窗們都知道, Java的虛擬機對內存的管理大部分狀況下就是指堆內存的管理, GC的也是對堆內存的清理和回收. 下面就看一下堆外內存的對JVM的意義.java
第一次瞭解到堆外內存的使用場景是在使用netty, netty中提到的一個概念, "零拷貝", 也是netty高性能的緣由之一. 零拷貝, 主要體如今三個方面:緩存
在使用堆外內存的同時也帶來了新的問題, 相比較堆內存, 堆外內存的分配和回收要更耗時, 因此netty提供了基於內存池的緩衝區重用機制.服務器
將本地緩存和堆外內存聯繫到一塊兒, 是有一次調試線上頻繁FULL GC而後OOM的問題, 當時的狀況是, 線上頻繁的報警有FULLGC, 懷疑有內存泄露,, 經過dump堆內存快照, 分析後發現有一個特別大的HashMap, 緣由是打點日誌引發的, 應用默認集成了日誌系統會自動記錄用戶的全部行爲, 應用這邊合併日誌後發送到日誌接收端, 日誌接收端掛掉了, 致使一直在重試, 重試的過程當中不停的有新的日誌加進來, 最後致使FULLGC. 當時我就在想, 若是將這種本地緩存移到堆外是否是就能夠不用參與GC, 也可使用更大的內存.性能
堆外內存有如下特色:線程
堆外內存更適合生命週期中等或長期的對象調試
關於堆外內存的回收
堆外內存的回收其實依賴於咱們的GC機制(堆外內存不會對GC形成什麼影響)
首先咱們要知道在java層面和咱們在堆外分配的這塊內存關聯的只有與之關聯的DirectByteBuffer對象了,它記錄了這塊內存的基地址以及大小,那麼既然和GC也有關,那就是GC能經過操做DirectByteBuffer對象來間接操做對應的堆外內存了。
DirectByteBuffer對象在建立的時候關聯了一個PhantomReference,說到PhantomReference它其實主要是用來跟蹤對象什麼時候被回收的,它不能影響GC決策.
GC過程當中若是發現某個對象除了只有PhantomReference引用它以外,並無其餘的地方引用它了,那將會把這個引用放到java.lang.ref.Reference.pending隊列裏,在GC完畢的時候通知ReferenceHandler這個守護線程去執行一些後置處理, 而DirectByteBuffer關聯的PhantomReference是PhantomReference的一個子類,在最終的處理裏會經過Unsafe的free接口來釋放DirectByteBuffer對應的堆外內存塊日誌
什麼是基地址 這裏就提出了段的概念, 將1G的數據劃分爲n個段, 每個段是64K, 每個段也就是每個64K就是一個基地址 段內的數據的地址就是當前基地址的偏移地址, 此時 段地址+偏移地址就可以找到真正的內存數據了.netty
爲何要主動調用System.gc
System.gc()會對新生代的老生代都會進行內存回收,這樣會比較完全地回收DirectByteBuffer對象以及他們關聯的堆外內存.
DirectByteBuffer對象自己實際上是很小的,可是它後面可能關聯了一個很是大的堆外內存,所以咱們一般稱之爲**冰山對象*.
咱們作ygc的時候會將新生代裏的不可達的DirectByteBuffer對象及其堆外內存回收了,可是沒法對old裏的DirectByteBuffer對象及其堆外內存進行回收,這也是咱們一般碰到的最大的問題.
若是有大量的DirectByteBuffer對象移到了old,可是又一直沒有作cms gc或者full gc,而只進行ygc,那麼咱們的物理內存可能被慢慢耗光,可是咱們還不知道發生了什麼,由於heap明明剩餘的內存還不少(前提是咱們禁用了System.gc -- JVM參數DisableExplicitGC)。code
JVM GC-Invisible Heap
淘寶基於OpenJDK HotSpot VM,定製且開源的服務器版Java虛擬機. GC-Invisible Heap,簡稱GCIH,是一種將Java對象從Java堆內移動到堆外,而且能夠在JVM間共享這些對象的技術。對象
如何從JVM移入堆外內存
遍歷全部對象, 從根對象開始, 根據對象的引用關係遞歸調用moveIn(將該對象在GCIH的地址encode到刻對象的header上, 並設置header的最低2位爲01).
如何阻止GC掃描 正常的GC過程是, 從root對象出發, 標記活的對象的同時, 將這個對象push到queue, 以便後續的GC線程處理 GCIH更改了GC過程, 當某個對象的地址在GCIH地址空間內時, 直接返回, 不將對象放到queue中