本文內容整理自公衆號騰訊Bugly,感謝原做者的分享。php
對於Android應用來講,內存向來是比較重要的性能指標。內存佔用太高,會影響應用的流暢度,甚至引起OOM,很是影響用戶體驗。所以,內存優化也向來是行業內的重點工做項和難點工做項。html
手Q在很早以前就開發了不少內存優化技術:android
1)自研內存泄露檢測系統 LeakInspector天網:程序員
LeakInspector是一套完整內存泄露檢測系統:可以自動檢測應用內存泄露問題;並提供兜底回收以及自動提單功能;數據庫
2)圖片引用大圖告警:緩存
可以自動檢測出業務圖片不合理使用:好比解碼的圖片尺寸大於顯示尺寸2倍以上等問題。推進業務進行專項優化;安全
3)內存觸頂監控:微信
可以檢測出內存不足時佔用內存較高的業務場景,並定位到相應的頁面,推進業務進行優化。網絡
以上這些技術都取得了很好的內存優化效果,但他們的特色是:主要針對明顯的內存問題,缺乏更深刻的內存分析。數據結構
所以,手Q內存問題也一直存在,主要表如今如下兩方面:
1)手Q的平均內存一直持續增加,版本間增幅較高,手Q一月一個版本,平均每版本增加大概5.3M;
2)用戶的OOM率大概0.1%。
此次咱們主要從監控和清理兩個角度出發,系統化的進一步優化手Q內存:
1)統一緩存監控:開發實現全面的內存緩存監控系統,可以更細緻的監控手Q內存緩存使用狀況,及時發現輕度不合理問題,推動優化;
2)內存清理 在監控的基礎上,開發實現自動清理機制:一方面統一調度手Q各業務主動清理內存,另外一方面,經過深刻的技術研究,實現系統內存清理技術。
經過監控和清理相互配合,咱們最終實現了優化手Q總體內存,下降OOM率的效果。如下是詳細方案。
學習交流:
- 即時通信開發交流羣:320837163[推薦]
- 移動端IM開發入門文章:《新手入門一篇就夠:從零開發移動端IM》
(本文同步發佈於:http://www.52im.net/thread-1524-1-1.html)
《騰訊技術分享:Android手Q的線程死鎖監控系統技術實踐》
《微信團隊原創分享:Android內存泄漏監控和優化技巧總結》
《QQ音樂團隊分享:Android中的圖片壓縮技術詳解(上篇)》
《QQ音樂團隊分享:Android中的圖片壓縮技術詳解(下篇)》
《微信客戶端團隊負責人技術訪談:如何着手客戶端性能監控和優化》
統一緩存監控主要包含圖片緩存監控和業務對象緩存監控兩部分:
1)圖片緩存監控主要關注Bitmap的引用,定位圖片問題;
2)業務對象緩存監控,主要監控手Q各業務對象緩存,及時發現緩存問題。
3.1圖片緩存監控
對於Android應用來講,Bitmap向來是內存的佔用大戶。在手Q中平均有300+ Bitmap對象。
統計顯示:Bitmap引用內存佔手Q總內存40%左右:
減小圖片佔用內存,須要規範圖片緩存的使用。前期咱們在手Q中封裝實現了圖片專用多級緩存QQLruChe,並要求各業務必須使用全局圖片專用緩存來緩存圖片。一方面能夠方便調控圖片緩存業務,另外一方面,經過淘汰以及清理策略,能夠有效控制圖片緩存大小。但因爲手Q業務衆多,業務獨立開闢圖片緩存的狀況仍是時有發生。所以咱們開發了一套圖片緩存監控系統,及時檢測出圖片緩存私藏問題,同時也監控圖片的其餘不合理使用。
圖片緩存監控使用內存快照技術實現,分爲終端數據採集和後臺數據分析兩部分。
流程以下所示:
終端數據採集:客戶端實時檢測當前可用內存,當可用內存不足時,自動生成內存快照文件,上報到後臺。
後臺數據分析:在後臺,咱們實現了一套Hprof文件分析以及Bitmap引用歸併技術,批量分析內存快照文件,輸出Bitmap引用鏈並進行歸類統計,過濾全局圖片專用緩存以及View層引用後,分析出存在圖片緩存私藏的業務。
實現圖片緩存監控過程當中咱們主要遇到如下幾個難點:
1)內存快照文件大,約300M左右:
內存文件過大會致使上傳流量和存儲成本比較大,並且上傳耗時長。針對這個問題,一方面,咱們對大盤用戶採樣上報,並提供良好的用戶交互。另外一方面咱們深刻分析內存快照採集原理,自研miniDump工具,經過native hook技術在生成內存快照時剔除了tyte[]數據,從而使文件體積減小70%;
2)內存快照文件人工分析成本高:
經過MAT人工分析內存快照文件耗時費力,並且分析數量有限,用戶上報的內存文件不少,沒法定位top問題。針對該問題,咱們深刻研究MAT插件技術,自研引用鏈分析以及Bitmap引用歸併工具,自動化分析內存快照文件,歸類Bitmap圖片引用。
經過圖片監控系統,咱們有效檢測出如下幾類業務問題:
1)全局圖片專用緩存佔用空間大,存在優化空間:
bitmap引用鏈歸併發現全局圖片專用緩存佔較高。同時,咱們也統計了OOM用戶全局圖片緩存的內存量,平均約10M左右。所以有必要在內存不足時,自動trimToSize,釋放內存空間;
2)業務bug——邏輯完成後,沒有及時釋放圖片引用:
業務邏輯存在問題,好比有幾類業務在頁面退出後,沒有及時釋放背景圖資源引用;
3)業務私自開闢圖片緩存:
業務獨立開闢緩存cache緩存bitmap,沒有使用全局圖片專用緩存;
4)業務緩存數據對象中引用圖片:
業務內存緩存的數據對象中,含有bitmap成員,內存空間大。可優化爲緩存key,bitmap對象存到全局圖片專用緩存中;
5)圖片靜態引用:
定義靜態的Bitmap或者Drawable對象,進程週期內,對象所引用的資源都沒法釋放。
在手Q730版本,圖片緩存監控系統檢測出32例業務問題,提單26例,累計節省內存約23M。
3.2業務對象緩存監控
業務對象緩存監控主要是經過實現自定義集合類,實時上報各業務內存緩存使用狀況到後臺。在後臺分析歸併,從而定位業務緩存問題。
如上圖所示,業務對象緩存主要分爲終端數據採集、後臺數據分析、緩存清理三部分:
終端數據採集: 經過自定義實現QQHashMap,QQConCurrentHashMap,QQLruCache等集合類,在系統原有集合類基礎上,封裝統計功能,實時統計程序運行期間各緩存的內存指標:插入次數,查詢次數,刪除次數,遍歷次數,命中次數,未命中次數,緩存使用率,內存佔用等;
後臺分析:分析終端上報的用戶數據,對各業務緩存進行歸類統計,統計出平均內存佔用,最大內存佔用,內存佔用中位數,緩存命中率,緩存浪費率等指標;
內存清理:監控系統在監控的基礎上,也增長了清理接口。當檢測到當前可用堆內存比較低,用戶處於內存高負荷狀態時,統一調度清理邏輯,進行內存自清理優化。
經過統一緩存監控,咱們檢測出了不少業務緩存問題,主要可歸爲如下三類。
1)緩存浪費率高:
典型案例1:手Q表情某類緩存,平均浪費率超過88%,至關於緩存1000個對象,有800+沒有使用過;
典型案例2:某紅包模板緩存,存儲後從不訪問,浪費率100%。
針對這類問題,推進業務優化內存緩存結構,去除無用緩存,優化緩存方案,以下降浪費率。
2)緩存內存佔用大:
典型案例1:手Q某新聞類圖片緩存,私自緩存Bitmap,最大佔用內存15M,佔全部圖片緩存的35%;
典型案例2:錢包類背景圖緩存,內存佔用約1M左右,使用後未及時釋放。
針對這類問題,對於緩存圖片的業務,推進業務接入全局圖片專用緩存;對於非圖片類業務,接入自動清理,及時釋放內存空間。
3)緩存結構存在優化空間:
典型案例1:討論組成員緩存,設計爲LRUCache可淘汰緩存,可是用戶不曾用滿過。初始開闢空間過大;
典型案例2:未讀消息緩存,極端用戶緩存個數超過9000個,無數量上限控制。
針對這類問題,推進業務更新或者優化緩存結構,增長上限控制等。
統一監控,能夠有效發現業務緩存問題進行專項優化。但監控具備必定的滯後性,所以在監控的基礎上,咱們同時也增長了內存清理控制模塊。
內存清理主要分爲業務內存清理以及系統內存清理。業務內存清理,包含統一圖片緩存清理,以及業務緩存對象清理兩部分。這裏前文已簡單介紹。接下來咱們介紹下兩例系統相關的內存清理技術:系統ClassLoader內存清理和系統預加載圖片清理。
4.1系統ClassLoader內存清理
前期,咱們分析了不少內存快照,發現一個共性的問題:在內存快照中有個ZipFile對象,內存佔用一直超過2.6M。這個zipFile被系統類ClassLoader引用。
經過分析系統源碼,咱們發現ZipFile記錄了安裝包全部的類文件信息,手Q安裝包中有超過15000個文件,文件越多,zipFile佔用內存就越大。
咱們進一步分析ClassLoader相關源碼,發現只有在調用ClassLoader的findResource方法查找圖片等安裝包內資源時,纔會使用到ZipFile的內容,未發現其餘使用場景。同時,經過findResource方式查找資源存在必定的弊端:耗時很長,在Android系統上不推薦使用。
詳情分析可參考:
http://blog.nimbledroid.com/2016/04/06/slow-ClassLoader.getResourceAsStream-zh.html
綜合評估,能夠清理ClassLoader引用的這塊內存。
清理主要面臨如下幾個難點:
1)Android系統碎片化嚴重,兼容性問題比較突出:
不一樣版本,zipFIle成員變量的位置以及變量名不一樣。zipFile初始化時機改變:4.3之前建立時即初始化,4.3以後,第一次訪問纔會初始化。各廠商對系統API內部修改沒法預期;
2)強行清理,可能致使功能異常:
系統內部代碼邏輯可能會受到影響,並且影響沒法預期。手Q當前使用ClassLoader查找資源的業務功能會受到影響。後期新增業務沒法預期,清理會致使系統功能失效;
3)清理後再次加載zipFile耗時長,可能致使卡頓。
下圖是咱們清理系統ClassLoader的實現方案,採用代理,兜底,緩存,上報等手段逐一攻克以上難點,完美實現清理系統ClassLoader內存的效果。
1)針對兼容性問題,咱們經過反射代理替換了系統的ZipFile爲HookZipFile,替換完成後,清理掉zipFile內存。替換機制兼容系統不一樣版本以及特殊機型,對系統邏輯無影響。
2)針對清理致使的功能異常,咱們實現了兜底能力,下次訪問時,會從新建立zipFile。
3)針對耗時問題,內部封裝實現緩存功能。並針對業務訪問增長堆棧上報,及時推進業務改用其餘方式獲取資源。
內存清理方案,經過內部兼容性測試,發佈後外網無crash問題,經過不斷迭代,兼容率達到100%。而且內存清理效果明顯,平均清理內存量約2.6M。
4.2系統預加載圖片清理
系統預加載圖片緩存是zygote進程初始化時,經過preloadResources()預加載的通用圖片資源,後續android應用進程都是從Zygote fork出來的,因此就繼承了這部分預加載的圖片資源。因爲是靜態強引用,這部分圖片資源會一直佔用內存空間。
預加載的好處在於系統只在zygote執行一次加載操做,全部應用用到該資源不須要再從新加載,減小資源加載耗時。與此同時,sPreloadedDrawables屬於靜態對象,會一直引用圖片緩存,因此該系統機制會佔用較高內存,在有些系統上,內存佔用空間超過20M。
所以這裏存在內存優化的空間,當內存佔用高時,能夠主動清理掉這部份內存,以便釋放可觀的堆內存空間,減小內存耗盡的風險。經過分析drawable加載機制的源碼,咱們瞭解到若是預加載的資源沒有在sPreloadedDrawables中找到,會從新decode解碼加載,不會影響現有功能。
所以清理後的風險可控,主要面臨的難點是兼容性問題:
1)系統API變更較多:
sPreloadDrawables數據結構類型,對象存儲位置,不一樣API版本之間都有改動;
2)廠商自定義修改較多:
好比:小米7.0系統以及華爲部分機型各自擴展了ResourceImpl實現,自定義了本身的資源加載基類,致使沒法定位到sPreloadDrawable;OPPO部分機型,修改了sPreloadDrawable類的屬性等等。
下圖是咱們清理系統預加載圖片緩存的實現方案,經過反射替換的方式,攔截替換系統的預加載緩存爲自定義圖片緩存,內部管理圖片加載,在內存不足時,及時清理預加載圖片緩存。
針對兼容性問題,咱們實現了一套完備的兼容性方案:
1)替換前兼容檢測;
2)系統版本兼容處理;
3)特定機型兼容處理;
4)失敗上報統計,不斷兼容。
系統預加載圖片清理,經過不斷迭代,已經能夠兼容幾乎全部機型。版本發佈後,未引入系統功能異常以及外網crash問題。內存清理量比較可觀:平均在15M左右,最高達到25M。
5.1橫向對比
在灰度階段,咱們對用戶進行了ABTest測試,一半用戶接入內存優化邏輯,一半用戶不接入。大盤上報統計顯示:優化用戶OOM率明顯低於未優化用戶:OOM率由0.09%降至0.053%。
內存分佈方面,內存優化顯著下降了高內存用戶佔比。由3.05%降至1.7%。高內存用戶是指當前可用內存不足20%的用戶,是OOM高發用戶羣,下降這部分用戶比例,可有效下降OOM率。
5.2縱向對比
咱們從7.3.0版本接入內存優化,從版本迭代來看,優化效果顯著:OOM率由0.09%左右下降至0.047%左右,降幅47%:
手Q版本間平均內存增幅明顯放緩,版本增幅由5.8M左右降至1.14M左右:
[1] QQ、微信團隊原創技術文章:
《騰訊技術分享:Android版手機QQ的緩存監控與優化實踐》
《微信團隊分享:iOS版微信的高性能通用key-value組件技術實踐》
《微信團隊分享:iOS版微信是如何防止特殊字符致使的炸羣、APP崩潰的?》
《騰訊團隊分享 :一次手Q聊天界面中圖片顯示bug的追蹤過程分享》
《微信團隊分享:微信Android版小視頻編碼填過的那些坑》
《微信團隊披露:微信界面卡死超級bug「15。。。。」的前因後果》
《月活8.89億的超級IM微信是如何進行Android端兼容測試的》
《微信客戶端團隊負責人技術訪談:如何着手客戶端性能監控和優化》
《微信團隊原創分享:Android版微信的臃腫之困與模塊化實踐之路》
《微信團隊原創分享:微信客戶端SQLite數據庫損壞修復實踐》
《騰訊原創分享(一):如何大幅提高移動網絡下手機QQ的圖片傳輸速度和成功率》
《騰訊原創分享(二):如何大幅壓縮移動網絡下APP的流量消耗(下篇)》
《騰訊原創分享(二):如何大幅壓縮移動網絡下APP的流量消耗(上篇)》
《如約而至:微信自用的移動端IM網絡層跨平臺組件庫Mars已正式開源》
《開源libco庫:單機千萬鏈接、支撐微信8億用戶的後臺框架基石 [源碼下載]》
《微信新一代通訊安全解決方案:基於TLS1.3的MMTLS詳解》
《微信團隊原創分享:Android版微信後臺保活實戰分享(進程保活篇)》
《微信團隊原創分享:Android版微信後臺保活實戰分享(網絡保活篇)》
《Android版微信從300KB到30MB的技術演進(PPT講稿) [附件下載]》
《微信團隊原創分享:Android版微信從300KB到30MB的技術演進》
《微信技術總監談架構:微信之道——大道至簡(PPT講稿) [附件下載]》
《微信海量用戶背後的後臺系統存儲架構(視頻+PPT) [附件下載]》
《微信異步化改造實踐:8億月活、單機千萬鏈接背後的後臺解決方案》
《架構之道:3個程序員成就微信朋友圈日均10億發佈量[有視頻]》
《微信團隊原創Android資源混淆工具:AndResGuard [有源碼]》
《移動端IM實踐:Android版微信如何大幅提高交互性能(一)》
《移動端IM實踐:Android版微信如何大幅提高交互性能(二)》
《移動端IM實踐:WhatsApp、Line、微信的心跳策略分析》
《移動端IM實踐:谷歌消息推送服務(GCM)研究(來自微信)》
《信鴿團隊原創:一塊兒走過 iOS10 上消息推送(APNS)的坑》
>> 更多同類文章 ……
[2] 有關QQ、微信的技術故事:
《技術往事:微信估值已超5千億,雷軍曾有機會收編張小龍及其Foxmail》
《2017微信數據報告:日活躍用戶達9億、日發消息380億條》
《技術往事:創業初期的騰訊——16年前的冬天,誰動了馬化騰的代碼》
《技術往事:史上最全QQ圖標變遷過程,追尋IM巨人的演進歷史》
《開發往事:深度講述2010到2015,微信一路風雨的背後》
《開發往事:記錄微信3.0版背後的故事(距微信1.0發佈9個月時)》
>> 更多同類文章 ……
(本文同步發佈於:http://www.52im.net/thread-1524-1-1.html)