Buffer概念
Buffer是针对原始数据类型具有固定大小的数据块容器,从通道读取数据或向通道写入数据,可以看作通道读写端点。
它提供了一系列方法可以方便的处理内存块,以便从通道读取和写入数据。
Buffer分类
Java为除了布尔型以为的其它原始类型提供了对应的Buffer实现。如下图所示:
Buffer的重要属性
- Capacity: Buffer所能容纳的最大数据量/字节,不能被修改。一旦缓冲区已满,应在写入前清理。
- Limit: 读写操作限额。写模式下表示最大支持的写入量,最大不能超过Capacity;读模式下表示可以读取的最大数据量。
- Position: 要操作数据的起始位置,可以被get和put方法自动更新。
- Mark: 标记buffer的位置。调用
mark()
记录当前位置;调用reset()
回退到记录位置。
Buffer的主要方法
allocate(int capacity)
创建新的Buffer并指定最大容量。read() and put()
通道的read方法用于将数据从通道写入缓冲区,而put是缓冲区的方法,用于将数据写入缓冲区flip()
将Buffer从写模式切换为读模式,设置Position为0,并将Limit设置为写入时的位置write() and get()
通道的write方法用于将数据从缓冲区写入通道,而get方法是缓冲区的方法,用于从缓冲区读取数据rewind()
用于重新读取数据,设置Postion为0,但不会改变Limitclear() and compact()
两个方法都是用来将Buffer读模式切换为写模式。clear()
使位置归0,Limit等于Capacity,但不会清除Buffer中的数据,只是标记为被重新初始化;当存在未读数据时,使用compact()
方法,会将未读数据复制到开头,并设置位置为最后一个未读数据位置之后,Limit仍然设置为Capacitymark() and reset()
mark()
标记当前位置,reset()
退回到标记位置
两种特别的Buffer
Direct Buffer:
Buffer中定义了isDirect()
,用于区分堆内Buffer和堆外Buffer。Direct Buffer属于堆外内存,使用allocateDirect方法直接创建,而堆内使用allocate方法创建。MappedByteBuffer:
将文件按照指定大小直接映射为内存区域,当程序访问这个内存区域时将直接操作这块儿文件数据,省去了空间内存拷贝。用FileChannel.map创建MappedByteBuffer,其本质属于Direct Buffer。
Buffer和垃圾收集
Java会尽量的对DirectBuffer仅做本地IO操作,对大数据量IO密集型操作,可能会带来非常大的性能优势。原因如下:
- Direct Buffer整个生命周期中内存地址不会发生变化,内核可以安全的访问,很多IO操作会很高效
- 减少堆内对象存储的可能额外维护工作,访问效率有所提高
Direct Buffer创建和销毁过程中,都会比一般的堆内Buffer增加部分开销,所以通常都建议用于长期使用、数据较大的场景。
另外,大多数垃圾收集过程中,都不会主动收集Direct Buffer,它的垃圾收集过程,就是基于Cleaner(一个内部实现)和幻象引用(PhantomReference)机制,其本身不是public类型,内部实现了一个Deallocator负责销毁的逻辑。对它的销毁往往要拖到full GC的时候,所以使用不当很容易导致OutOfMemoryError。
DirectBuffer回收建议:
- 在应用程序中,显式地调用System.gc()来强制触发。
- 在大量使用Direct Buffer的部分框架(Netty)中,框架会自己在程序中调用释放方法。
- 重复使用Direct Buffer。