加载文件流_未关闭的文件流会引起内存泄露么?
專注于Java領(lǐng)域優(yōu)質(zhì)技術(shù),歡迎關(guān)注
來自:技術(shù)小黑屋
最近接觸了一些面試者,在面試過程中有涉及到內(nèi)存泄露的問題,其中有不少人回答說,如果文件打開后,沒有關(guān)閉會(huì)導(dǎo)致內(nèi)存泄露。當(dāng)被繼續(xù)追問,為什么會(huì)導(dǎo)致內(nèi)存泄露時(shí),大部分人都沒有回答出來。
本文將具體講一講 文件(流)未關(guān)閉與內(nèi)存泄露的關(guān)系。
什么是內(nèi)存泄露
- 定義:當(dāng)生命周期長(zhǎng)的實(shí)例L 不合理地持有一個(gè)生命周期短的實(shí)例S,導(dǎo)致S實(shí)例無法被正常回收
舉例說明
上面的代碼可能會(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í)例無法被回收銷毀。
驗(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è)賮聿僮饕徊?#xff0c;驗(yàn)證上面的結(jié)論。
- 然后利用工具執(zhí)行強(qiáng)制GC回收
- 過幾秒后,我們執(zhí)行heap dump。
- 我們使用 MAT 對(duì)上一步的dump文件進(jìn)行分析(需進(jìn)行格式轉(zhuǎn)換)
- 堆分析文件,查找MyBufferedReader或者FileInputStream或者InputStreamReader 沒有發(fā)現(xiàn)這些實(shí)例,說明已經(jīng)GC回收
- 出于謹(jǐn)慎考慮,我們按照包名查找java.io在排除無關(guān)實(shí)例外,依舊無法找到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)全局
從一次文件打開說起
當(dāng)我們嘗試打開文件/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)檫^多打開文件導(dǎo)致CPU和RAM占用居高的考慮,每個(gè)進(jìn)程都會(huì)有可用的file descriptor 限制。
- 所以如果不釋放file descriptor,會(huì)導(dǎo)致應(yīng)用后續(xù)依賴file descriptor的行為(socket連接,讀寫文件等)無法進(jìn)行,甚至是導(dǎo)致進(jìn)程崩潰。
- 當(dāng)我們調(diào)用FileInputStream.close后,會(huì)釋放掉這個(gè)file descriptor。
因此到這里我們可以說,不關(guān)閉流不是內(nèi)存泄露問題,是資源泄露問題(file descriptor 屬于資源)。
不手動(dòng)關(guān)閉會(huì)怎樣
不手動(dòng)關(guān)閉的真的會(huì)發(fā)生上面的問題么? 其實(shí)也不完全是。
因?yàn)閷?duì)于這些流的處理,源代碼中通常會(huì)做一個(gè)兜底處理。以FileInputStream為例
是的,在finalize方法中有調(diào)用close來釋放file descriptor.
但是finalize方法執(zhí)行速度不確定,不可靠
所以,我們不能依賴于這種形式,還是要手動(dòng)調(diào)用close來釋放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/
來自:https://droidyue.com/blog/2019/06/09/will-unclosed-stream-objects-cause-memory-leaks/
總結(jié)
以上是生活随笔為你收集整理的加载文件流_未关闭的文件流会引起内存泄露么?的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Multisim 版本针对3D元件库说明
- 下一篇: java请求注释_求达人给java代码【