Java堆外内存

最近才接触到堆外内存的概念,为什么要使用堆外内存,最大的原因应该就是少一次内存拷贝,因为如果想在内核空间进行数据处理,就必须将数据从Java堆中拷贝到内核控件中,但是Java提供了一种更加直接的方法,就是直接在堆外(内核)申请内存,存储数据,减少数据拷贝,这也就是所谓的零拷贝(zero-copy)

API

NIO包下的ByteBuffer下的allocateDirect是帮助我们申请直接内存的,并创建一个DirectByteBuffer对象,其中allocateDirect方法的实现是Unsafe包下的native方法,所以当做黑盒吧.
直接内存和堆内存的区别还有一点,直接内存已经不在JVM的运行时数据区域了,所以不受GC的回收,但又不完全是这样,详细的看下面底层,这就很有意思了,具体的方法还是Unsafe包下的native方法

底层

之前我一直认为对外内存是不被GC回收的,但是看了Netty的内存池的实现后,发现其实和GC还是有关系的.
上面我们说到申请堆外内存是通过Unsafe类下的方法,堆外内存类是DirectByteBuffer,但这其实是一个堆上的对象,只存着基地址和大小等几个属性,和一个Cleaner,它所代表的是内存中的一大块区域,当这个堆上的对象被回收后,也就堆外的内存也会被回收.

手动回收

堆外内存是可以手动回收的,也就是刚才说的DIrectByteBuffer中的Cleaner对象,我们从DirectByteBuffer中拿到这个对象后,执行clean()方法,就可以释放了,事实上,JVM回收堆外内存也是这样,不过要复杂一点

JVM回收

我们知道Java中的引用类型有四种,但是经常使用的只有强引用,这里用到就是最后一种,虚引用.
看一下Cleaner的声明:
public class Cleaner extends PhantomReference<Object>
这个Cleaner继承了虚引用类,这就意味着它拥有类虚引用的特性,当GC发现Cleaner对象不在被持有(在这里就是DirectByteBuffer失效),会将这个对象放进 Reference类pending list静态变量里(这是虚引用特性),然后这里介绍一个最高优先级的守护线程ReferenceHandler,这个守护线程会一直扫描pending list队列,如果发现队列元素是Cleaner类型的,就执行其clean()方法,回收堆外内存.

Netty

Netty的高性能原因之一就是使用了直接内存,那么Netty作为一个框架,就一定要提供内存回收的方法,具体使用的内存管理机制是基于Jemalloc算法的,大致介绍一下:

首先会预申请一大块内存 Arena ,Arena 由许多 Chunk 组成,而每个 Chunk 默认由2048个page组成。
Chunk 通过 AVL 树的形式组织 Page ,每个叶子节点表示一个 Page ,而中间节点表示内存区域,节点自己记录它在整个 Arena 中的偏移地址。当区域被分配出去后,中间节点上的标记位会被标记,这样就表示这个中间节点以下的所有节点都已被分配了。大于 8k 的内存分配在 PoolChunkList 中,而 PoolSubpage 用于分配小于 8k 的内存,它会把一个 page 分割成多段,进行内存分配。

彩蛋

看一位大佬博客中的一句话,很好玩 :

C/C++ 和 java 中有个围城,城里的想出来,城外的想进去!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值