加载文件流_未关闭的文件流会引起内存泄露么?
專注于Java領(lǐng)域優(yōu)質(zhì)技術(shù),歡迎關(guān)注
來(lái)自:技術(shù)小黑屋
最近接觸了一些面試者,在面試過(guò)程中有涉及到內(nèi)存泄露的問(wèn)題,其中有不少人回答說(shuō),如果文件打開(kāi)后,沒(méi)有關(guān)閉會(huì)導(dǎo)致內(nèi)存泄露。當(dāng)被繼續(xù)追問(wèn),為什么會(huì)導(dǎo)致內(nèi)存泄露時(shí),大部分人都沒(méi)有回答出來(lái)。
本文將具體講一講 文件(流)未關(guān)閉與內(nèi)存泄露的關(guān)系。
什么是內(nèi)存泄露
- 定義:當(dāng)生命周期長(zhǎng)的實(shí)例L 不合理地持有一個(gè)生命周期短的實(shí)例S,導(dǎo)致S實(shí)例無(wú)法被正常回收
舉例說(shuō)明
上面的代碼可能會(huì)發(fā)生內(nèi)存泄露
- 我們調(diào)用AppSettings.getInstance.setup()傳入一個(gè)Activity實(shí)例
- 當(dāng)上述的Activity退出時(shí),由于被AppSettings中屬性mAppContext持有,進(jìn)而導(dǎo)致內(nèi)存泄露。
為什么上面的情況就會(huì)發(fā)生內(nèi)存泄露
- 以 Android 為例,GC 回收對(duì)象采用GC Roots強(qiáng)引用可到達(dá)機(jī)制。
- Activity實(shí)例被AppSettings.sInstance持有
- AppSettings.sInstance由于是靜態(tài),被AppSettings類持有
- AppSettings類被加載它的類加載器持有
- 而類加載器就是GC Roots的一種
- 由于上述關(guān)系導(dǎo)致Activity實(shí)例無(wú)法被回收銷毀。
驗(yàn)證是否引起內(nèi)存泄露
因此,想要證明未關(guān)閉的文件流是否導(dǎo)致內(nèi)存泄露,需要查看文件流是否是GC Roots強(qiáng)引用可到達(dá)。
示例代碼1(輔助驗(yàn)證GC 發(fā)生)
示例代碼2
這里我們這樣操作
分析上圖,我們發(fā)現(xiàn)
- FileInputStream 只被 FinalizerReference 這個(gè)類(GC Root)持有
- 上述持有的原因是,FileInputStream重寫了finalize,會(huì)被加入到FinalizerReference的析構(gòu)處理集合
- 上述引用會(huì)隨著Finalizer守護(hù)線程處理后解除,即FileInputStream實(shí)例徹底銷毀。
所以,我們?cè)賮?lái)操作一波,驗(yàn)證上面的結(jié)論。
- 然后利用工具執(zhí)行強(qiáng)制GC回收
- 過(guò)幾秒后,我們執(zhí)行heap dump。
- 我們使用 MAT 對(duì)上一步的dump文件進(jìn)行分析(需進(jìn)行格式轉(zhuǎn)換)
- 堆分析文件,查找MyBufferedReader或者FileInputStream或者InputStreamReader 沒(méi)有發(fā)現(xiàn)這些實(shí)例,說(shuō)明已經(jīng)GC回收
- 出于謹(jǐn)慎考慮,我們按照包名查找java.io在排除無(wú)關(guān)實(shí)例外,依舊無(wú)法找到testInputStream中的實(shí)例。再次證明已經(jīng)被GC回收
因而我們可以確定,正常的使用流,不會(huì)導(dǎo)致內(nèi)存泄露的產(chǎn)生。
當(dāng)然,如果你刻意顯式持有Stream實(shí)例,那就另當(dāng)別論了。
為什么需要關(guān)閉流
首先我們看一張圖
如上圖從左至右有三張表
- file descriptor table 歸屬于單個(gè)進(jìn)程
- global file table(又稱open file table) 歸屬于系統(tǒng)全局
- inode table 歸屬于系統(tǒng)全局
從一次文件打開(kāi)說(shuō)起
當(dāng)我們嘗試打開(kāi)文件/path/myfile.txt
1.從inode table 中查找到對(duì)應(yīng)的文件節(jié)點(diǎn)
2.根據(jù)用戶代碼的一些參數(shù)(比如讀寫權(quán)限等)在open file table 中創(chuàng)建open file 節(jié)點(diǎn)
3.將上一步的open file節(jié)點(diǎn)信息保存,在file descriptor table中創(chuàng)建 file descriptor
4.返回上一步的file descriptor的索引位置,供應(yīng)用讀寫等使用。
file descriptor 和流有什么關(guān)系
- 當(dāng)我們這樣FileInputStream("/sdcard/a.txt") 會(huì)獲取一個(gè)file descriptor。
- 出于穩(wěn)定系統(tǒng)性能和避免因?yàn)檫^(guò)多打開(kāi)文件導(dǎo)致CPU和RAM占用居高的考慮,每個(gè)進(jìn)程都會(huì)有可用的file descriptor 限制。
- 所以如果不釋放file descriptor,會(huì)導(dǎo)致應(yīng)用后續(xù)依賴file descriptor的行為(socket連接,讀寫文件等)無(wú)法進(jìn)行,甚至是導(dǎo)致進(jìn)程崩潰。
- 當(dāng)我們調(diào)用FileInputStream.close后,會(huì)釋放掉這個(gè)file descriptor。
因此到這里我們可以說(shuō),不關(guān)閉流不是內(nèi)存泄露問(wèn)題,是資源泄露問(wèn)題(file descriptor 屬于資源)。
不手動(dòng)關(guān)閉會(huì)怎樣
不手動(dòng)關(guān)閉的真的會(huì)發(fā)生上面的問(wèn)題么? 其實(shí)也不完全是。
因?yàn)閷?duì)于這些流的處理,源代碼中通常會(huì)做一個(gè)兜底處理。以FileInputStream為例
是的,在finalize方法中有調(diào)用close來(lái)釋放file descriptor.
但是finalize方法執(zhí)行速度不確定,不可靠
所以,我們不能依賴于這種形式,還是要手動(dòng)調(diào)用close來(lái)釋放file descriptor。
關(guān)閉流實(shí)踐
Java 7 之后,可以使用try-with-resource方式處理
Kotlin 可以使用use
當(dāng)然,還有最基礎(chǔ)的手動(dòng)關(guān)閉的形式
Reference
- https://stackoverflow.com/questions/26541513/why-is-it-good-to-close-an-inputstream
- https://www.reddit.com/r/learnjava/comments/577769/why_do_you_need_to_close_streams/
來(lái)自:https://droidyue.com/blog/2019/06/09/will-unclosed-stream-objects-cause-memory-leaks/
總結(jié)
以上是生活随笔為你收集整理的加载文件流_未关闭的文件流会引起内存泄露么?的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Multisim 版本针对3D元件库说明
- 下一篇: java请求注释_求达人给java代码【