乐趣区

Java中的Heap-Buffer与Direct-Buffer

在使用 Java NIO 时,会经常和 ByteBuffer 打交道(吐槽下,每次手动 flip 切换读写模式太不友好)。在空 Buffer 创建时,有两种方式:

ByteBuffer.allocateDirect(capacity)
ByteBuffer.allocate(capacity)

那么这两种 Buffer 的分配又有什么不一样呢?

Heap Buffer

字面意思,在 java heap 上分配的内存。此块内存区域受 JVM 管理,GC 负责回收。使用时无需担心 Heap Buffer 的回收问题。

Direct Buffer

堆外内存(说非堆不太准确,毕竟非堆区域不止这一块),时分配在 C Heap 上的 Buffer,由于不属于 JVM HEAP,管理 / 监控起来会比较困难,但也会被 GC 回收。DirectByteBuffer 自身是(Java)堆内的,它背后真正承载数据的 buffer 是在(Java)堆外——native memory 中的。这是 malloc() 分配出来的内存,是用户态的。

那么为什么有了 Heap Buffer 还需要 Direct Buffer 呢?

在 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 的话最好还是复用,避免过多的创建。

堆外内存的监控

堆外内存不像堆内内存监控那么简单,不能直接看堆信息,但可以通过一些监控工具来查看

jconsole

jvisualvm


注:需要安装 VisualVM-BufferMonitor 和 VisualVM-MBeans 插件

退出移动版