入口ByteBuffer.allocateDirect

    public static ByteBuffer allocateDirect(int capacity) {
return new DirectByteBuffer(capacity);
}

DirectByteBuffer构造函数

    DirectByteBuffer(int cap) {                   // package-private

        super(-1, 0, cap, cap);
boolean pa = VM.isDirectMemoryPageAligned();
int ps = Bits.pageSize();
long size = Math.max(1L, (long)cap + (pa ? ps : 0));
Bits.reserveMemory(size, cap); long base = 0;
try {
base = unsafe.allocateMemory(size);
} catch (OutOfMemoryError x) {
Bits.unreserveMemory(size, cap);
throw x;
}
unsafe.setMemory(base, size, (byte) 0);
if (pa && (base % ps != 0)) {
// Round up to page boundary
address = base + ps - (base & (ps - 1));
} else {
address = base;
}
cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
att = null; }

JDK中使用DirectByteBuffer对象来表示堆外内存,每个DirectByteBuffer对象在初始化时,都会创建一个对用的Cleaner对象,这个Cleaner对象会在合适的时候执行

unsafe.freeMemory(address),从而回收这块堆外内存。当初始化一块堆外内存时,对象的引用关系如下:

其中firstCleaner类的静态变量,Cleaner对象在初始化时会被添加到Clener链表中,和first形成引用关系,ReferenceQueue是用来保存需要回收的Cleaner对象。

如果该DirectByteBuffer对象在一次GC中被回收了

 

此时,只有Cleaner对象唯一保存了堆外内存的数据(开始地址、大小和容量),在下一次Full GC时,把该Cleaner对象放入到ReferenceQueue中,并触发clean方法。


1. 堆外内存的创建

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

堆外内存的限额默认与堆内内存(由-Xmx 设定)相仿,可用 -XX:MaxDirectMemorySize 重新设定。如果已经超限,会主动执行Sytem.gc(),期待能主动回收一点堆外内存。

然后休眠一百毫秒,看看totalCapacity降下来没有,如果内存还是不足,就抛出OOM异常。如果额度被批准,就调用大名鼎鼎的sun.misc.Unsafe去分配内存。

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

存在于堆内的DirectByteBuffer对象很小,只存着基地址和大小等几个属性,和一个Cleaner,但它代表着后面所分配的一大段内存,是所谓的冰山对象。

快速回顾一下堆内的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()就行。

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

4. Cleaner如何与GC相关联?

Cleaner就是PhantomReference的子类。

当GC时发现它除了PhantomReference外已不可达(持有Cleaner的DirectByteBuffer失效了),就会把它放进 Reference类pending list静态变量里。

然后另有一条ReferenceHandler线程,关注着这个pending list,如果看到有对象类型是Cleaner,就会执行它的clean(); 如果是其他类型就放入ReferenceQueue中,

这样应用的代码可以从Queue里拖出这些理论上已死的对象,这是一种比finalizer更轻量更好的机制。

参考:

Are Java DirectByteBuffer wrappers garbage collected?

占小狼 : DirectByteBuffer

江南白衣: 堆外内存


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堆外内存的使用

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

  7. Netty之Java堆外内存扫盲贴

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

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

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

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

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

随机推荐

  1. Git -- 忽略特殊文件

    有些时候,你必须把某些文件放到Git工作目录中,但又不能提交它们,比如保存了数据库密码的配置文件啦,等等,每次git status都会显示Untracked files ...,有强迫症的童鞋心里肯定 ...

  2. Win7/Win10多用户同时使用远程桌面

    Win7/Win10正常情况下是不允许多用户同时远程的,即一个用户远程进来会把另一个用户踢掉,需要破解. Win7:安装UniversalTermsrvPatch-x64.exe,见https://p ...

  3. Spring注解@Component、@Repository、@Service、@Controller @Resource、@Autowired、@Qualifier、@scope

    以下内容摘自部分网友的,并加上了自己的理解 @Service用于标注业务层组件(我们通常定义的service层就用这个) @Controller用于标注控制层组件(如struts中的action.Sp ...

  4. Google Colab 免费GPU服务器使用教程

    Google免费GPU使用教程(亲测可用)   今天突然看到一篇推文,里面讲解了如何薅资本主义羊毛,即如何免费使用Google免费提供的GPU使用权. 可以免费使用的方式就是通过Google Cola ...

  5. python使用requests发送multipart/form-data请求数据

    def client_post_mutipart_formdata_requests(request_url,requestdict): #功能说明:发送以多部分表单数据格式(它要求post的消息体分 ...

  6. /usr/bin/ld: cannot find -lncurses是咋回事?

    你的系統是32位的還是64位的? 如果是32位的就用:sudo apt-get install libncurses5-dev 如果是64位的,就用:sudo apt-get install lib3 ...

  7. 解决在antd中使用 autoprefixer 9.4.5 会抛出错误 Replace text-decoration-skip: ink to text-decoration-skip-ink: auto, because spec had been changed 的问题

    其实这个和antd的版本有关系,只需要把antd的版本升级到3.12.4就可以了 yarn add antd@ --save 记得重新运行一下项目

  8. connect()返回SOCKET_ERROR不一定就是连接失败

    connect()用于建立与指定socket的连接. 头文件: #include <sys/socket.h> 函数原型: int connect(int s, const struct ...

  9. Puppet软件资源管理

    1.实现的功能:     管理那些软件包被安装,那些软件包被卸载     管理软件包是否更新     要求系统配置yum源(RedHat系统).zypper源(Suse系统)等等 2.可用参数: en ...

  10. solus 系统 - 编译安裝 ibus-rime 中文输入法(附:小鹤双拼双形配置文件)

    編譯方法參見官網 - https://github.com/rime/home/wiki/RimeWithIBus 安装依赖:列出几个可能用到的命令 #安裝cmake gcc等开发工具 sudo eo ...