Java虚拟机:JVM堆外内存如何被回收,你知道么?

堆外内存

JVM启动时分配的内存,称为堆内存,与之相对的,在代码中还可以使用堆外内存,比如Netty,广泛使用了堆外内存,但是这部分的内存并不归JVM管理,GC算法并不会对它们进行回收,所以在使用堆外内存时,要格外小心,防止内存一直得不到释放,造成线上故障。

堆外内存的申请和释放

JDK的 ByteBuffer类提供了一个接口 allocateDirect(intcapacity)进行堆外内存的申请,底层通过 unsafe.allocateMemory(size)实现,接下去看看在JVM层面是如何实现的。

可以发现,最底层是通过 malloc方法申请的,但是这块内存需要进行手动释放,JVM并不会进行回收,幸好 Unsafe提供了另一个接口 freeMemory可以对申请的堆外内存进行释放。

堆外内存的回收机制

如果每次申请堆外内存,都需要在代码中显示的释放,对于Java这门语言的设计来说,显然不够合理,既然JVM不会管理这些堆外内存,它们是如何回收的呢?

DirectByteBuffer

JDK中使用 DirectByteBuffer对象来表示堆外内存,每个 DirectByteBuffer对象在初始化时,都会创建一个对应的 Cleaner对象,这个 Cleaner对象会在合适的时候执行 unsafe.freeMemory(address),从而回收这块堆外内存。

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

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

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

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

Cleaner对象的 clean方法主要有两个作用:

1、把自身从 Clener链表删除,从而在下次GC时能够被回收

2、释放堆外内存

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

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

不过很多线上环境的JVM参数有 -XX:+DisableExplicitGC,导致了 System.gc()等于一个空函数,根本不会触发FGC,这一点在使用Netty框架时需要注意是否会出问题。

知识点太多我就不讲解那么多了

分享一篇电子书籍《深入理解Java虚拟机:JVM高级特性与最佳实践》文档的朋友们转发收藏+关注私信“资料”立即获取


发表评论
留言与评论(共有 0 条评论)
   
验证码:

相关文章

推荐文章

'); })();