Java 堆外内存的使用

更多 Java 虚拟机方面的文章,请参见文集《Java 虚拟机》


为什么需要使用堆外内存

  • 将长期存活的对象(如 Local Cache )移入堆外内存( off-heap,又名直接内存 direct-memory),从而减少 CMS 管理的对象数量, 以降低 Full GC 的次数和频率,达到提高系统响应速度的目的。
  • 加快了复制的速度:堆内在 flush 到远程时,会先复制到直接内存,然后在发送;而堆外内存相当于省略掉了这个工作。

堆外内存不是 JVM 运行时数据区 Runtime Data Area 的一部分,这部分内存区域直接被操作系统管理,JVM 通过 JNI 本地接口操作堆外内存。

堆外内存的使用

在 JDK 1.4以前,对这部分内存访问没有光明正大的做法:只能通过反射拿到 Unsafe 类,然后调用allocateMemory()/freeMemory()来申请/释放这块内存。
1.4 开始新加入了 NIO,它引入了一种基于 Channel 与 Buffer 的 I/O 方式,可以使用 Native 函数库直接分配堆外内存,然后通过一个存储在 Java 堆里面的 DirectByteBuffer 对象作为这块内存的引用进行操作,ByteBuffer 提供了如下常用方法来跟堆外内存打交道:

  • public static ByteBuffer allocateDirect(int capacity)
    • 分配堆外内存,返回一个 DirectByteBuffer 堆外内存对象 return new DirectByteBuffer(capacity);
  • public abstract ByteBuffer put(byte b);
    • 向堆外内存中存放一个字节
  • public abstract byte get();
    • 从堆外内存中读取一个字节
  • public final ByteBuffer put(byte[] src)
    • 向堆外内存中存放一个字节数组
  • public ByteBuffer get(byte[] dst)
    • 从堆外内存中读取一个字节数组
  • public abstract ByteBuffer putInt(int value);
    • 向堆外内存中存放一个 int
  • public abstract int getInt();
    • 从堆外内存中读取一个 int
  • public abstract IntBuffer asIntBuffer()
    • 转换为一个 IntBuffer
  • public abstract ByteBuffer putLong(long value); 同上,以此类推
  • public abstract boolean isDirect();
    • 判断是否为堆外内存

ByteBuffer 包含了如下的几个属性:

  • private int mark = -1;:标记位置,记录当前 position 的值
  • private int position = 0;:当前位置
  • private int limit;:限制大小
  • private int capacity;:空间容量
  • 基本关系 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

堆外内存的设置

堆外内存的限额默认与堆内内存(由-XMX 设定)相仿,可用 -XX:MaxDirectMemorySize 重新设定。
当使用达到了阈值的时候将调用 System.gc 来做一次 Full GC,以此来回收掉没有被使用的堆外内存。

堆外内存的分配

DirectByteBuffer 中,首先向 Bits 类申请额度,Bits 类有一个全局的 totalCapacity 变量,记录着全部 DirectByteBuffer 的总大小,每次申请,都先看看是否超限:

  • 如果已经超限,会主动执行 Sytem.gc(),期待能主动回收一点堆外内存。然后休眠一百毫秒,看看 totalCapacity 降下来没有,如果内存还是不足,就抛出大家最头痛的 OOM 异常。
  • 如果额度被批准,就调用大名鼎鼎的 sun.misc.Unsafe 去分配内存,返回内存基地址,UnsafeC++实现在此,标准的 malloc。然后再调一次 Unsafe 把这段内存给清零。

堆外内存的回收

堆外内存基于 GC 的回收

存在于堆内的 DirectByteBuffer 对象很小,只存着基地址和大小等几个属性,和一个 Cleaner,但它代表着后面所分配的一大段内存,是所谓的冰山对象。
通过前面说的 Cleaner,堆内的 DirectByteBuffer 对象被 GC 时,它背后的堆外内存也会被回收。
这里可以看到一种尴尬的情况,因为 DirectByteBuffer 本身的个头很小,只要熬过了 Young GC,即使已经失效了也能在老生代里舒服的呆着,不容易把老生代撑爆触发 Full GC,如果没有别的大块头进入老生代触发Full GC,就一直在那耗着,占着一大片堆外内存不释放。
这时,就只能靠前面提到的申请额度超限时触发的 System.gc()来救场了。

堆外内存的主动回收

对于 Sun 的 JDK 这其实很简单,只要从 DirectByteBuffer 里取出那个 sun.misc.Cleaner,然后调用它的 clean() 就行。
例如:
((DirectBuffer)bb).cleaner().clean();


引用:
JVM初探——使用堆外内存减少Full GC
Netty之Java堆外内存扫盲贴
从0到1起步-跟我进入堆外内存的奇妙世界

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,565评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,021评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,003评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,015评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,020评论 5 370
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,856评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,178评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,824评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,264评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,788评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,913评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,535评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,130评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,102评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,334评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,298评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,622评论 2 343

推荐阅读更多精彩内容

  • 堆外内存, JDK 1.4 nio引进了ByteBuffer.allocateDirect()分配堆外内存 Byt...
    andersonoy阅读 2,050评论 0 1
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,579评论 18 399
  • 堆外内存 堆外内存是相对于堆内内存的一个概念。堆内内存是由JVM所管控的Java进程内存,我们平时在Java中创建...
    tomas家的小拨浪鼓阅读 40,412评论 19 71
  • 占小狼转载请注明原创出处,谢谢! 堆外内存 JVM启动时分配的内存,称为堆内存,与之相对的,在代码中还可以使用堆外...
    美团Java阅读 26,889评论 15 60
  • 从三月份找实习到现在,面了一些公司,挂了不少,但最终还是拿到小米、百度、阿里、京东、新浪、CVTE、乐视家的研发岗...
    时芥蓝阅读 42,184评论 11 349