Java的堆外内存本来是高贵而神秘的东西,只在一些缓存方案的收费企业版里出现。但自从用了Netty,就变成了天天打交道的事情,毕竟堆外内存能减少IO时的内存复制,不需要堆内存Buffer拷贝一份到直接内存中,然后才写入Socket中;而且也没了烦人的GC。

好在,Netty所用的堆外内存只是Java NIO的 DirectByteBuffer类,通读一次很快。还有一些sun.misc.*的类木有源码,要自己跑去OpenJdk那看个明白。

1. 堆外内存的创建

在DirectByteBuffer中,首先向Bits类申请额度,Bits类有一个全局的 totalCapacity变量,记录着全部DirectByteBuffer的总大小,每次申请,都先看看是否超限 -- 堆外内存的限额默认与堆内内存(由-XMX 设定)相仿,可用 -XX:MaxDirectMemorySize 重新设定。

如果已经超限,会主动执行Sytem.gc(),期待能主动回收一点堆外内存。然后休眠一百毫秒,看看totalCapacity降下来没有,如果内存还是不足,就抛出大家最头痛的OOM异常。

如果额度被批准,就调用大名鼎鼎的sun.misc.Unsafe去分配内存,返回内存基地址,Unsafe的C++实现在此,标准的malloc。然后再调一次Unsafe把这段内存给清零。跑个题,Unsafe的名字是提醒大家这个类只给Sun自家用的,你们别用,不然哪天Sun把它藏起来了你们就哭死。果然,JDK9里就Oracle可能动手哦

JDK7开始,DirectByteBuffer分配内存时默认已不做分页对齐,不会再每次分配并清零 实际需要+分页大小(4k)的内存,这对性能应有较大提升,所以Oracle专门写在了Enhancements in Java I/O里。

最后,创建一个Cleaner,并把代表清理动作的Deallocator类绑定 -- 降低Bits里的totalCapacity,并调用Unsafe调free去释放内存。Cleaner的触发机制后面再说。

2. 堆外内存基于GC的回收

存在于堆内的DirectByteBuffer对象很小,只存着基地址和大小等几个属性,和一个Cleaner,但它代表着后面所分配的一大段内存,是所谓的冰山对象。通过前面说的Cleaner,堆内的DirectByteBuffer对象被GC时,它背后的堆外内存也会被回收。

快速回顾一下堆内的GC机制,当新生代满了,就会发生young gc;如果此时对象还没失效,就不会被回收;撑过几次young gc后,对象被迁移到老生代;当老生代也满了,就会发生full gc。

这里可以看到一种尴尬的情况,因为DirectByteBuffer本身的个头很小,只要熬过了young gc,即使已经失效了也能在老生代里舒服的呆着,不容易把老生代撑爆触发full gc,如果没有别的大块头进入老生代触发full gc,就一直在那耗着,占着一大片堆外内存不释放。

这时,就只能靠前面提到的申请额度超限时触发的system.gc()来救场了。但这道最后的保险其实也不很好,首先它会中断整个进程,然后它让当前线程睡了整整一百毫秒,而且如果gc没在一百毫秒内完成,它仍然会无情的抛出OOM异常。还有,万一,万一大家迷信某个调优指南设置了-DisableExplicitGC禁止了system.gc(),那就不好玩了。

所以,堆外内存还是自己主动点回收更好,比如Netty就是这么做的。

3. 堆外内存的主动回收

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

前面说的,clean()执行时实际调用的是被绑定的Deallocator类,这个类可被重复执行,释放过了就不再释放。所以GC时再被动执行一次clean()也没所谓。

在Netty里,因为不确定跑在Sun的JDK里(比如安卓),所以多废了些功夫来确定Cleaner的存在。

4. Cleaner如何与GC相关联?

涨知识的时间到了,原来JDK除了StrongReference,SoftReference 和 WeakReference之外,还有一种PhantomReference,Phantom是幻影的意思,Cleaner就是PhantomReference的子类。

当GC时发现它除了PhantomReference外已不可达(持有它的DirectByteBuffer失效了),就会把它放进 Reference类pending list静态变量里。然后另有一条ReferenceHandler线程,名字叫 "Reference Handler"的,关注着这个pending list,如果看到有对象类型是Cleaner,就会执行它的clean(),其他类型就放入应用构造Reference时传入的ReferenceQueue中,这样应用的代码可以从Queue里拖出这些理论上已死的对象,做爱做的事情——这是一种比finalizer更轻量更好的机制。

5. 其实

专家们说,OpenJDK没有接受jemalloc(redis们在用)的补丁,直接用malloc在OS里申请一段内存,比在已申请好的JVM堆内内存里划一块出来要慢,所以我们在Netty一般用池化的 PooledDirectByteBuf 对DirectByteBuffer进行重用 ,《Netty权威指南》说性能提升了23倍,所以基本不需要头痛堆外内存的释放,顺便还告别了大数据流量下的频繁GC。

文章持续修订,转载请保留原链接: http://calvin1978.blogcn.com/articles/directbytebuffer.html

Netty之Java堆外内存扫盲贴的更多相关文章

  1. google-perftools 分析JAVA 堆外内存

    google-perftools 分析JAVA 堆外内存 分类: j2se2011-08-25 21:48 3358人阅读 评论(4) 收藏 举报 javahbasehtml工具os 原文转自:htt ...

  2. Java堆外内存管理

    Java堆外内存管理   1.JVM可以使用的内存分外2种:堆内存和堆外内存: 堆内存完全由JVM负责分配和释放,如果程序没有缺陷代码导致内存泄露,那么就不会遇到java.lang.OutOfMemo ...

  3. Java堆外内存之二:堆外内存使用总结

    目录: <堆外内存操作类ByteBuffer> <DirectBuffer> <Unsafe(java可直接操作内存(),挂起与恢复,CAS操作)> 有时候对内存进 ...

  4. 实战经验 | Cassandra Java堆外内存排查经历全记录

    背景 最近准备上线cassandra这个产品,同事在做一些小规格ECS(8G)的压测.压测时候比较容易触发OOM Killer,把cassandra进程干掉.问题是8G这个规格我配置的heap(Xmx ...

  5. 超干货!Cassandra Java堆外内存排查经历全记录

    背景 最近准备上线cassandra这个产品,同事在做一些小规格ECS(8G)的压测.压测时候比较容易触发OOM Killer,把cassandra进程干掉.问题是8G这个规格我配置的heap(Xmx ...

  6. Java 堆外内存

    入口ByteBuffer.allocateDirect public static ByteBuffer allocateDirect(int capacity) { return new Direc ...

  7. Java堆外内存之突破JVM枷锁

    对于有Java开发经验的朋友都知道,Java中不需要手动的申请和释放内存,JVM会自动进行垃圾回收:而使用的内存是由JVM控制的. 那么,什么时机会进行垃圾回收,如何避免过度频繁的垃圾回收?如果JVM ...

  8. Java堆外内存的使用

    堆外内存的回收见HeapByteBuffer和DirectByteBuffer以及回收DirectByteBuffer 基本类型长度 在Java中有很多的基本类型,比如: byte,一个字节是8位bi ...

  9. Java堆外内存之五:堆外内存管理类ByteBuffer

    本篇主要讲解如何使用直接内存(堆外内存),并按照下面的步骤进行说明: 相关背景-->读写操作-->关键属性-->读写实践-->扩展-->参考说明 希望对想使用直接内存的朋 ...

随机推荐

  1. ACM: 限时训练题解-Rock-Paper-Scissors-前缀和

    Rock-Paper-Scissors   Rock-Paper-Scissors is a two-player game, where each player chooses one of Roc ...

  2. linux下使用线程锁互斥访问资源

    linux使用线程锁访问互斥资源: 1.线程锁的创建 pthread_mutex_t g_Mutex; 2.完整代码如下 #include <stdio.h> #include <s ...

  3. 使用Uboot启动内核并挂载NFS根文件系统

    配置编译好内核之后,将生成的内核文件uImage拷贝到/tftpboot/下,通过tftp服务器将内核下载到开发板,使用命令:tftp 31000000 uImage.下载完成之后配置bootargs ...

  4. 关于Nodejs的多进程模块Cluster

    关于Nodejs的多进程模块Cluster   前述 我们都知道nodejs最大的特点就是单进程.无阻塞运行,并且是异步事件驱动的.Nodejs的这些特性能够很好的解决一些问题,例如在服务器开发中,并 ...

  5. Nodejs操作redis

    //npm install redis //首先加载node_redis模块 var redis = require('redis'); // 创建redis连接 var client = redis ...

  6. [LintCode] Sort Integers 整数排序

    Given an integer array, sort it in ascending order. Use selection sort, bubble sort, insertion sort ...

  7. android-GridView控件的使用

    GridView 按行列方式显示多个组件(二维布局界面) 数据源(集合)-适配器(SimpleAdapter)-视图界面(GridView),加载适配器-配置监听器(OnItemClickListen ...

  8. QWeb、Widget继承

    对于Odoo前端来说,所有的js对象都是继承自openerp.web.Class这个类,然后由此派生出Widget,由Widget派生出其他诸如View等可视化部件,结合QWeb,我们可以实现对现有部 ...

  9. c# select标签绑定枚举,并以Description做Text显示

    今天在做项目时遇到一个问题: 开发中有些字段是枚举类型如 Dept 企业表中可能有个字段 Property 性质 0:事业单位,1:私企,2:外企,但有时我们不会单独为性质这个字段定义一张表, 而是在 ...

  10. 2016huasacm暑假集训训练三 C - Til the Cows Come Home

    题目链接:http://acm.hust.edu.cn/vjudge/contest/123674#problem/C N题目大意是有n个点,然后给出从a点到b点的距离,a和b是互相可以抵达的,则是无 ...