ByteBuffer底层是通过byte数组的方式来存储数据的,所谓直接缓冲是指byte数组是通过堆外存存储的,并没有存在jvm堆上,不受jvm垃圾回收的约束。
ByteBuffer的创建有两种方式,allocate和allocateDirect,其中通过allocate创建出来的是HeapByteBuffer(堆缓冲),源码如下:
HeapByteBuffer继承ByteBuffer,在ByteBuffer中有byte数组来存储数据
通过allocateDirect创建出来的是DirectByteBuffer(直接缓冲),源码如下:
在创建DirectByteBuffer过程中,存储数据的空间通过native方法来进行内存分配和初始化,最终通过一个内存地址将DirectByteBuffer和存储数据的数组关联在一起。源码如下:
如果使用堆缓冲,java堆属于用户空间,以IO输入为例,首先是用户空间进程向内核请求某个磁盘空间数据,然后内核将磁盘数据读取到内核空间的buffer中,然后用户空间的进程再将内核空间buffer中的数据读取到自身的buffer中,然后进程就可以访问使用这些数据,如下图:
在上述过程中,有了一次数据拷贝,在高并发的场景下,必定有性能的损耗,直接缓冲就解决了此问题。目前的操作系统,用户空间和内核空间的区分一般采用虚拟内存来实现,因此用户空间和内存空间都是在虚拟内存中。使用虚拟内存无非是因为其两大优势:一是它可以使多个虚拟内存地址指向同一个物理内存;二是虚拟内存的空间可以大于物理内存的空间。对于第一点在进行IO操作时就可以将用户空间的buffer区和内核空间的buffer区指向同一个物理内存。这样用户空间的程序就不需要再去内核空间再取回数据,而是可以直接访问,避免了数据拷贝,提升性能。
再补充一个知识点,使用HeapByteBuffer时,为什么从磁盘读取数据到buffer需要做一次copy,为什么不能直接从磁盘copy到buffer。这个copy过程从jdk源码也有体现,如下:
从源码看到,确实做了一次copy,这么做其实是在迁就OpenJDK里的HotSpot VM的一点实现细节。HeapByteBuffer存储对象的byte数组在java堆上,而HotSpot VM里的GC除了CMS之外都是要移动对象的,是所谓“compacting GC”。如果要把一个Java里的 byte[] 对象的引用传给native代码,让native代码直接访问数组的内容的话,就必须要保证native代码在访问的时候这个 byte[] 对象不能被移动,也就是要被“pin”(钉)住。可惜HotSpot VM出于一些取舍而决定不实现单个对象层面的object pinning,要pin的话就得暂时禁用GC——也就等于把整个Java堆都给pin住。HotSpot VM对JNI的Critical系API就是这样实现的。这用起来就不那么顺手。所以 Oracle/Sun JDK / OpenJDK 的这个地方就用了点绕弯的做法。它假设把 HeapByteBuffer 背后的 byte[] 里的内容拷贝一次是一个时间开销可以接受的操作,同时假设真正的I/O可能是一个很慢的操作。于是它就先把 HeapByteBuffer 背后的 byte[] 的内容拷贝到一个 DirectByteBuffer 背后的native memory去,这个拷贝会涉及 sun.misc.Unsafe.copyMemory() 的调用,背后是类似 memcpy() 的实现。这个操作本质上是会在整个拷贝过程中暂时不允许发生GC的,虽然实现方式跟JNI的Critical系API不太一样。(具体来说是 Unsafe.copyMemory() 是HotSpot VM的一个intrinsic方法,中间没有safepoint所以GC无法发生)。然后数据被拷贝到native memory之后就好办了,就去做真正的I/O,把 DirectByteBuffer 背后的native memory地址传给真正做I/O的函数。这边就不需要再去访问Java对象去读写要做I/O的数据了。
4.1 优点
前面已经分析了,在进行IO操作时,例如文件读写,或者socket读写,少了一次copy性能有提升。
注意,仅限于有IO操作的场景下
4.2 缺点
在非IO操作的场景下,例如仅仅做数据的编解码,不和机器硬件打交道,那么即使使用了HeapByteBuffer也不会产生copy动作,如果此时仍然在使用DirectByteBuffer,由于数据存储部分在堆外存,内存空间的分配和释放比堆内存更加复杂一些,性能也稍慢一些,在netty中是通过buf池的方案来解决的,后续的netty博客会深入讲解。
基于此我们可以总结一下HeapByteBuffer和DirectByteBuffer的使用场景:对于后端业务的编解码操作,使用HeapByteBuffer,对于IO通信,使用DirectByteBuffer。
到此这篇Bytebuffer 性能(bytebuffer direct)的文章就介绍到这了,更多相关内容请继续浏览下面的相关推荐文章,希望大家都能在编程的领域有一番成就!版权声明:
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若内容造成侵权、违法违规、事实不符,请将相关资料发送至xkadmin@xkablog.com进行投诉反馈,一经查实,立即处理!
转载请注明出处,原文链接:https://www.xkablog.com/hd-xnyh/52509.html