Java 堆外内存
入口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)
,从而回收这块堆外内存。当初始化一块堆外内存时,对象的引用关系如下:
其中first
是Cleaner
类的静态变量,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 堆外内存的更多相关文章
- google-perftools 分析JAVA 堆外内存
google-perftools 分析JAVA 堆外内存 分类: j2se2011-08-25 21:48 3358人阅读 评论(4) 收藏 举报 javahbasehtml工具os 原文转自:htt ...
- Java堆外内存管理
Java堆外内存管理 1.JVM可以使用的内存分外2种:堆内存和堆外内存: 堆内存完全由JVM负责分配和释放,如果程序没有缺陷代码导致内存泄露,那么就不会遇到java.lang.OutOfMemo ...
- Java堆外内存之二:堆外内存使用总结
目录: <堆外内存操作类ByteBuffer> <DirectBuffer> <Unsafe(java可直接操作内存(),挂起与恢复,CAS操作)> 有时候对内存进 ...
- 实战经验 | Cassandra Java堆外内存排查经历全记录
背景 最近准备上线cassandra这个产品,同事在做一些小规格ECS(8G)的压测.压测时候比较容易触发OOM Killer,把cassandra进程干掉.问题是8G这个规格我配置的heap(Xmx ...
- 超干货!Cassandra Java堆外内存排查经历全记录
背景 最近准备上线cassandra这个产品,同事在做一些小规格ECS(8G)的压测.压测时候比较容易触发OOM Killer,把cassandra进程干掉.问题是8G这个规格我配置的heap(Xmx ...
- Java堆外内存的使用
堆外内存的回收见HeapByteBuffer和DirectByteBuffer以及回收DirectByteBuffer 基本类型长度 在Java中有很多的基本类型,比如: byte,一个字节是8位bi ...
- Netty之Java堆外内存扫盲贴
Java的堆外内存本来是高贵而神秘的东西,只在一些缓存方案的收费企业版里出现.但自从用了Netty,就变成了天天打交道的事情,毕竟堆外内存能减少IO时的内存复制,不需要堆内存Buffer拷贝一份到直接 ...
- Java堆外内存之突破JVM枷锁
对于有Java开发经验的朋友都知道,Java中不需要手动的申请和释放内存,JVM会自动进行垃圾回收:而使用的内存是由JVM控制的. 那么,什么时机会进行垃圾回收,如何避免过度频繁的垃圾回收?如果JVM ...
- Java堆外内存之五:堆外内存管理类ByteBuffer
本篇主要讲解如何使用直接内存(堆外内存),并按照下面的步骤进行说明: 相关背景-->读写操作-->关键属性-->读写实践-->扩展-->参考说明 希望对想使用直接内存的朋 ...
随机推荐
- (原)关于i++和++i的小程序测试
今天看到一个程序,于是用vs运行了一下,结果出乎我的意料: 代码: ; +(i++))+(+(i++)); i=; +(++i))+(+(++i)); i=; printf("x=%d,y= ...
- Docker孵化的5个开源项目
版权声明:本文为博主原创文章.未经博主同意不得转载. https://blog.csdn.net/M2l0ZgSsVc7r69eFdTj/article/details/81977243 回想过去短短 ...
- Ubuntu16.04搭建Postfix作为SMTP服务器
一.DNS配置 类型 名称 值 TTL A mail 128.199.254.32 1小时 MX @ mail.example.com(优先:10) 1小时 TXT @ v=spf ...
- golang获取命令行参数
部署golang项目时难免要通过命令行来设置一些参数,那么在golang中如何操作命令行参数呢?可以使用os库和flag库. 1.golang os库获取命令行参数 os可以通过变量Args来获取命令 ...
- QT QML 3D模型查看器
原文链接:http://amin-ahmadi.com/2018/01/28/viewing-3d-models-using-qt/ 本文使用QT Quick中的Scene3D QML类型来查看3D模 ...
- [Tensorflow] Cookbook - The Tensorflow Way
本章介绍tf基础知识,主要包括cookbook的第一.二章节. 方针:先会用,后定制 Ref: TensorFlow 如何入门? Ref: 如何高效的学习 TensorFlow 代码? 顺便推荐该领域 ...
- nginx-启动|关闭|重新加载配置文件的命令
1.1 进入操作目录 D: cd D:\NginxTest\nginx-1.10.2 1.2 启动指令 nginx -c conf\nginx.conf 1.3 关闭指令 nginx -s stop ...
- Spark排序之SortBy
sortBy函数源码:接收三个参数,第一个参数必须,第二个和第三个参数非必要 def sortBy[K]( f: (T) => K, ascending: Boolean = true, num ...
- 如何用Baas快速在腾讯云上开发小程序-系列4:实现客户侧商品列表、商品详情页程序
版权声明:本文由贺嘉 原创文章,转载请注明出处: 文章原文链接:https://www.qcloud.com/community/article/431172001487671163 来源:腾云阁 h ...
- Android手机用KSWEB搭建Web服务器成功安装WordPress
之前部落分享的几个免费Web服务器软件都是用来安装在本地电脑上,搭建Apache.PhpMyAdmin.MySQL等网站运行环境,然后我们就可以在电脑上测试运行Wordpress.Discuz! 论坛 ...