jvm 堆外内存_JVM源码分析之堆外内存(直接内存)

1、堆外内存定义

内存对象分配在JVM中堆以外的内存,也可以称为直接内存,这些内存直接受操作系统管理(而不是JVM),这样做的好处是能够在一定程度上减少垃圾回收对应用程序造成的影响。一般我们使用Unsafe和NIO包下ByteBuffer来创建堆外内存。

2、为什么使用堆外内存

1、减少了垃圾回收

使用堆外内存的话,堆外内存是直接受操作系统管理( 而不是虚拟机 )。这样做的结果就是能保持一个较小的堆内内存,以减少垃圾收集对应用的影响。

2、提升复制速度(io效率)

堆内内存由JVM管理,属于“用户态”;而堆外内存由OS管理,属于“内核态”。如果从堆内向磁盘写数据时,数据会被先复制到堆外内存,即内核缓冲区,然后再由OS写入磁盘,使用堆外内存避免了这个操作。

91a2f12f0a4a64998c2c990366eebaf8.png

3、堆外内存申请

JDK的ByteBuffer类提供了一个接口allocateDirect(int capacity)进行堆外内存的申请,底层通过unsafe.allocateMemory(size)实现。Netty、Mina等框架提供的接口也是基于ByteBuffer封装的。

cc76548986be222c708b90aec88a6d8e.png

源码分析如下:

c51f7c7d32dd6bcb2b407fea62b27c48.png

4、堆外内存释放

虽然堆外内存直接受操作系统管理,但是不代表JVM不进行内存回收,要了解堆外内存释放,必须了解以下内容:

1)当初始化一块堆外内存时,对象的引用关系如下:

d99b229679965b5be5c22604f8299c72.png

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

源码分析如下(和对象的引用关系对照):

cleaner对象在以下代码中创建

ada264429bbc81661ef430090e3c3d64.png

first对象和ReferenceQueue对象是在以下代码中,

2d6fe94c8e5c3a100d1295217262865e.png

2)如果该DirectByteBuffer对象在一次GC中被回收了,会发生什么?

cddb613ad8549c726bf05e7dbd097e1c.png

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

Cleaner对象的clean方法主要有两个作用: 1、把自身从Clener链表删除,从而在下次GC时能够被回收 2、释放堆外内存

如果JVM一直没有执行FullGC的话,无效的Cleaner对象就无法放入到ReferenceQueue中,从而堆外内存也一直得不到释放,内存岂不是会爆?

其实在初始化DirectByteBuffer对象时,如果当前堆外内存的条件很苛刻时(不够时),会主动调用System.gc()强制执行FullGC,代码如下:

d90c5ffae0b124e80f7f2ea72f21f6e4.png
6283f4fc72e9ba6270a6966d1ed49b16.png

5、总结

如果我们大面积使用堆外内存并且没有限制,那迟早会导致内存溢出,毕竟程序是跑在一台资源受限的机器上,因为这块内存的回收不是你直接能控制的,当然你可以通过别的一些途径,比如反射,直接使用Unsafe接口等,但是这些务必给你带来了一些烦恼,Java与生俱来的优势被你完全抛弃了—开发不需要关注内存的回收,由gc算法自动去实现。另外上面的gc机制与堆外内存的关系也说了,如果一直触发不了cms gc或者full gc,那么后果可能很严重。

你的赞和关注是我继续创作的动力~

原文:https://juejin.im/post/5d9ecc7c5188252ba4209b26?utm_source=tuicool&utm_medium=referral

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值