Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android 非靜態內部類導致內存洩漏原因深入剖析

Android 非靜態內部類導致內存洩漏原因深入剖析

編輯:關於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等等造成的洩漏,也是能融會貫通的,只要大家多想一下它們之間的引用關系即可。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved