ByteBuffer总结
- 1. 组成
- 1.1 类关系
- 1.2 Buffer
- 1.2.1 属性
- 1.2.2 API
- 读取(get)
- 填充(put)
- 翻转(flip & rewind)
- 释放(clear)
- 1.2.3 创建方式
- 1.2 HeapByteBuffer
- 1.3 HeapByteBufferR
- 1.4 DirectByteBuffer
- 构造方法
- 系统内核交互
- mmap内存映射
- 内存回收问题
- 优缺点
- 1.5 DirectByteBufferR
- 1.6 MappedByteBuffer
- 2. API
- 3. 参考

Buffer 即缓冲器,它将特定基础类型(可以是byte、char、short、int、long、double、float、boolean的任意一种)的元素的线性有限序列作为缓冲容器, 缓冲区的基本属性是其容量,限制和位置。

1.2.1 属性
- 容量(Capacity)
缓冲区能够容纳的数据元素的最大数量。这一容量在缓冲区创建时被设定,并且永远不能
被改变。 - 上界(Limit)
缓冲区的第一个不能被读或写的元素。或者说,缓冲区中现存元素的计数。 - 位置(Position)
下一个要被读或写的元素的索引。位置会自动由相应的 get( )和 put( )函数更新。 - 标记(Mark)
一个备忘位置。调用 mark( )来设定 mark = postion。调用 reset( )设定 position =
mark。标记在设定前是未定义的(undefined)。
1.2.2 API
API的设计采用级联调用,类似StringBuffer的设计,方便链式调用
读取(get)
我们知道,Buffer是一个上层抽象类,定义了缓冲概念的方法模型,具体的缓存数据存储是有实现它的子类也就是具体的基本数据类型的子类(byte、char、short、int、long、double、float、boolean的任意一种)进行实现限定数据类型的存储和提供对应获取数据的类型的。
填充(put)
相当于get方法的对应方法,需要注意的是get和put是结对出现的,缓冲数据存储是顺序的,因此读取也是要遵循顺序,否则会得到非期望结果甚至异常。
翻转(flip & rewind)
flip() 函数将一个能够继续添加数据元素的填充状态的缓冲区翻转成一个准备读出元素的释放状态。这里可以这么理解,当写入数据的时候,position、limit等标记属性都会进行变动符合写入状态下的写模式操作,当读取的时候必然要对position、limit等标记属性进行变更以符合读模式的要求,相反情况也是如此,因此flip函数提供的就是这种读写模式互相切换的功能。
rewind() 它只是将位置值设回 0。您可以使用它后退,重读已经被翻转的缓冲区中的数据。
释放(clear)
将position重置为0,此时缓冲区内存数据并未发生变更,只是改变了标识
1.2.3 创建方式
- 直接开辟内存空间
CharBuffer charBuffer = CharBuffer.allocate (100) - 使用自己指定的内存空间
char [] myArray = new char [100];
CharBuffer charbuffer = CharBuffer.wrap (myArray);

以堆内存实现缓存区,即JVM内存实现,此时缓存区是通过字节数组数据结构进行实现
以堆内存实现缓存区,且只读不可写,该类是一个包内封装类不能直接声明,可以通过调用byteBuffer.asReadOnlyBuffer() 获得。

以堆外内存实现缓存区
构造方法
通过DirectByteBuffer类的依赖看到主要依赖JVM底层的sun.misc.Cleaner
、sun.misc.Unsafe、sun.misc.VM
实现
- Cleaner 负责堆外内存回收
- Unsafe 进行内存分配
- VM 提供基本功能支持
系统内核交互
编写一段简易代码,调用ByteBuffer.allocateDirect(10) 来开辟堆外内存,以Linux OS作为操作系统
通过强大的strace命令对应用进程和操作系统接口方法交互进行跟踪和输出,
strace -o log.txt -T -tt -e trace=memory java DirectByteBufferTest
如下:
可以看到分别调用了mmap、mprotect、munmap
- mmap 进行内存映射
- mprotect 对内存区域进行保护
- munmap 对内存映射进行解除
感兴趣的话可以继续深入了解函数的作用,这里不展开。
mmap内存映射

共享内存可以说是最有用的进程间通信方式,也是最快的IPC形式, 因为进程可以直接读写内存,而不需要任何数据的拷贝。对于像管道和消息队列等通信方式,则需要在内核和用户空间进行四次的数据拷贝,而共享内存则只拷贝两次数据: 一次从输入文件到共享内存区,另一次从共享内存区到输出文件。实际上,进程之间在共享内存时,并不总是读写少量数据后就解除映射,有新的通信时,再重新建立共享内存区域。而是保持共享区域,直到通信完毕为止,这样,数据内容一直保存在共享内存中,并没有写回文件。共享内存中的内容往往是在解除映射时才写回文件的。因此,采用共享内存的通信方式效率是非常高的。
内存回收问题
- 系统使用Netty进行服务器之间的通信,而NIO使用的DirectByteBuffer,其申请的native memory只有在old gen GC(full GC/major GC或者concurrent GC)时才可以回收。主要原理是Full GC的会对old gen做reference processing,进而可以触发Cleaner对已死的DirectByteBuffer对象做清理工作;而如果很长一段时间没做过GC或者只做了young GC,则不会在old gen触发Cleaner的工作,那么就可能让本来已经死了的、但已经晋升到old gen的DirectByteBuffer关联的native memory得不到及时释放;
- 为DirectByteBuffer分配空间的过程中,会通过System.gc()的显示调用强迫已经无用的DirectByteBuffer对象释放掉他们关联的native memory;但是如果系统在运行的时候,设置了JVM参数:-XX:DisableExplicitGC,那么System.gc()的显示调用将变成空调用;
- 此时只有自然触发old gen GC进行内存回收,或者等待native memory占用达到了设置的MaxDirectMemorySize之后,出现内存溢出;
- 如果不使用-XX:MaxDirectMemorySize={size}进行设置,MaxDirectMemorySize其值将默认为JVM可以使用最大对内存(-Xmx)减去一个Survivor space的值。
优缺点
优点:
- 不会占用JVM内存空间,从操作系统中开辟内存空间
- 由于不占用JVM内存空间,因此不会出现受Young Gen GC控制,在老年代GC(full GC/major GC或者concurrent GC)时才可以回收
- 底层通过操作系统OS内存文件映射进行操作,省去了JVM内存复制到系统内存的过程,实现零拷贝。原本读取和写入均需要 用户空间 <–> 内核空间 <–> 磁盘存储 的四次copy传输,现在通过mmap实现了用户空间共享内存直接映射磁盘文件,减少了内核空间的拷贝。
缺点:
- 不受JVM垃圾回收机制控制,虽然提供了内存回收机制,出现问题不容易排查
以堆外内存实现缓存区,且只读不可写,类似HeapByteBufferR
是ByteBuffer对File文件流进行处理的实现,底层也是支持mmap来直接操作磁盘文件,减少内核空间拷贝实现高性能操作的
下面通过代码示例和注释说明一些常用方法,allocateXXX、flip、get、put、rewind、clear
到此这篇ByteBuffer读取文件流(bytebufferinputstream)的文章就介绍到这了,更多相关内容请继续浏览下面的相关推荐文章,希望大家都能在编程的领域有一番成就!版权声明:
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若内容造成侵权、违法违规、事实不符,请将相关资料发送至xkadmin@xkablog.com进行投诉反馈,一经查实,立即处理!
转载请注明出处,原文链接:https://www.xkablog.com/rfx/28857.html