个人认为rocket两个核心技术一个是网络io,一个是文件io。看到MappedByteBuffer的时候对内存映射的释放逻辑产生了疑惑
基础知识
- java中mmap过程如下,最终的mappedByteBuffer实际是一个DirectByteBuffer对象
File file = new File("xxx");
FileChannel fileChannel = new RandomAccessFile(file, "rw").getChannel();
MappedByteBuffer mappedByteBuffer = fileChannel.map(MapMode.READ_WRITE, 0, 4096);
- 看一下DirectByteBuffer构建源码,可以看到每一个DirectByteBuffer都会包含一个Cleaner实例(视图viewer没有)

- Cleaner里面是利用的虚引用,当DirectByteBuffer对象被垃圾回收时,会调用构造Cleaner时传入的回调方法,对MappedByteBuffer来说就是执行unmap,具体逻辑在FileChannelImpl的内部类Unmapper。对普通DirectByteBuffer来说就是Deallocator中的unsafe.freeMemory()
所以根据Cleaner的工作原理就能够理解,为什么网上那么多资料说直接内存会随着gc被回收,有些人甚至在需要主动回收直接内存时直接调用System.gc()
- 当我们想要主动释放内存映射时,最简单的办法就是直接调用
((DirectBuffer) mappedByteBuffer).cleaner().clean()
进阶部分
- 后来了解到Cleaner类在java9前后的包是不一样的,所以如果代码里持有Cleaner的对象,就不能在所有jdk中编译了,当然直接调用
((DirectBuffer) mappedByteBuffer).cleaner().clean()
肯定没有问题
java9前:sun.misc.Cleaner
java9后:java.lang.ref.Cleaner
- 联想到netty是直接内存使用大户,看下netty中的做法。netty中有两个类CleanerJava6、CleanerJava9对应不同的jdk版本分别使用
CleanerJava6根据jdk是否有Unsafe类决定通过Unsafe方法还是反射,调用DirectByteBuffer中Cleaner属性的clean()方法

CleanerJava9直接通过反射调用Unsafe的invokeCleaner()方法(invokeCleaner在java9后才有),内部也是调用的DirectByteBuffer中Cleaner属性的clean()方法

rocket中的做法
- 在rocket中unmap的逻辑如下,也是通过反射调用clean()方法,和我之前就关注的一个同学的博客做法完全一致,希望以后面基 :)

疑惑思考
- 为啥大家都非要用反射来调用clean呢?
((DirectBuffer) mappedByteBuffer).cleaner().clean()
简单粗暴不香吗?还有就是viewed()方法中的viewedBuffer是什么,没看到有这个名字的方法啊?
相当于用hotspot编译之后再openjdk上运行,就会报错 https://stackoverflow.com/questions/54323645/apache-spark-method-not-found-sun-nio-ch-directbuffer-cleanerlsun-misc-cleaner
网友评论