Buffer和Channel總是成對出現(xiàn),在Java NIO中Buffer用于和NIO通道進(jìn)行交互,數(shù)據(jù)總是從Chanel中讀入緩沖區(qū),然后在從緩沖區(qū)寫入Channel中匕积。
緩沖區(qū)本質(zhì)上是一塊物理上連續(xù)分配的內(nèi)存區(qū)域,這塊內(nèi)存區(qū)域被包裝成NIO Buffer對象榜跌,并提供了一組方法闪唆,用于方便的訪問該內(nèi)存區(qū)域。Buffer映射操作能夠直接操作底層平臺的資源钓葫。這些操作節(jié)省了在不同地址空間中復(fù)制數(shù)據(jù)的開銷——這在現(xiàn)代計算機(jī)體系結(jié)構(gòu)中是開銷很大的操作(相比于Java 面向流的IO)悄蕾。
1- 常用的Buffer類型
可以通過char,short础浮,int帆调,long,float 或 double類型來操作緩沖區(qū)中的字節(jié)豆同, MappedByteBuffer贷帮,用于表示內(nèi)存映射文件。
2- Buffer的分配
在使用Buffer之前需要先分配指定大小的內(nèi)存區(qū)域诱告。分配的緩沖區(qū)大小是定長的,不可以擴(kuò)展容量。并將分配的緩沖區(qū)元素都置為0精居。
- 不同類型Buffer的分配
每個Buffer類型的數(shù)據(jù)都有一個allocate的靜態(tài)方法來分配指定類型大小的緩沖數(shù)據(jù)區(qū)域锄禽,而且這些Buffer類型都是抽象類,不可實(shí)例化(但是可以使用類的靜態(tài)方法)靴姿。
- ByteBuffer不同內(nèi)存區(qū)域的Buffer分配
- 在Java堆上分配內(nèi)存沃但,HeapByteBuffer是NIO的包內(nèi)訪問權(quán)限類,包外不可獲取佛吓,方法返回向上轉(zhuǎn)型為ByteBuffer宵晚,其他Buffer類也都有這個方法,分配的是堆上內(nèi)存區(qū)域(新建了一個byte數(shù)組)维雇。
-
在堆外內(nèi)存上分配緩沖區(qū)淤刃,只有ByteBuffer可以分配堆外緩沖區(qū)。
堆外內(nèi)存使用unsafe.allocateMemory方法分配
但是可以先分配ByteBuffer的堆外內(nèi)存吱型,然后轉(zhuǎn)換為其他類型的來訪問逸贾。
還有堆外分配不一定能分配成功,需要進(jìn)行判斷
- 堆上(HeapByteBuffer) VS 堆外(DirectByteBuffer)
- 分配和銷毀堆外直接內(nèi)存緩沖區(qū)通常要比分配和銷毀堆上緩沖區(qū)消耗更多的系統(tǒng)資源津滞。
- DirectByteBuffer比HeapByteBuffer讀寫性能更高铝侵,可以提高網(wǎng)絡(luò)交互的速度:HeapByteBuffer會發(fā)生頻繁的直接內(nèi)存和JVM堆內(nèi)存之間的相互的拷貝,比如flush數(shù)據(jù)到遠(yuǎn)程的時候触徐,會將JVM內(nèi)存拷貝到直接內(nèi)存然后才會進(jìn)行數(shù)據(jù)發(fā)送的工作咪鲜;而DirectByteBuffer是對直接內(nèi)存的保證所以會省去內(nèi)存拷貝的過程,這在計算操作系統(tǒng)中節(jié)省可觀的性能消耗撞鹉。
- 基于NIO的開源web框架如Netty疟丙,Nginx都基于DirectByteBuffer提高了整體讀寫性能。如Netty的ByteBuf完成了對ByteBuffer的包裝和拓展孔祸,需要注意的是在NIO網(wǎng)絡(luò)編程時只有ByteBuffer能和Channel進(jìn)行讀寫交互隆敢,ByteBuf在于Channel進(jìn)行交互時也是轉(zhuǎn)換成了ByteBuffer之后再與Channel交互。
- 由于分配DirectByteBuffer比較消耗系統(tǒng)資源崔慧,但是又能提高讀寫性能拂蝎,Netty的做法是分配DirectByteBuffer后自己根據(jù)引用計數(shù)做內(nèi)存回收,重新復(fù)用DirectByteBuffer惶室,而不是直接釋放掉分配好的內(nèi)存温自。
- DirectByteBuffer回收管理(程序員完全控制管理)
直接內(nèi)存的釋放是GC(full gc,調(diào)用System.gc())自動回收來控制的皇钞,并不由程序員控制悼泌,沒有類似的close或者free等顯示釋放內(nèi)存空間的方法,但是如何才能有程序員完全掌握回收的控制權(quán)呢夹界?DirectByteBuffer有一個Cleaner域用于內(nèi)存釋放馆里,給了程序猿一線生機(jī)。
- 設(shè)置JVM 參數(shù)DisableExplicitGC ,禁用full gc(這樣DirectByteBuffer就不會被系統(tǒng)回收了) 鸠踪,嚴(yán)重警告:禁用full gc丙者,需要嚴(yán)格的測試,存在內(nèi)存泄露的風(fēng)險营密,必要進(jìn)行堆外內(nèi)存管理(其實(shí)Netty就是這么干的)
- DirectByteBuffer構(gòu)造函數(shù)會新建一個Deallocator類來初始化這個Cleaner域
-
Deallocator是DirectByteBuffer靜態(tài)內(nèi)部類械媒,含有一個run方法,方法內(nèi)部使用unsafe.freeMemory釋放分配的直接內(nèi)存空間
-
Cleaner類有一個方法clean方法评汰,調(diào)用的是傳進(jìn)去的Deallocator對象的run方法纷捞,可以用來釋放分配的堆外直接內(nèi)存。
- 代碼示例
DirectByteBuffer實(shí)現(xiàn)了DirectBuffer接口被去,DirectByteBuffer是default訪問權(quán)限主儡,但是DirectBuffer是public,如果不是出于特殊考慮建議不要通過DirectBuffer直接操作DirectByteBuffer编振,容易造成安全隱患(這也是DirectByteBuffer定義為default訪問權(quán)限的原因)
3- Buffer的使用
Buffer的使用一般搭配Channel
- 將數(shù)據(jù)讀到Buffer中
- 將Buffer中數(shù)據(jù)讀出
get方法也可以獲取指定位置的數(shù)據(jù)
- position(位置)缀辩、capacity(容量)、limit(限制)
position指的是當(dāng)前在緩沖數(shù)組中的位置踪央;capacity指的是緩沖數(shù)組的大小臀玄,在創(chuàng)建時指定,代表著最大可存儲的數(shù)據(jù)長度畅蹂;limit指的是有效數(shù)據(jù)的長度健无,limit小于等于capacity。
- 讀寫模式轉(zhuǎn)換時液斜,PCL的變化
- 新建:position=0累贤;limit = capacity
- 讀入數(shù)據(jù)大小5byte:position = 5;limit=capacity
- flip(寫->讀)模式轉(zhuǎn)換:position=0少漆;limit=轉(zhuǎn)換前position位置
- clear方法:和新建一樣
- rewind方法:position=0臼膏;limit=保持不變
flip方法用于寫->讀轉(zhuǎn)換,clear方法用于重置緩沖數(shù)組等待下一次將數(shù)據(jù)寫入緩沖數(shù)組示损,rewind方法用于重讀數(shù)組渗磅,需要注意的是這些只是position、limit 的位置在發(fā)生變化检访,緩沖數(shù)組的數(shù)據(jù)并沒有被清除始鱼,只有下次寫入才能將原來的數(shù)據(jù)覆蓋。當(dāng)將緩沖數(shù)組數(shù)據(jù)讀出時脆贵,只會讀出position-limit范圍內(nèi)的數(shù)據(jù)(有效數(shù)據(jù))医清,而不會讀取limit-capacity之間的數(shù)據(jù)(上次寫入時遺留的數(shù)據(jù),等待被覆蓋)卖氨。
其他的方法還有mark()與reset()方法会烙,通過調(diào)用Buffer.mark()方法负懦,可以標(biāo)記Buffer中的一個特定position。之后可以通過調(diào)用Buffer.reset()方法恢復(fù)到這個position持搜。這樣就提供了在一個緩沖數(shù)組中反復(fù)遍歷操作讀入數(shù)據(jù)的便利和靈活性密似,而不像面向流的IO不能操作當(dāng)前位置的前一個字節(jié)。