內(nèi)存泄漏
是指該被GC垃圾回收的宴杀,由于有另外一個對象仍然在引用它,導致無法回收蜗元,造成內(nèi)存泄漏或渤,過多的內(nèi)存泄漏會導致OOM。
不少人認為JAVA程序许帐,因為有垃圾回收機制劳坑,應該沒有內(nèi)存泄漏。我們已經(jīng)知道了成畦,如果某個對象距芬,從根節(jié)點可到達涝开,也就是存在從根節(jié)點到該對象的引用鏈,那么該對象是不會被 GC 回收的框仔。如果說這個對象已經(jīng)不會再被使用到了舀武,是無用的,我們依然持有他的引用的話离斩,就會造成內(nèi)存泄漏银舱,例如 一個長期在后臺運行的線程持有 Activity 的引用,這個時 候 Activity 執(zhí)行了 onDestroy 方法跛梗,那么這個 Activity 就是從根節(jié)點可到達并且無用的對象寻馏, 這個 Activity 對象就是泄漏的對象,給這個對象分配的內(nèi)存將無法被回收核偿。如果我們的java運行很久,而這種內(nèi)存泄漏不斷的發(fā)生诚欠,最后就沒內(nèi)存可用了。當然java的漾岳,內(nèi)存泄漏和C/C++是不一樣的轰绵。如果java程序完全結(jié)束后,它所有的對象就都不可達了尼荆,系統(tǒng)就可以對他們進行垃圾回收左腔,它的內(nèi)存泄漏僅僅限于它本身,而不會影響整個系統(tǒng)的捅儒。C/C++的內(nèi)存泄漏就比較糟糕了液样,它的內(nèi)存泄漏是系統(tǒng)級,即使該C/C++程序退出野芒,它泄漏的內(nèi)存也無法被系統(tǒng)回收蓄愁,永遠不可用了,除非重啟機器狞悲。
Android的一個應用程序的內(nèi)存泄漏對別的應用程序影響不大撮抓。為了能夠使得Android應用程序安全且快速的運行,Android的每個應用程序都會使用一個專有的Dalvik虛擬機實例來運行摇锋,它是由Zygote服務進程孵化出來的丹拯,也就是說每個應用程序都是在屬于自己的進程中運行的。Android為不同類型的進程分配了不同的內(nèi)存使用上限荸恕,如果程序在運行過程中出現(xiàn)了內(nèi)存泄漏的而造成應用進程使用的內(nèi)存超過了這個上限乖酬,則會被系統(tǒng)視為內(nèi)存泄漏,從而被kill掉融求,這使得僅僅自己的進程被kill掉咬像,而不會影響其他進程(如果是system_process等系統(tǒng)進程出問題的話,則會引起系統(tǒng)重啟)。
android常見內(nèi)存泄漏
1.Handler 引起的內(nèi)存泄漏
我們知道县昂,主線程的Looper對象不斷從消息隊列中取出消息肮柜,然后再交給Handler處理。如果在Activity中定義Handler對象倒彰,那么Handler肯定是持有Activty的引用审洞。而每個Message對象是持有Handler的引用的(Message對象的target屬性持有Handler引用),從而導致Message間接引用到了Activity待讳。如果在Activty destroy之后芒澜,消息隊列中還有Message對象,Activty是不會被回收的创淡。當然了痴晦,如果消息正在準備(處于延時入隊期間)放入到消息隊列中也是一樣的。
2.單例模式引起的內(nèi)存泄漏
單例模式在Android開發(fā)中會經(jīng)常用到辩昆,但是如果使用不當就會導致內(nèi)存泄漏阅酪。因為單例的靜態(tài)特性使得它的生命周期同應用的生命周期一樣長旨袒,如果一個對象已經(jīng)沒有用處了汁针,但是單例還持有它的引用,那么在整個應用程序的生命周期它都不能正常被回收砚尽,從而導致內(nèi)存泄漏施无。
3.非靜態(tài)內(nèi)部類創(chuàng)建靜態(tài)實例引起的內(nèi)存泄漏
靜態(tài)變量存儲在方法區(qū),它的生命周期從類加載開始必孤,到整個進程結(jié)束猾骡。一旦靜態(tài)變量初始化后,它所持有的引用只有等到進程結(jié)束才會釋放敷搪。在Android開發(fā)中兴想,靜態(tài)持有很多時候都有可能因為其使用的生命周期不一致而導致內(nèi)存泄漏,所以我們在新建靜態(tài)持有的變量的時候需要多考慮一下各個成員之間的引用關系赡勘,并且盡量少地使用靜態(tài)持有的變量嫂便,以避免發(fā)生內(nèi)存泄漏。當然闸与,我們也可以在適當?shù)臅r候講靜態(tài)量重置為null毙替,使其不再持有引用,這樣也可以避免內(nèi)存泄漏践樱。
4.非靜態(tài)匿名內(nèi)部類引起的內(nèi)存泄漏
非靜態(tài)內(nèi)部類厂画、匿名內(nèi)部類 都會持有外部類的一個引用,如果有一個靜態(tài)變量引用了非靜態(tài)內(nèi)部類或者匿名內(nèi)部類拷邢,導致非靜態(tài)內(nèi)部類或者匿名內(nèi)部類的生命周期比外部類(Activity)長袱院,就會導致外部類在該被回收的時候,無法被回收掉,引起內(nèi)存泄漏, 除非外部類被卸載(JVM自帶的類加載器所加載的類忽洛,在虛擬機的生命周期中抛人,始終不會被卸載,除非使用自定義的類加載器脐瑰,感興趣的同學可以研究一下)妖枚。
5.注冊/反注冊未成對使用引起的內(nèi)存泄漏
在andorid開發(fā)中,我們經(jīng)常會在Activity的onCreate中注冊廣播接受器苍在、EventBus等绝页,如果忘記成對的使用反注冊,可能會引起內(nèi)存泄漏寂恬。開發(fā)過程中應該養(yǎng)成良好的相關续誉,在onCreate或onResume中注冊,要記得相應的在onDestroy或onPause中反注冊初肉。
6.資源對象沒有關閉引起的內(nèi)存泄漏
當我們打開資源時酷鸦,一般都會使用緩存。比如讀寫文件資源牙咏、打開數(shù)據(jù)庫資源臼隔、使用Bitmap資源等等。當我們不再使用時妄壶,應該關閉它們摔握,使得緩存內(nèi)存區(qū)域及時回收。雖然有些對象丁寄,如果我們不去關閉氨淌,它自己在finalize()函數(shù)中會自行關閉。但是這得等到GC回收時才關閉伊磺,這樣會導致緩存駐留一段時間盛正。如果我們頻繁的打開資源,內(nèi)存泄漏帶來的影響就比較明顯了屑埋。
7.集合對象沒有及時清理引起的內(nèi)存泄漏
我們通常會把一些對象裝入到集合中豪筝,當不使用的時候一定要記得及時清理集合,讓相關對象不再被引用雀彼。如果集合是static壤蚜、不斷的往里面添加東西、又忘記去清理徊哑,肯定會引起內(nèi)存泄漏袜刷。
內(nèi)存優(yōu)化
1、Handler持有的引用最好使用弱引用莺丑,在Activity被釋放的時候要記得清空Message著蟹,取消Handler對象的Runnable墩蔓;
2、非靜態(tài)內(nèi)部類萧豆、非靜態(tài)匿名內(nèi)部類會自動持有外部類的引用奸披,為避免內(nèi)存泄露,可以考慮把內(nèi)部類聲明為靜態(tài)的涮雷;
3阵面、對于生命周期比Activity長的對象,要避免直接引用Activity的context洪鸭,可以考慮使用ApplicationContext样刷;?
4、廣播接收器览爵、EventBus等的使用過程中置鼻,注冊/反注冊應該成對使用;
5蜓竹、不再使用的資源對象Cursor箕母、File、Bitmap等要記住正確關閉俱济;?
6嘶是、集合里面的東西、有加入就應該對應有相應的刪除
如何檢查和分析內(nèi)存泄漏
因為內(nèi)存泄漏是在堆內(nèi)存中姨蝴,所以對我們來說并不是可見的俊啼。通常我們可以借助MAT、LeakCanary等工具來檢測應用程序是否存在內(nèi)存泄漏左医。
1、MAT是一款強大的內(nèi)存分析工具同木,功能繁多而復雜浮梢。
2.使用 LeakCanary 檢測Android 的內(nèi)存泄漏。
內(nèi)存泄漏防不勝防彤路,通過LeakCanary工具秕硝,我們能在開發(fā)測試階段發(fā)現(xiàn)絕大多數(shù)的內(nèi)存泄漏。這個工具是開源的洲尊,使用也非常方便远豺,而且能夠相對準確定位出是哪里出現(xiàn)內(nèi)存泄漏。