Java知識梳理四

一即横、Java中的文件復(fù)制

1.Java IO實現(xiàn)文件復(fù)制

? ? ? ?利用java.io類庫,直接為源文件構(gòu)建一個FileInputStream讀取阵翎,然后再為目標(biāo)文件構(gòu)建一個FileOutputStream逢并,完成寫入工作。示例代碼如下:

 public static void copyFileByStream(File source, File dest) throws IOException {
     try (InputStream is = new FileInputStream(source);
         OutputStream os = new FileOutputStream(dest);){
         byte[] buffer = new byte[1024];
         int length;
         while ((length = is.read(buffer)) > 0) {
                os.write(buffer, 0, length);
         }
     }
 }
2.Java NIO實現(xiàn)文件復(fù)制

? ? ? ?利用java.nio類庫提供的transferTo或transferFrom方法實現(xiàn)郭卫。示例代碼如下:

    public static void copyFileByChannel(File source, File dest) throws IOException {
        try (FileChannel sourceChannel = new FileInputStream(source).getChannel();
             FileChannel targetChannel = new FileOutputStream(dest).getChannel();){
                for (long count = sourceChannel.size() ;count>0 ;) {
                long transferred = sourceChannel.transferTo(
                    sourceChannel.position(), count, targetChannel);
                    sourceChannel.position(sourceChannel.position() + transferred);
                    count -= transferred;
            }
        }
    }

? ? ? ?當(dāng)然砍聊,Java標(biāo)準(zhǔn)類庫本身已經(jīng)提供了幾種Files.copy的實現(xiàn)。對于Copy的效率贰军,這個其實與操作系統(tǒng)和配置等情況相關(guān)玻蝌,總體上來說,NIO transferTo/From的方式 可能更快词疼,因為它更能利用現(xiàn)代操作系統(tǒng)底層機制俯树,避免不必要拷貝和上下文切換。

3.復(fù)制機制的分析

? ? ? ?首先贰盗,你需要理解用戶態(tài)空間(User Space)和內(nèi)核態(tài)空間(Kernel Space)许饿,這是操作系統(tǒng)層面的基本概念,操作系統(tǒng)內(nèi)核童太、硬件驅(qū)動等運行在內(nèi)核態(tài)空間米辐,具有相對高的特權(quán)胸完;而用戶態(tài)空間,則是給普通應(yīng)用和服務(wù)使用翘贮。你可以參考:https://en.wikipedia.org/wiki/User_space赊窥。當(dāng)我們使用輸入輸出流進(jìn)行讀寫時,實際上是進(jìn)行了多次上下文切換狸页,比如應(yīng)用讀取數(shù)據(jù)時锨能,先在內(nèi)核態(tài)將數(shù)據(jù)從磁盤讀取到內(nèi)核緩存算途,再切換到用戶態(tài)將數(shù)據(jù)從內(nèi)核緩存讀取到用戶緩存爪模。寫入操作也是類似,僅僅是步驟相反滋戳。所以斋竞,這種方式會帶來一定的額外開銷倔约,可能會降低IO效率。
? ? ? ?而基于NIOtransferTo的實現(xiàn)方式坝初,在Linux和Unix上浸剩,則會使用到零拷貝技術(shù),數(shù)據(jù)傳輸并不需要用戶態(tài)參與鳄袍,省去了上下文切換的開銷和不必要的內(nèi)存拷貝绢要,進(jìn)而可能提高應(yīng)用拷貝性能。注意拗小,transferTo不僅僅是可以用在文件拷貝中重罪,與其類似的,例如讀取磁盤文件哀九,然后進(jìn)行Socket發(fā)送剿配,同樣可以享受這種機制帶來的性能和擴展性提高。JDK的源代碼中勾栗,內(nèi)部實現(xiàn)和公共API定義也不是可以能夠簡單關(guān)聯(lián)上的惨篱,NIO部分代碼甚至是定義為模板而不是Java源文件,在build過程自動生成源碼围俘,簡單紹一下部分JDK代碼機制和如何繞過隱藏障礙砸讳。
? ? ? ?? 首先,直接跟蹤界牡,發(fā)現(xiàn)FileSystemProvider只是個抽象類簿寂,閱讀它的源碼能夠理解到,原來文件系統(tǒng)實際邏輯存在于JDK內(nèi)部實現(xiàn)里宿亡,公共API其實是通過ServiceLoader機制加載一系列文件系統(tǒng)實現(xiàn)常遂,然后提供服務(wù)。
? ? ? ?? 我們可以在JDK源碼里搜索FileSystemProvider和nio挽荠,可以定位到sun/nio/fs克胳,我們知道NIO底層是和操作系統(tǒng)緊密相關(guān)的平绩,所以每個平臺都有自己的部分特有文件系統(tǒng)邏輯。

image

? ? ? ?? 省略掉一些細(xì)節(jié)漠另,最后我們一步步定位到UnixFileSystemProvider → UnixCopyFile.Transfer捏雌,發(fā)現(xiàn)這是個本地方法。
? ? ? ?? 最后笆搓,明確定位到UnixCopyFile.c性湿,其內(nèi)部實現(xiàn)清楚說明竟然只是簡單的用戶態(tài)空間拷貝!
? ? ? ?所以满败,我們明確這個最常見的copy方法其實不是利用transferTo肤频,而是本地技術(shù)實現(xiàn)的用戶態(tài)拷貝。如何提高類似拷貝等IO操作的性能算墨,有一些寬泛的原則:在程序中宵荒,使用緩存等機制,合理減少IO次數(shù)(在網(wǎng)絡(luò)通信中米同,如TCP傳輸骇扇,window大小也可以看作是類似思路);使用transferTo等機制面粮,減少上下文切換和額外IO操作;盡量減少不必要的轉(zhuǎn)換過程继低,比如編解碼熬苍;對象序列化和反序列化,比如操作文本文件或者網(wǎng)絡(luò)通信袁翁,如果不是過程中需要使用文本信息柴底,可以考慮不要將二進(jìn)制信息轉(zhuǎn)換成字符串,直接傳輸二進(jìn)制信息粱胜。

4.掌握NIO Buffer

? ? ? ?Java為每種原始數(shù)據(jù)類型都提供了相應(yīng)的Buffer實現(xiàn)(布爾除外)柄驻,所以掌握和使用Buffer是十分必要的,尤其是涉及Direct Buffer等使用焙压,因為其在垃圾收集等方面的特殊性鸿脓,更要重點掌握。

image

Buffer有幾個基本屬性:
? ? ? ?? capcity涯曲,它反映這個Buffer到底有多大野哭,也就是數(shù)組的長度。
? ? ? ?? position幻件,要操作的數(shù)據(jù)起始位置拨黔。
? ? ? ?? limit,相當(dāng)于操作的限額绰沥。在讀取或者寫入時篱蝇,limit的意義很明顯是不一樣的贺待。比如,讀取操作時零截,很可能將limit設(shè)置到所容納數(shù)據(jù)的上限狠持;而在寫入時,則會設(shè)置容量或容量以下的可寫限度瞻润。
? ? ? ?? mark喘垂,記錄上一次postion的位置,默認(rèn)是0绍撞,算是一個便利性的考慮正勒,往往不是必須的。
? ? ? ?簡單梳理下Buffer的基本操作:我們創(chuàng)建了一個ByteBuffer傻铣,準(zhǔn)備放入數(shù)據(jù)章贞,capcity當(dāng)然就是緩沖區(qū)大小,而position就是0非洲,limit默認(rèn)就是capcity的大醒枷蕖;當(dāng)我們寫入幾個字節(jié)的數(shù)據(jù)時两踏,position就會跟著水漲船高败京,但是它不可能超過limit的大小梦染;如果我們想把前面寫入的數(shù)據(jù)讀出來赡麦,需要調(diào)用flip方法,將position設(shè)置為0帕识,limit設(shè)置為以前的position那里泛粹;如果還想從頭再讀一遍,可以調(diào)用rewind肮疗,讓limit不變晶姊,position再次設(shè)置為0。

4.Direct Buffer和垃圾收集

? ? ? ?? Direct Buffer:如果我們看Buffer的方法定義伪货,你會發(fā)現(xiàn)它定義了isDirect()方法们衙,返回當(dāng)前Buffer是否是Direct類型。這是因為Java提供了堆內(nèi)和堆外(Direct)Buffer超歌,我們可以以它的allocate或者allocateDirect方法直接創(chuàng)建砍艾。
? ? ? ?? MappedByteBuffer:它將文件按照指定大小直接映射為內(nèi)存區(qū)域,當(dāng)程序訪問這個內(nèi)存區(qū)域時將直接操作這塊兒文件數(shù)據(jù)巍举,省去了將數(shù)據(jù)從內(nèi)核空間向用戶空間傳輸?shù)膿p耗脆荷。我們可以使用FileChannel.map創(chuàng)建MappedByteBuffer,它本質(zhì)上也是種Direct Buffer。在實際使用中蜓谋,Java會盡量對Direct Buffer僅做本地IO操作梦皮,對于很多大數(shù)據(jù)量的IO密集操作,可能會帶來非常大的性能優(yōu)勢桃焕,因為:
? ? ? ?? Direct Buffer生命周期內(nèi)內(nèi)存地址都不會再發(fā)生更改剑肯,進(jìn)而內(nèi)核可以安全地對其進(jìn)行訪問,很多IO操作會很高效观堂。
? ? ? ?? 減少了堆內(nèi)對象存儲的可能額外維護(hù)工作让网,所以訪問效率可能有所提高。
? ? ? ? 但是請注意师痕,Direct Buffer創(chuàng)建和銷毀過程中溃睹,都會比一般的堆內(nèi)Buffer增加部分開銷,所以通常都建議用于長期使用胰坟、數(shù)據(jù)較大的場景因篇。使用Direct Buffer,我們需要清楚它對內(nèi)存和JVM參數(shù)的影響笔横。首先竞滓,因為它不在堆上,所以Xmx之類參數(shù)吹缔,其實并不能影響DirectBuffer等堆外成員所使用的內(nèi)存額度商佑,我們可以使用下面參數(shù)設(shè)置大小:

-XX:MaxDirectMemorySize=512M

? ? ? ? 從參數(shù)設(shè)置和內(nèi)存問題排查角度來看涛菠,這意味著我們在計算Java可以使用的內(nèi)存大小的時候莉御,不能只考慮堆的需要,還有Direct Buffer等一系列堆外因素俗冻。如果出現(xiàn)內(nèi)存不足,堆外內(nèi)存占用也是一種可能性牍颈。另外迄薄,大多數(shù)垃圾收集過程中,都不會主動收集Direct Buffer煮岁,它的垃圾收集過程讥蔽,就是基于我在專欄前面所介紹的Cleaner(一個內(nèi)部實現(xiàn))和幻象引用(PhantomReference)機制,其本身不是public類型画机,內(nèi)部實現(xiàn)了一個Deallocator負(fù)責(zé)銷毀的邏輯冶伞。對它的銷毀往往要拖到full GC的時候,所以使用不當(dāng)很容易導(dǎo)致OutOfMemoryError步氏。對于Direct Buffer的回收:
? ? ? ?? 在應(yīng)用程序中响禽,顯式地調(diào)用System.gc()來強制觸發(fā)。
? ? ? ?? 另外一種思路是,在大量使用Direct Buffer的部分框架中芋类,框架會自己在程序中調(diào)用釋放方法隆嗅,Netty就是這么做的,有興趣可以參考其實現(xiàn)(PlatformDependent0)侯繁。
? ? ? ?? 重復(fù)使用Direct Buffer胖喳。

5.跟蹤和診斷Direct Buffer內(nèi)存占用?

? ? ? ?因為通常的垃圾收集日志等記錄贮竟,并不包含Direct Buffer等信息丽焊,所以Direct Buffer內(nèi)存診斷也是個比較頭疼的事情。幸好咕别,在JDK8之后的版本技健,我們可以方便地使用Native Memory Tracking(NMT)特性來進(jìn)行診斷,你可以在程序啟動時加上下面參數(shù):

    -XX:NativeMemoryTracking={summary|detail}

? ? ? ?注意顷级,激活NMT通常都會導(dǎo)致JVM出現(xiàn)5%~10%的性能下降凫乖,請謹(jǐn)慎考慮。運行時弓颈,可以采用下面命令進(jìn)行交互式對比:

    // 打印NMT信息
    jcmd <pid> VM.native_memory detail 
    // 進(jìn)行baseline帽芽,以對比分配內(nèi)存變化
    jcmd <pid> VM.native_memory baseline
    // 進(jìn)行baseline,以對比分配內(nèi)存變化
    jcmd <pid> VM.native_memory detail.diff

? ? ? ?可以在Internal部分發(fā)現(xiàn)Direct Buffer內(nèi)存使用的信息翔冀,這是因為其底層實際是利用unsafe_allocatememory导街。嚴(yán)格說,這不是JVM內(nèi)部使用的內(nèi)存纤子,所以在JDK11以后搬瑰,其實它是歸類在other部分里。JDK 9的輸出片段如下控硼,“+”表示的就是diff命令發(fā)現(xiàn)的分配變化:

    -Internal (reserved=679KB +4KB, committed=679KB +4KB)
                  (malloc=615KB +4KB #1571 +4)
                  (mmap: reserved=64KB, committed=64KB)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末泽论,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子卡乾,更是在濱河造成了極大的恐慌翼悴,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,602評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件幔妨,死亡現(xiàn)場離奇詭異鹦赎,居然都是意外死亡,警方通過查閱死者的電腦和手機误堡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評論 2 382
  • 文/潘曉璐 我一進(jìn)店門古话,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人锁施,你說我怎么就攤上這事陪踩≌让牵” “怎么了?”我有些...
    開封第一講書人閱讀 152,878評論 0 344
  • 文/不壞的土叔 我叫張陵膊毁,是天一觀的道長胀莹。 經(jīng)常有香客問我,道長婚温,這世上最難降的妖魔是什么描焰? 我笑而不...
    開封第一講書人閱讀 55,306評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮栅螟,結(jié)果婚禮上荆秦,老公的妹妹穿的比我還像新娘。我一直安慰自己力图,他們只是感情好步绸,可當(dāng)我...
    茶點故事閱讀 64,330評論 5 373
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著吃媒,像睡著了一般瓤介。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上赘那,一...
    開封第一講書人閱讀 49,071評論 1 285
  • 那天刑桑,我揣著相機與錄音,去河邊找鬼募舟。 笑死祠斧,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的拱礁。 我是一名探鬼主播琢锋,決...
    沈念sama閱讀 38,382評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼呢灶!你這毒婦竟也來了吴超?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,006評論 0 259
  • 序言:老撾萬榮一對情侶失蹤鸯乃,失蹤者是張志新(化名)和其女友劉穎烛芬,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體飒责,經(jīng)...
    沈念sama閱讀 43,512評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,965評論 2 325
  • 正文 我和宋清朗相戀三年仆潮,在試婚紗的時候發(fā)現(xiàn)自己被綠了宏蛉。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,094評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡性置,死狀恐怖拾并,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤嗅义,帶...
    沈念sama閱讀 33,732評論 4 323
  • 正文 年R本政府宣布屏歹,位于F島的核電站,受9級特大地震影響之碗,放射性物質(zhì)發(fā)生泄漏蝙眶。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,283評論 3 307
  • 文/蒙蒙 一褪那、第九天 我趴在偏房一處隱蔽的房頂上張望幽纷。 院中可真熱鬧,春花似錦博敬、人聲如沸友浸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽收恢。三九已至,卻和暖如春祭往,著一層夾襖步出監(jiān)牢的瞬間伦意,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評論 1 262
  • 我被黑心中介騙來泰國打工链沼, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留默赂,地道東北人。 一個月前我還...
    沈念sama閱讀 45,536評論 2 354
  • 正文 我出身青樓括勺,卻偏偏與公主長得像缆八,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子疾捍,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,828評論 2 345

推薦閱讀更多精彩內(nèi)容