Java 堆外內(nèi)存的使用

更多 Java 虛擬機(jī)方面的文章滩褥,請參見文集《Java 虛擬機(jī)》


為什么需要使用堆外內(nèi)存

  • 將長期存活的對象(如 Local Cache )移入堆外內(nèi)存( off-heap衰伯,又名直接內(nèi)存 direct-memory)牧抵,從而減少 CMS 管理的對象數(shù)量, 以降低 Full GC 的次數(shù)和頻率帅刊,達(dá)到提高系統(tǒng)響應(yīng)速度的目的法挨。
  • 加快了復(fù)制的速度:堆內(nèi)在 flush 到遠(yuǎn)程時(shí)吱涉,會先復(fù)制到直接內(nèi)存,然后在發(fā)送捺疼;而堆外內(nèi)存相當(dāng)于省略掉了這個(gè)工作疏虫。

堆外內(nèi)存不是 JVM 運(yùn)行時(shí)數(shù)據(jù)區(qū) Runtime Data Area 的一部分,這部分內(nèi)存區(qū)域直接被操作系統(tǒng)管理啤呼,JVM 通過 JNI 本地接口操作堆外內(nèi)存卧秘。

堆外內(nèi)存的使用

在 JDK 1.4以前,對這部分內(nèi)存訪問沒有光明正大的做法:只能通過反射拿到 Unsafe 類官扣,然后調(diào)用allocateMemory()/freeMemory()來申請/釋放這塊內(nèi)存翅敌。
1.4 開始新加入了 NIO,它引入了一種基于 Channel 與 Buffer 的 I/O 方式醇锚,可以使用 Native 函數(shù)庫直接分配堆外內(nèi)存哼御,然后通過一個(gè)存儲在 Java 堆里面的 DirectByteBuffer 對象作為這塊內(nèi)存的引用進(jìn)行操作坯临,ByteBuffer 提供了如下常用方法來跟堆外內(nèi)存打交道:

  • public static ByteBuffer allocateDirect(int capacity)
    • 分配堆外內(nèi)存,返回一個(gè) DirectByteBuffer 堆外內(nèi)存對象 return new DirectByteBuffer(capacity);
  • public abstract ByteBuffer put(byte b);
    • 向堆外內(nèi)存中存放一個(gè)字節(jié)
  • public abstract byte get();
    • 從堆外內(nèi)存中讀取一個(gè)字節(jié)
  • public final ByteBuffer put(byte[] src)
    • 向堆外內(nèi)存中存放一個(gè)字節(jié)數(shù)組
  • public ByteBuffer get(byte[] dst)
    • 從堆外內(nèi)存中讀取一個(gè)字節(jié)數(shù)組
  • public abstract ByteBuffer putInt(int value);
    • 向堆外內(nèi)存中存放一個(gè) int
  • public abstract int getInt();
    • 從堆外內(nèi)存中讀取一個(gè) int
  • public abstract IntBuffer asIntBuffer()
    • 轉(zhuǎn)換為一個(gè) IntBuffer
  • public abstract ByteBuffer putLong(long value); 同上恋昼,以此類推
  • public abstract boolean isDirect();
    • 判斷是否為堆外內(nèi)存

ByteBuffer 包含了如下的幾個(gè)屬性:

  • private int mark = -1;:標(biāo)記位置看靠,記錄當(dāng)前 position 的值
  • private int position = 0;:當(dāng)前位置
  • private int limit;:限制大小
  • private int capacity;:空間容量
  • 基本關(guān)系 mark <= position <= limit <= capacity

示例如下:

public static void main(String[] args) {
    ByteBuffer bb = ByteBuffer.allocateDirect(1024);
    bb.putChar('A');
    bb.putInt(123);

    System.out.println("capacity: " + bb.capacity());
    System.out.println("limit: " + bb.limit());
    System.out.println("position: " + bb.position());

    bb.position(0);
    System.out.println(bb.getChar());
    System.out.println(bb.getInt());
}

輸出:

capacity: 1024
limit: 1024
position: 6
A
123

堆外內(nèi)存的設(shè)置

堆外內(nèi)存的限額默認(rèn)與堆內(nèi)內(nèi)存(由-XMX 設(shè)定)相仿,可用 -XX:MaxDirectMemorySize 重新設(shè)定液肌。
當(dāng)使用達(dá)到了閾值的時(shí)候?qū)⒄{(diào)用 System.gc 來做一次 Full GC挟炬,以此來回收掉沒有被使用的堆外內(nèi)存。

堆外內(nèi)存的分配

DirectByteBuffer 中嗦哆,首先向 Bits 類申請額度谤祖,Bits 類有一個(gè)全局的 totalCapacity 變量,記錄著全部 DirectByteBuffer 的總大小老速,每次申請粥喜,都先看看是否超限:

  • 如果已經(jīng)超限,會主動(dòng)執(zhí)行 Sytem.gc()橘券,期待能主動(dòng)回收一點(diǎn)堆外內(nèi)存额湘。然后休眠一百毫秒,看看 totalCapacity 降下來沒有旁舰,如果內(nèi)存還是不足锋华,就拋出大家最頭痛的 OOM 異常。
  • 如果額度被批準(zhǔn)箭窜,就調(diào)用大名鼎鼎的 sun.misc.Unsafe 去分配內(nèi)存毯焕,返回內(nèi)存基地址,UnsafeC++實(shí)現(xiàn)在此磺樱,標(biāo)準(zhǔn)的 malloc纳猫。然后再調(diào)一次 Unsafe 把這段內(nèi)存給清零。

堆外內(nèi)存的回收

堆外內(nèi)存基于 GC 的回收

存在于堆內(nèi)的 DirectByteBuffer 對象很小竹捉,只存著基地址和大小等幾個(gè)屬性续担,和一個(gè) Cleaner,但它代表著后面所分配的一大段內(nèi)存活孩,是所謂的冰山對象。
通過前面說的 Cleaner乖仇,堆內(nèi)的 DirectByteBuffer 對象被 GC 時(shí)憾儒,它背后的堆外內(nèi)存也會被回收。
這里可以看到一種尷尬的情況乃沙,因?yàn)?DirectByteBuffer 本身的個(gè)頭很小起趾,只要熬過了 Young GC,即使已經(jīng)失效了也能在老生代里舒服的呆著警儒,不容易把老生代撐爆觸發(fā) Full GC训裆,如果沒有別的大塊頭進(jìn)入老生代觸發(fā)Full GC眶根,就一直在那耗著,占著一大片堆外內(nèi)存不釋放边琉。
這時(shí)属百,就只能靠前面提到的申請額度超限時(shí)觸發(fā)的 System.gc()來救場了。

堆外內(nèi)存的主動(dòng)回收

對于 Sun 的 JDK 這其實(shí)很簡單变姨,只要從 DirectByteBuffer 里取出那個(gè) sun.misc.Cleaner族扰,然后調(diào)用它的 clean() 就行。
例如:
((DirectBuffer)bb).cleaner().clean();


引用:
JVM初探——使用堆外內(nèi)存減少Full GC
Netty之Java堆外內(nèi)存掃盲貼
從0到1起步-跟我進(jìn)入堆外內(nèi)存的奇妙世界

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末定欧,一起剝皮案震驚了整個(gè)濱河市渔呵,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌砍鸠,老刑警劉巖扩氢,帶你破解...
    沈念sama閱讀 221,695評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異爷辱,居然都是意外死亡录豺,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評論 3 399
  • 文/潘曉璐 我一進(jìn)店門托嚣,熙熙樓的掌柜王于貴愁眉苦臉地迎上來巩检,“玉大人,你說我怎么就攤上這事示启【た蓿” “怎么了?”我有些...
    開封第一講書人閱讀 168,130評論 0 360
  • 文/不壞的土叔 我叫張陵夫嗓,是天一觀的道長迟螺。 經(jīng)常有香客問我,道長舍咖,這世上最難降的妖魔是什么矩父? 我笑而不...
    開封第一講書人閱讀 59,648評論 1 297
  • 正文 為了忘掉前任,我火速辦了婚禮排霉,結(jié)果婚禮上窍株,老公的妹妹穿的比我還像新娘。我一直安慰自己攻柠,他們只是感情好球订,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,655評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著瑰钮,像睡著了一般冒滩。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上浪谴,一...
    開封第一講書人閱讀 52,268評論 1 309
  • 那天开睡,我揣著相機(jī)與錄音因苹,去河邊找鬼。 笑死篇恒,一個(gè)胖子當(dāng)著我的面吹牛扶檐,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播婚度,決...
    沈念sama閱讀 40,835評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼蘸秘,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了蝗茁?” 一聲冷哼從身側(cè)響起醋虏,我...
    開封第一講書人閱讀 39,740評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎哮翘,沒想到半個(gè)月后颈嚼,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,286評論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡饭寺,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,375評論 3 340
  • 正文 我和宋清朗相戀三年阻课,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片艰匙。...
    茶點(diǎn)故事閱讀 40,505評論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡限煞,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出员凝,到底是詐尸還是另有隱情署驻,我是刑警寧澤,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布健霹,位于F島的核電站旺上,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏糖埋。R本人自食惡果不足惜宣吱,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,873評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望瞳别。 院中可真熱鬧征候,春花似錦、人聲如沸祟敛。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,357評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽垒棋。三九已至,卻和暖如春痪宰,著一層夾襖步出監(jiān)牢的瞬間叼架,已是汗流浹背畔裕。 一陣腳步聲響...
    開封第一講書人閱讀 33,466評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留乖订,地道東北人扮饶。 一個(gè)月前我還...
    沈念sama閱讀 48,921評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像乍构,于是被迫代替她去往敵國和親甜无。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,515評論 2 359

推薦閱讀更多精彩內(nèi)容

  • 堆外內(nèi)存, JDK 1.4 nio引進(jìn)了ByteBuffer.allocateDirect()分配堆外內(nèi)存 Byt...
    andersonoy閱讀 2,070評論 0 1
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法哥遮,類相關(guān)的語法岂丘,內(nèi)部類的語法,繼承相關(guān)的語法眠饮,異常的語法奥帘,線程的語...
    子非魚_t_閱讀 31,664評論 18 399
  • 堆外內(nèi)存 堆外內(nèi)存是相對于堆內(nèi)內(nèi)存的一個(gè)概念。堆內(nèi)內(nèi)存是由JVM所管控的Java進(jìn)程內(nèi)存仪召,我們平時(shí)在Java中創(chuàng)建...
    tomas家的小撥浪鼓閱讀 40,892評論 19 71
  • 占小狼轉(zhuǎn)載請注明原創(chuàng)出處寨蹋,謝謝! 堆外內(nèi)存 JVM啟動(dòng)時(shí)分配的內(nèi)存扔茅,稱為堆內(nèi)存已旧,與之相對的,在代碼中還可以使用堆外...
    美團(tuán)Java閱讀 26,988評論 15 60
  • 從三月份找實(shí)習(xí)到現(xiàn)在召娜,面了一些公司运褪,掛了不少,但最終還是拿到小米萤晴、百度吐句、阿里、京東店读、新浪嗦枢、CVTE、樂視家的研發(fā)崗...
    時(shí)芥藍(lán)閱讀 42,275評論 11 349