使用之前写的文章里的例子
https://blog.csdn.net/zlpzlpzyd/article/details/135292683
import java.io.File;
import java.io.FileInputStream;
import java.io.Serializable;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
public class TestHeapByteBuffer implements Serializable {
public static void main(String[] args) {
long start = System.currentTimeMillis();
File file = new File(Control.PATH);
ByteBuffer byteBuffer = ByteBuffer.allocate(Control.SIZE);
try (FileChannel fileChannel = new FileInputStream(file).getChannel()) {
while (fileChannel.read(byteBuffer) > 0) {
byteBuffer.clear();
}
} catch (Throwable e) {
e.printStackTrace();
}
long duration = System.currentTimeMillis() - start;
System.out.println(duration);
}
}
HeapByteBuffer 在堆上创建的缓冲区,通过?FileChannel 的 read() 读取缓冲区时,会先通过?IOUtil.read() 将?ByteBuffer 获取一个临时?DirectByteBuffer 添加到原来的?ByteBuffer 中。
间接调用?Util 的?getTemporaryDirectBuffer() 获取临时的?DirectByteBuffer,使用完毕后销毁。
可见,HeapByteBuffer 使用的缓冲区不是单纯在堆上处理,还需要借助于?DirectByteBuffer 来处理。
这样就面临一个问题,每次调用 read() 都会造成一个开销问题。
上面只是拿了 read() 来讲解,write() 类似。
import java.io.File;
import java.io.FileInputStream;
import java.io.Serializable;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
public class TestDirectByteBuffer implements Serializable {
public static void main(String[] args) {
long start = System.currentTimeMillis();
File file = new File(Control.PATH);
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(Control.SIZE);
try (FileChannel fileChannel = new FileInputStream(file).getChannel()) {
while (fileChannel.read(byteBuffer) > 0) {
byteBuffer.clear();
}
} catch (Throwable e) {
e.printStackTrace();
}
long duration = System.currentTimeMillis() - start;
System.out.println(duration);
}
}
直接在堆外分配的内存缓冲区,在构造器中有三个操作,如下图
获取?DirectByteBuffer 的最大直接内存
在 VM 类中可以看到,默认最大值是 64MB,可以通过参数?-XX:MaxDirectMemorySize 进行修改,具体修改参数修改可以参见如下的官方文档
https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html
接下来是给 Bits 中的变量赋值
因为参数 size 和 cap 是创建?ByteBuffer 时指定的,totalCapacity 默认值为 0,加上 maxMemory 的值为 64 MB,所以比较式右边的值为 64 MB,cap <= 右边的数据,所以进入循环处理,第一次就停止循环到被调用方停止当前执行方法。
调用?Unsafe 的 allocateMemory() 先向内存中分配一个新块
调用?Unsafe 的 setMemory() 向分配的内存块中设置对应的字节
其中创建的?Deallocator 是一个内部类,创建的对象作为一个后台线程处理。Cleaner 是一个 PhantomReference 对象,即对应的值无法获取。
线程执行的时候会触发?Unsafe 的?freeMemory() 和?Bits.unreserveMemory() 触发堆外的内存清理操作,但是触发时间无法控制。
通过分析可以得出如下
HeapByteBuffer 使用简单,每次执行数据读取写入间接创建?DirectByteBuffer,效率低。
DirectByteBuffer 使用相对麻烦,但是效率高,需要考虑到通过 ByteBuffer?分配的缓冲区与 jvm 参数?-XX:MaxDirectMemorySize 是否合理的问题,不然的在运行过程中会出现内存溢出问题。内部是通过?PhantomReference(Cleaner 的父类)来处理创建的缓冲区,最终通过?Reference 的 clean() 来间接执行堆外内存回收。
摘自网络上的解答
https://cheng-dp.github.io/2018/12/11/direct-memory-and-direct-byte-buffer/
减少复制操作,加快传输速度
HotSpot虚拟机中,GC除了CMS算法之外,都需要移动对象。
在NIO实现中(如: FileChannel.read(ByteBuffer dst), FileChannel.write(ByteByffer src)), 底层要求连续的内存,且使用期间不得变动, 如果提供的Buffer是HeapByteBuffer,为了保证在数据传输时,被传输的byte数组背后的对象不会被GC回收或者移动,JVM会首先将堆中的byte数组拷贝至直接内存,再由直接内存进行传输。
那么,相比于HeapByteBuffer在堆上分配空间,直接只用DirectByteBuffer在直接内存分配就节省了一次拷贝,加快了数据传输的速度。
减少GC压力
虽然GC仍然管理DirectByteBuffer,但基于DirectByteBuffer分配的空间不属于GC管理,如果IO数量较大,可以明显降低GC压力。
http://lovestblog.cn/blog/2015/05/12/direct-buffer/
DirectByteBuffer在创建的时候会通过Unsafe的native方法来直接使用malloc分配一块内存,这块内存是heap之外的,那么自然也不会对gc造成什么影响(System.gc除外),因为gc耗时的操作主要是操作heap之内的对象,对这块内存的操作也是直接通过Unsafe的native方法来操作的,相当于DirectByteBuffer仅仅是一个壳,还有我们通信过程中如果数据是在Heap里的,最终也还是会copy一份到堆外,然后再进行发送,所以为什么不直接使用堆外内存呢。对于需要频繁操作的内存,并且仅仅是临时存在一会的,都建议使用堆外内存,并且做成缓冲池,不断循环利用这块内存。
需要频繁操作的小内存,并且仅仅是临时存在一会的,都建议使用堆外内存,并且做成缓冲池,不断循环利用这块内存。
摘自网络上的解答
创建和销毁比普通Buffer慢。
虽然DirectByteBuffer的传输速度很快,但是创建和销毁比普通Buffer慢。因此DirectByteBuffer适合只是短时使用需要频繁创建和销毁的场合。
使用直接内存要设置-XX:MaxDirectMemorySize指定最大大小。
直接内存不受GC管理,而基于DirectByteBuffer对象的自动回收过程并不稳定,如DirectByteBuffer对象被MinorGC经过MinorGC进入老年代,但是由于堆内存充足,迟迟没有触发Full GC,DirectByteBuffer将不会被回收,其申请的直接内存也就不会被释放,最终造成直接内存的OutOfMemoryError。
如果我们大面积使用堆外内存并且没有限制,那迟早会导致内存溢出,毕竟程序是跑在一台资源受限的机器上,因为这块内存的回收不是你直接能控制的,当然你可以通过别的一些途径,比如反射,直接使用Unsafe接口等,但是这些务必给你带来了一些烦恼,Java与生俱来的优势被你完全抛弃了—开发不需要关注内存的回收,由gc算法自动去实现。另外上面的gc机制与堆外内存的关系也说了,如果一直触发不了cms gc或者full gc,那么后果可能很严重。
参考链接
https://www.zhihu.com/question/60892134
https://www.cnblogs.com/Chary/p/16718014.html
https://developer.aliyun.com/article/763697
https://juejin.cn/post/6844903744119783431
??????https://www.infoq.cn/news/2014/12/external-memory-heap-memory/