...既然妹紙不在家,剛好最近一直在為項目做內存洩漏的優化工作,那...

2020-12-17 IT之家

結果因為妹紙公司臨時有事,她不得不回公司一趟…然後我也只能宅家裡了,既然妹紙不在家,剛好最近一直在為項目做內存洩漏的優化工作,那就來寫一點個人總結好了。

什麼是內存洩漏

對於不同的語言平臺來說,進行標記回收內存的算法是不一樣的,像Android(Java)則採用GC-Root的標記回收算法。下面這張圖就展示了Android內存的回收管理策略(圖來自Google 2011的IO大會)

圖中的每個圓節點代表對象的內存資源,箭頭代表可達路徑。當圓節點與GC Roots存在可達路徑時,表示當前資源正被引用,虛擬機是無法對其進行回收的(如圖中的黃色節點)。反過來,如果圓節點與GC Roots不存在可達路徑,則意味著這塊對象的內存資源不再被程序引用,系統虛擬機可以在GC過程中將其回收掉。

有了上面的內存回收的慄子,那麼接下來就可以說說什麼是內存洩漏了。從定義上講,Android(Java)平臺的內存洩漏是指沒有用的對象資源任與GC-Root保持可達路徑,導致系統無法進行回收。舉一個最簡單的慄子,我們在Activity的onCreate函數中註冊一個廣播接收者,但是在onDestory函數中並沒有執行反註冊,當Activity被finish掉時,Activity對象已經走完了自身的生命周期,應該被資源回收釋放掉,但由於沒有反註冊,此時Activity和GC-Root間任然有可達路徑存在,導致Activity雖然被銷毀,但是所佔用的內存資源卻無法被回收掉。類似的慄子其實有很多,不一一例舉了。

洩漏的源頭了解完內存洩漏的理論知識後,再來歸類一下內存洩漏的源頭。這裡我將其歸位以下三類:

自身編碼引起

由項目開發人員自身的編碼造成。

第三方代碼引起

這裡的第三方代碼包含兩類:第三方非開源的SDK和開源的第三方框架。

系統原因

由Android系統自身造成的洩漏,如像WebView、InputMethodManager等引起的問題,還有某些第三方ROM存在的問題。

洩漏的定位

內存洩漏不像閃退的BUG,排查起來相對要比較困難些,比較極端的情況是當你的應用OOM了才發現存在內存洩漏問題,到了這種情況才去排查處理問題的話,對用戶的影響就太大了。為此,我們能夠在編碼中儘早發現到問題就不要拖到上線之後才去填坑,下面介紹一些我比較常用排查內存洩漏的工具。

靜態代碼分析工具——Lint

Lint是Android Studio自帶的工具,使用姿勢很簡單Analyze -> Inspect Code然後選擇想要掃面的區域即可

對可能引起洩漏的編碼,Lint都會進行溫馨提示。

這裡只是拋磚引玉的介紹Lint,實際上玩法還有很多,大家可以自行拓展學習。除了Lint外,還有像FindBugs、Checkstyle等靜態代碼分析工具也是很不錯的。

嚴苛模式——StrictMode

StrictMode是Android系統提供的API,在開發環境下引入可以更早的暴露發現問題。官方文檔連結在下面(需要科學上網):

https://developer.android.com/reference/android/os/StrictMode.html

以官網的示例代碼為慄子,一般StrictMode只在測試環境下啟用,到了生產環境就會進行關閉,通常我們都會藉助BuildConfig.DEBUG來實現。

啟用StrictMode後,在過濾日誌的地方加上StrictMode的過濾Tag,如果手機連接著電腦進行開發,定期觀察一下StrictMode這個Tag下的日誌,一般你看到一大堆紅色告警的Log,就需要好好排查一下是否跟內存洩漏有關了。

LeakCanary

Square公司出品的內存分析工具,官方地址如下:

https://github.com/square/leakcanary/

LeakCanary和StrictMode一樣,需要在項目代碼中集成,不過代碼也非常簡單,如下的官方示例。

build.gradle引入,Application中加入兩三行代碼,即可搞定。以上只是簡單的引入,還有更多使用姿勢建議詳細閱讀它的Wiki下FAQ:

https://github.com/square/leakcanary/wiki/FAQ

我對使用LeakCanary有以下兩點感受:

當內存洩漏發生時,LeakCanary會彈窗提示並生成對應的堆存儲信息記錄,這讓我們對隱蔽的內存洩漏問題有了更加直觀的感覺,但從實際使用來看,LeakCanary的每個提示也並非是真正存在內存洩漏問題,要想確定是否存在問題我們還需要藉助MAT來進行最後的確定。

Android系統本身就存在一些問題導致應用內存洩漏,LeakCanary的 AndroidExcludedRefs 類幫助我們處理了不少這類問題。

Android Memory Monitor

AndroidStudio提供的工具,用於監控應用的內存使用狀態,在開發中也是非常實用的工具,可以用來列印出內存的狀態信息。

列印獲得的內存信息如下,可以通過右上角的綠色三角形按鈕去分析洩漏的Activity和一些重複的字符串,目前只支持這兩個,希望Google後面能夠加入更多可選分析規則

同樣,這裡也只是拋磚引玉的簡單介紹,關於它的使用在官方文檔已經說得很詳細了,需要的童鞋自行查看下方連結(需科學上網):

https://developer.android.com/studio/profile/am-hprof.html

Memory Analyzer (MAT)

老牌子分析工具,可以從 http://www.eclipse.org/mat/ 下載獲得,網上關於MAT使用的文章好多,大家可以自行查找。上面的Android Memory Monitor生成的對儲存信息文件可以配置MAT一起來分析使用,由於Android Memory Monitor生成的hprof文件不是標準格式,所以需要做一下轉換,然後導入MAT

然後通過OQL先定位出洩漏的對象

通過排除除了強引用之外的其他引用鏈,最後分析到GC Root的位置

MAT使用起來相對繁瑣,但不失為定位根源問題的利器。

adb shell命令

使用adb shell dumpsys meminfo [PackageName],可以列印出指定包名的應用內存信息

使用該命令可以很直觀的觀察到Activity的洩漏問題,是我平常分析比較常用的一種方式。除了使用命令外,AndroidStudio也提供了下面的功能,和使用命令是一樣效果的。

如果對adb shell命令感興趣,更多的信息可以看下面提供的資源:

http://adbshell.com/

https://github.com/mzlogin/awesome-adb

以上就是我在做內存洩漏分析的時候會用到的工具,通常都是結合起來用,畢竟每個工具都有優缺點,通過使用多個工具互補分析問題可以極大的提高我們的效率和最終取得的效果。

洩漏的解決策

略聊完工具,最後來談談內存洩漏問題的解決策略。我把它總結為以下三點:

完成需求功能開發後,再去優化內存洩漏問題;

洩漏源有多處時,核心功能產生的洩漏優先處理,用戶使用頻繁的功能引起的洩漏優先處理;

處理洩漏避免影響原有的代碼邏輯,優化過後最好能夠讓測試童鞋過一遍相關的功能,避免引入未知的BUG;

總結

對於如何在編碼上去解決內存洩漏問題,網絡上有提供了很多場景及其解決方案,大家可以自行藉助搜尋引擎。通過掌握分析方法和對洩漏場景及其解決方案的積累,相信大家處理內存洩漏問題是遊刃有餘的。當然,也並不是所有內存洩漏問題我們都能夠進行處理,就例如第二章節提到的洩漏源頭是由第三方代碼引起時,我們就顯得無能為力了。最近在排查的過程中就發現不少第三方SDK存在洩漏問題,遇上這種情況就得找找可替代的SDK進行更換了。以上就是我做內存洩漏分析的一些心得總結,如果有錯誤和不足,還請大家指出。

相關焦點

  • Android 性能優化之內存洩漏,使用MAT&LeakCanary解決問題
    ,內存這個小妮子比較調皮,每個月總有那麼幾次洩漏或者溢出(OOM),這篇文章所講的是內存溢出,這裡要注意,內存溢出和內存洩漏是兩個概念,這點大家要清楚,當然,內存洩漏過多會導致內存洩漏,至於什麼是內存洩漏呢,大家都知道我們的內存回收機制是GC,所以用一句話來概括:GC回收機制所無法回收的垃圾對象。
  • 高頻面試點:Android性能優化之內存優化(上篇)
    但是當Zygote進程在fork第一個應用程式進程之前,會將已經使用的那部分堆內存劃分為一部分,還沒有使用的堆內存劃分為另一部分,也就是Allocation Space。但無論是應用程式進程,還是Zygote進程,當他們需要分配對象時,都是在各自的Allocation Space堆上進行。
  • Android 內存洩漏探討
    內存洩漏大家都不陌生了,簡單粗俗的講,就是該被釋放的對象沒有釋放,一直被某個或某些實例所持有卻不再被使用導致 GC 不能回收。最近自己閱讀了大量相關的文檔資料,打算做個 總結 沉澱下來跟大家一起分享和學習,也給自己一個警示,以後 coding 時怎麼避免這些情況,提高應用的體驗和質量。
  • 用valgrind定位內存洩漏
    緣起年前,寫了使用mtrace定位內存洩漏,在留言中,有讀者提到了希望介紹valgrind,那好,今天就介紹使用valgrind定位內存洩漏。大約2-3年前,楊同學讓我幫做模擬面試,他求職的是C++崗位,我問了這樣一個問題:在你的項目中,你是如何定位內存洩漏的呢?
  • 如何通過wrap malloc定位C/C++程序的內存洩漏
    理論上,只要我們足夠小心,在每次申請的時候,都牢記釋放,那這個世界就清淨了,但現實往往沒有那麼美好,比如拋異常了,釋放內存的語句執行不到,又或者某菜鳥程式設計師不小心埋了一個雷,所以,我們必須直面真實的世界,那就是我們會遭遇內存洩漏。怎麼查內存洩漏?
  • Android性能優化:帶你全面實現內存優化
    定義優化處理 應用程式的內存使用、空間佔用2. 作用避免因不正確使用內存 & 缺乏管理,從而出現 內存洩露(ML)、內存溢出(OOM)、內存空間佔用過大 等問題,最終導致應用程式崩潰(Crash)3.
  • Android性能優化--內存優化
    本文來自Dotry投稿,連結:https://www.jianshu.com/p/38b627adaecd上一篇文章關於Android性能優化--啟動優化探討了啟動優化相關的知識點,在本篇將介紹內存優化的相關優化。
  • 檢查是否內存洩漏的方法
    這個方法呢,我覺得應該是用在開發階段的,就是每當我們寫好一個模塊或者一個功能時,就檢查一下,確保無誤在接著往下做。網上也有一些檢查內存洩漏的工具,一搜一大堆資料就出來了,我就不介紹了。,這樣就堆中的這個 1024位元組的內存 就一直被佔用著,所以就造成了內存洩漏。
  • Android 輕鬆解決內存洩漏
    的強引用就會導致內存洩漏。,造成內存洩漏。這必然會導致一系列問題,如果你的 app 進程設計上是長駐內存的,那即使 app 切到後臺,這部分內存也不會被釋放。解決方法不要在類初始化時初始化靜態成員,也就是可以考慮懶加載。架構設計上要思考是否真的有必要這樣做,儘量避免。如果架構需要這麼設計,那麼此對象的生命周期你有責任管理起來。
  • 最全的Android內存優化技巧
    這樣做不僅能減小APK的大小,還可以在一定程度上減少內存的開銷2.顯然,這還使得每級Generation的內存區域可用空間變小,GC就會更容易被觸發,容易出現內存抖動,從而引起性能問題關於內存洩漏相關問題具體見:寫給程式設計師的內存洩漏治理手冊內存使用策略優化優化布局層次,減少內存消耗越扁平化的視圖布局,佔用的內存就越少,效率越高。
  • 理解閉包與內存洩漏
    三、內存洩漏內存洩漏常常與閉包緊緊聯繫在一起,很容易讓人誤以為閉包就會導致內存洩漏。其實閉包只是讓內存常駐,而濫用閉包才會導致內存洩漏。當然並不是說內存dump文件的大小不斷增大就存在內存洩漏,如果應用的訪問量確實在一直增大,那麼內存曲線只增不減也屬於正常情況,我們只能根據具體情況判斷是否存在內存洩漏的可能。
  • 線程上下文類加載器ContextClassLoader內存洩漏隱患
    這裡基於這兩個Issue描述的內容,對ContextClassLoader內存洩漏隱患做一次復盤。ClassLoader相關的內容一個JVM實例(Java應用程式)裡面的所有類都是通過ClassLoader加載的。
  • JavaScript內存管理機制以及四種常見的內存洩漏解析
    你的代碼不應該依賴於當前基本數據類型的大小。編譯器會插入與作業系統交互的代碼,並同時在棧上申請要存儲的變量所需的字節數。在上面這個例子中,編譯器知道每個變量準確的內存地址。事實上,當我們寫入變量n時,它就會被翻譯成類似「內存地址4127963」這樣的內部信息。注意,如果嘗試訪問x[4],那就會訪問到與m相關的數據。
  • 【221期】面試官:談談內存洩漏和內存溢出的聯繫與區別
    二、內存洩漏原因1、在內存中供用戶使用的空間分為三部分:靜態存儲區:靜態存儲區數據在程序開始就已經分配好了內存,執行過程中,它們所佔的存儲單元是固定的,在程序結束時就 釋放,所以該區數據一般為全局變量。動態存儲區:動態存儲區數據是在程序的執行過程中根據需要動態分配和動態釋放的存儲單元。
  • iOS內存洩漏的檢測及定位工具-Instruments
    ,經常利用Xcode進行debug測試,某次測試debug時APP突然閃退,控制臺列印log最後一行提示:「Terminated due to memory issue」原來是內存問題導致,後來經過和開發小哥哥溝通,分析得知可能是內存洩漏,於是利用Instruments工具進行檢測發現果然存在內存洩漏,並成功定位到內存洩漏的代碼所在,問題完美解決。
  • 獨家|Linux進程內存用量分析之堆內存篇
    而對於進程內存膨脹這類問題,原因通常有三個:1.內存洩漏。2.分配器管理的空閒內存較多而造成的內存空洞。3.有未統計使用的未知內存佔用。內存洩漏問題可以使用一些工具來檢測。但是對於後兩種問題,卻一直沒有比較通用的方法去確定。本文將介紹幾種內存洩漏檢測工具,並通過實際例子介紹一種分析堆內存佔用量的工具和方法,幫助定位內存膨脹問題。
  • iOS 內存洩漏場景與解決方案
    只要 self 不釋放,dealloc 就不會執行,timer 就無法在 dealloc 中銷毀,self 始終被強引用,永遠得不到釋放,循環矛盾,最終造成內存洩漏。那麼如果只把 timer 作為局部變量,而不是屬性呢?
  • C語言中的指針和內存洩漏
    即使您的模塊編碼得足夠好,也可能由於某個共存模塊執行某些內存操作而具有不正確的行為。下面的示例代碼片段也可以說明這種場景。這還會導致不希望的輸出。內存洩漏內存洩漏可能真正令人討厭。下面的列表描述了一些導致內存洩漏的場景。●重新賦值我將使用一個示例來說明重新賦值問題。
  • Linux平臺中調試C/C++內存洩漏方法 (騰訊和MTK面試的時候問到的)
    自從 70 年代末期以來,C/C++ 程式設計師就一直討論此類錯誤,但其影響在 2007 年仍然很大。與許多其他類型的常見錯誤不同,內存錯誤通常具有隱蔽性,即它們很難再現,症狀通常不能在相應的原始碼中找到。例如,無論何時何地發生內存洩 漏,都可能表現為應用程式完全無法接受,同時內存洩漏不是顯而易見[1]。存在內存錯誤的 C 和 C++ 程序會導致各種問題。
  • C語言內存洩漏,看完這篇就懂了!
    可能不少開發者都遇到過內存洩漏導致的網上問題,具體表現為單板在現網運行數月以後