編輯:關於Android編程
上周發現蘑菇街IM-Android代碼裡面,一些地方代碼編寫不當,存在內存洩漏的問題,在和瘋紫交流的過程中,發現加深了一些理解,所以決定寫一下分析思路,相互學習。
一個不會被使用的對象,因為另一個正在使用的對象持有該對象的引用,導致它不能正常被回收,而停留在堆內存中。
最壞的情況,App可能會因為大量的內存洩漏而導致內存耗盡,引發Crash,如果內存未耗盡,App也會猶豫內存空間不足,出現頻繁的GC(垃圾回收),每次一出GC都是非常耗時的阻塞性操作,會造成設備非常嚴重的卡頓,給用戶的體驗就是,手機無論做什麼操作,都是卡的,這也是Android設備玩久了之後常見的現象。
在sendGoodsMessage方法中,使用了一個非靜態匿名內部類IMValueCallback,而這個非靜態匿名內部類對其外部類存在一個隱式引用,其外部類在銷毀之前,如果該非靜態內部類的sendMessage異步任務還未完成,將會導致外部類的內存資源無法正常釋放,造成了內存洩漏。
所以這個問題總結為: 非靜態內部類中線程生命周期不可控,能否正常回收完全由線程的生命周期決定。如果線程是永久運行的,那麼將永遠無法釋放,因為在Java中線程是垃圾回收機制的根源,在運行系統中DVM虛擬機總會硬件持有所有運行狀態的進程的引用,結果導致處於運行狀態的線程將永遠不會被回收。
非靜態內部類還有一種的情況的內存洩漏
非靜態內部類中創建了一個靜態實例,導致該實例的生命周期和應用ClassLoader級別,又因為該靜態實例又會隱式持有其外部類的引用,所以導致其外部類無法正常釋放,出現了洩漏問題。
深入分析
針對非靜態內部類引發的內存洩漏這個點,進行深入分析
為什麼非靜態內部類對外部類會存在一個隱式引用? 為什麼非靜態內部類中存在異步任務,可能會導致其對應的外部類內存資源無法正常釋放? 為什麼非靜態內部類中創建了一個靜態實例,會導致內存洩漏?
深入剖析-隱式引用
為什麼在非靜態匿名內部類中,我們可以訪問到外部類的testMethod方法?這就是隱式引用的作用。
通過編寫一個簡單的測試代碼,進行深入的分析
非靜態匿名內部類的源碼和編譯後的字節碼
args_size:這個代表著隱式引用的個數, 可以看出非靜態匿名內部類中確實持有外部類的引用。
非靜態內部類的源碼和編譯後的字節碼
靜態內部類的源碼和編譯後的字節碼
結論:非靜態內部類和非靜態匿名內部類中確實都持有外部類的引用, 靜態內部類中未持有外部類的引用
<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwPjxjb2RlPsTHw7TV4rj20v3Tw8rHtNO6zrb4wLS1xKO/PC9jb2RlPjwvcD4NCjxwPjxjb2RlPr/J0tTNqLn9xKK5vb3Wz9/Jz7T6wuu9+NDQt7Sx4NLro6zO0tXSwcvSu7j2sci9z9PQtPqx7dDUtcS0+sLro6y31s720rvPwqO6PC9jb2RlPjwvcD4NCjxwPjxjb2RlPjxpbWcgYWx0PQ=="" src="/uploadfile/Collfiles/20160913/20160913092034469.png" title="\" />
反編譯線上版本, c.class 這個就是MessagePresender類的字節碼
可以發現,在編譯器編譯過程中,幫我們隱式的傳入了this這個參數,這也是為什麼,我們平時在方法中能使用this這個關鍵字的原因。
了解了隱式引用,那麼為什麼它會是導致內存洩漏的根本原因? 這裡又得說明一下,虛擬機的垃圾回收策略。
java垃圾回收機制
垃圾回收機制:Java采用根搜索算法,當GC Roots不可達時,並且對象finalize沒有自救的情況下,才會回收。
回收對象:GC會收集那些不是GC roots且沒有被GC roots引用的對象。
(圖片來自網絡:http://www.infoq.com/cn/articles/jvm-memory-collection)
而這些引用就是對象之間的連線,垃圾回收的判定條件就在這些連線上,更詳細的說明就不在這裡描述,有興趣的自行Google,或者查閱《深入理解Java虛擬機》。
解決方案
通過上述的分析,要預防非靜態內部類的洩漏問題,就得管理好對象間的引用關系。
解決思路
去除隱式引用(通過靜態內部類來去除隱式引用) 手動管理對象引用(修改靜態內部類的構造方式,手動引入其外部類引用) 當內存不可用時,不執行不可控代碼(Android可以結合智能指針,WeakReference包裹外部類實例)
最後
並不是所有的內部類只能使用靜態內部類,只有在該內部類中的生命周期不可控制的情況下,我們要采用靜態內部類,其它時候大家可以照舊。
如果了解了這些,比如Context、Handler、Timer、靜態Acitivity、靜態View、Thread等等造成的洩漏,也是能融會貫通的,只要大家多想一下它們之間的引用關系即可。
本文實例講述了Android編程基於Contacts讀取聯系人的方法。分享給大家供大家參考,具體如下:Android Contacts簡介:這裡介紹安卓通訊錄數據庫。包括
Android是基於Java的,所以也分主線程,子線程!主線程:實現業務邏輯、UI繪制更新、各子線程串連,類似於將軍;子線程:完成耗時(聯網取數據、SD卡數據加載、後台長
本文實例為大家分享了Android計時器的三種方法,具體內容如下目錄:1、借助Timer實現2、調用handler.sendMessagedely(Message msg
我們知道在Android系統中,我們執行完耗時操作都要另外開啟子線程來執行,執行完線程以後線程會自動銷毀。想象一下如果我們在項目中經常要執行耗時操作,如果經常要開啟線程,