在使用Java NIO时,会经常和ByteBuffer打交道(吐槽下,每次手动flip切换读写模式太不友好)。在空Buffer创建时,有两种方式:
ByteBuffer.allocateDirect(capacity) ByteBuffer.allocate(capacity)
那么这两种Buffer的分配又有什么不一样呢?
字面意思,在java heap上分配的内存。此块内存区域受JVM管理,GC负责回收。使用时无需担心Heap Buffer的回收问题。
堆外内存(说非堆不太准确,毕竟非堆区域不止这一块),时分配在C Heap上的Buffer,由于不属于JVM HEAP,管理/监控起来会比较困难,但也会被GC回收。DirectByteBuffer 自身是(Java)堆内的,它背后真正承载数据的buffer是在(Java)堆外——native memory中的。这是 malloc() 分配出来的内存,是用户态的。
在JVM的垃圾回收器里,除了CMS,都是需要移动对象的;如果要把一个Java里的 byte[] 对象的引用传给native代码,让native代码直接访问数组的内容的话,就必须要保证native代码在访问的时候这个 byte[] 对象不能被移动,也就是要被“pin”(钉)住。
于是就出现了Direct Buffer,Direct Buffer是在C Heap中分配的内存,不像JVM堆内存是逻辑的,虽然也会被GC管理,但他是通过PhantomReference来达到的,正常的young gc或者mark and compact的时候不会在内存里移动。例如使用在传输数据时(磁盘IO传输和Socket传输都属于fd),如果传入HeapByteBuffer,首先会把HeapByteBuffer 背后的 byte[] 的内容拷贝到一个 DirectByteBuffer,然后再发送DirectByteBuffer中的数据。如果直接使用DirectByteBuffer的话,就会少了一次 HeapByteBuffer->DirectByteBuffer
的拷贝。
但是使用DirectByteBuffer也是有代价的,DirectByteBuffer比HeapByteBuffer的创建开销更大,所以如果要使用DirectByteBuffer的话最好还是复用,避免过多的创建。
堆外内存不像堆内内存监控那么简单,不能直接看堆信息,但可以通过一些监控工具来查看
注:需要安装VisualVM-BufferMonitor和VisualVM-MBeans插件