摘要:分析應用靜息態內存占用。這里采用的方式是靜息態內存進入,立即內存操作一段時間之后再內存一共有三次,可以利用對比的功能對比內存增量。
作者:楊超,騰訊移動客戶端開發 工程師
商業轉載請聯系騰訊WeTest獲得授權,非商業轉載請注明出處。
原文鏈接:http://wetest.qq.com/lab/view/359.html
歷時五天的內存優化已經結束,這里總結一下這幾天都做了什么,有哪些收獲。優化了,或可以優化的地方都有哪些。(因為很多事還沒做,有些結論需要一定樣本量才能斷定,所以叫一期)一期優化減少JavaHeap內存占用約26.5M。
在任何性能優化之前,要做的第一件事就是找到性能瓶頸!而找到性能瓶頸通常需要強大的debug工具輔助。內存方面Android有 AndroidStudio 的 Android Profiler、Allocation Tracker,以及Eclipse的MAT用于分析java的內存占用,相當強大。而偏向native層面的內存占用則找不到太好的工具,因此這里在做優化前,先造了幾個工具。
一、造輪子1. 線程創建分析工具
該工具使用native hook的方式,直接hook了pthread_create調用,并記錄每一個線程創建時的堆棧,并打印日志。同時維護一個running thread的集合,必要時 dump下來所有running thread的創建堆棧,用于分析野蠻線程創建的場景。以及對應的日志分析工具。
2、 Linux /proc/
主要用于跟蹤進程的 Code 部分內存(見下文)占用,分析出占用內存較多的dex,so文件。排查第三方SDK占用過多內存場景。網上只能找到一個perl腳本,功能不是很強大,鑒于筆者不熟悉perl的語法規范,改起來會比較困難,因此直接用python重寫了一個。
代碼在這里:https://gist.github.com/LanderlYoung/aedd0e1fe09214545a7f20c40c01776c
3、 快速Dump Android java heap腳本
因為分析內存需要很多dump操作,所以干脆寫了個Bash腳本。
Bash腳本鏈接:https://gist.github.com/LanderlYoung/9cd0f49e49e42746622cc8e7b4bbcc8a
(順便提一下,android提供的 hprof-conv 工具有個參數 -z 用于排除zygote的內存,十分便利。)
二、Android 進程 內存分類通常我們在系統的內存管理頁面看到的內存占用是進程的PSS,也就是整個進程的內存占用,因此我們做優化的要考慮到所有的內存,不僅僅是Java Heap。
使用Android Studio(3.0 beta)的 Android Profiler工具。
我們可以很清晰的看到
1)進程總內存占用: 180M
2)JavaHeap: 48M
3)NativeHeap:native層的 so 中調用malloc或new創建的內存 —— 28M
4)Graphics:OpenGL和SurfaceFlinger相關內存 ——58M
5)Stack:線程棧——1.89M
6)Code:dex+so相關代碼占用內存——37.75M
7)Other:蜜汁存在
上述6中內存占用除了兩種不需要考慮,其他5中通通需要優化。不需要考慮的是:
1)Other:暫時無從分析
2)Graphics:若應用沒有直接調用OpenGL,則可以確定這部分內存是由Android Framework操控的,可以忽略。(當然對于游戲類應用,這里肯定是優化重點。)
下面按照內存分類分開逐一介紹分析方法,和結論:
JavaHeap
這里必然是內存優化的重點,無需多言。但是企鵝FM的業務,UI,代碼已經比較龐大,分析起來會顯得力不從心。因此這里主要從兩個方面入手,希望能總結出一套分析方法。
1、分析應用 靜息態 內存占用。
所謂靜息態,是筆者自行定義的概念:
應用在退后臺之后,不保留活動的場景下的內存占用。
為什么要考察這個維度?因為這個是一個應用內存占用最低點的時候,后續打開任何Activity內存只會更多,不會更少!
2、分析方法
1)開發者選項開啟“不保留活動”
2)進入MainActivity,滑動頁面,操作一下
3)退后臺,Android Studio中強制執行GC
4)dump java heap (注意上面提到的 hprof-conf 加上 -z 參數排除zygote的干擾)
5)MAT 分析 dump 下來的JavaHeap
重點介紹一下MAT:
這里可以直接打開domanitor_tree看占用內存最多的實例。
從這里按照RetainedHeap倒序排列,一點一點的排查內存占用。很容易發現不正常的內存情況。
在企鵝FM中發現:
1)圖片的內存級緩存退后臺沒清空(此處屬于onTrimMemory回調的處理有誤),占用10M內存
2)ImageMisc — 280k
①
② 是一個buffer,可以在不用的時候釋放內存
③ 優化目標,徹底干掉
3)播放頁應用動畫的關系,UI是單例。其中相關View占用數百K內存,而button的icon直接引用住了5-6M的bitmap資源。
4)播放列表存儲了103個ShowInfo,每個ShowInfo 22k,總計內存約2.24M,ShowInfo冗余信息很多,可以考慮優化數據結構
5) DanmuManager — 510k
● mDanmuItemManager 內含眾多彈幕
● 每條彈幕6k
● UI相關數據,離開播放頁后應該清理彈幕(因為無需展示了)
● 優化目標,徹底干掉。
6) FileCacheService — 362k
①
② 其中緩存了每一個cache entry,其中圖片緩存較多
③ 每一個entry記錄完整文件路徑其比較長,因此路徑的字符串占用了很多內存
④ 優化方案:
● 文件Parent可以共用同一個File對象。
● entry = new File(parent, “entry_name”)
⑤ 優化目標 到100k
7)LiveRoomShowListManager -- 287k
①
② 優化目標:UI相關數據,離開界面應該徹底干掉
8) DB InsertHelper, Sql Statement clearBinding
① 700K到2M
② InsetHelper中會引用住最后一次執行DB insert調用的 數據(占位符)
③ InsertHelper的占位數據可以在insert完成之后清掉
針對上面提到的ShowInfo的數據結構優化
擬定優化方案:
1)ShowList存儲的ShowInfo數量過多,30個足矣。
2)ShowInfo中Album字段占用10k內存,其實同一個ShowList中大多數album是完全一致的(比如專輯類型的ShowList,主播類型的,自選集類型的,本地專輯的,etc...)。
預計內存占用 2M -> 30*12K = 360K
3)靜息態內存優化總結:
上述幾點加起來預期可以減少內存占用:
10M + 280K + 5M + 2M + 510K + 260k + 287k + 1M = 約20M
3、 MainActivity 操作一段時間之后內存增量
上面分析的是靜息態內存,下面看一下MainActivity操作一點時間之后,內存有怎樣的變化。
這里采用的方式是:
1)dump靜息態內存
2)進入MainActivity,立即dump內存
3)操作一段時間之后再dump內存
一共有三次dump,可以利用MAT對比heap的功能對比內存增量。
打開MAT的historgram視圖
工具欄最右邊有個雙箭頭的icon,點擊可對比dump:如下圖
增量最多的還是Bitmap(底層用byte[]存儲),借助MAT的 Finer 工具可以直接看到Bitmap的圖片。
這里發現的幾個問題是(時間關系,應該多次測試的,會發現更多問題):
① Banner的大圖沒有 Clip 導致 分辨率 很高
② 分類頁的 配置區域 沒有Clip
③ onRecycle沒有清除掉已經引用的Bitmap,導致引用住不能gc
主要說一下第3點,是Banner每一個Item有一個大圖做背景,當item的view被回收的時候,相應的ImageView仍然持有著大圖,導致其不能回收。這里發現了4張1M+的大圖,其實理論上應該只有1張。
這個問題可以推廣到所有的ListView場景,建議方式是:
替換為RecyclerView,在view回收的時候,ScarpView釋放圖片引用。
此外,MainActivity有5個tab,各個tab之間其實會用到相同的View(listview 的item),如果使用RecyclerView可以做到5個tab的RecyclerView共同復用同一個RecyclerPool,在節省內存的同時還能顯著提高性能。
這里不方便直接測試內存占用,預估可以節省內存5-10M。
4、 正常操作應用,觀察內存占用圖表是否有突起
這里主要用來測試異常內存分配的場景。
這里仍然需要很大人力,過很多頁面。
目前發現問題有:
1)service進程,發送wns請求的時候,內存異常增長2-3M。
這時可以使用AllocationTracker工具(點擊下圖工具欄的紅點),記錄峰值那一段內存的分配,如圖:
這里可以直接看到分配的棧,定位過去看,發現是這樣的代碼,因為head是一個65536長的數組(在 com.tencent.wns.session.Session 的構造函數寫死的長度),這里創建string就浪費了超大量的內存。建議可以改成下圖彈窗里的樣子
2)另外一個問題是播放進程,在切換節目的時候內存會突然增長2-3M,簡單跟進去看是exo創建buffer。似乎有問題,需要再多分析一下~
Native Heap
目前能看到的NativeHeap大小是
應用啟動:26M 此時已經初始化了 X5內核和IM SDK
UGC錄音:26M->34M 退出之后時32M,還有部分沒釋放,疑似內存泄漏
發起直播:32M->72M 退出之后42M,同樣沒有完全釋放
具體內部占用情況還沒測。。。(都說了是一期)。
官方文檔:
https://source.android.com/devices/tech/debug/native-memory
Code
這一段明顯看到占用了很多內存。各個場景下的使用情況是:
1)剛進入應用:38M
2)再使用UGC錄音:38.28M
3)再使用視頻直播(發起直播):46M
4)打開應用內WebView(X5內核):56M
以上是主進程的內存,占用相當多。需要注意的是code內存占用一般是通過read-only方式mmap映射到內存中的的dex、odex、so等文件,因此在內存緊張的情況下,系統會回收這些內存,只是在oom-killer中仍然會計算在內。
另外播放進程2.27M,service進程1.1M還屬于比較正常的水平。
顯然主進程的Code內存占用太多了,需要分析。這里通過解析Linux標準的 /proc/
● 應用so占用 app so map Rss = 3984 kB (其中IM SDK 2576k)
● 應用的dex占用 app dex map Rss = 15101 kB
● X5內核的so+dex內存占用 tbs mem map Rss = 29048 kB
● 直播so相關 avlive mem map Rss = 3092 kB
● 其中X5內核的代碼沒有打進apk,因此可以比較獨立的統計出來,占用有29M之多,讓人驚訝!
● 其次直播的java代碼打進了apk不方便多帶帶統計內存用量,但是so是獨立加載的,內存占用3M也是不少的。
● 最后是應用自身的dex占用有15M之多,因為自身代碼量很大,似乎可以理解,但是仍然很多啊!
這里需要考慮的是 X5 內核能否延時加載?因為沒打開WebView的時候就已經占用了數M了。另外WebView關閉之后是否可以銷毀。
直播相關SO,可以考慮直播退出之后從內存中卸載掉。(java規范是加載so的classloader被GC,相關so即可卸載)。
應用自身dex占用。android 8.0 對art優化一個叫做DexLayout 的能力,應為mmap映射的文件不會被立即加載進內存,在用到的時候是按照頁大小(4k)加載的,當用到的類在dex中分布很分散的時候,就會導致盲目加載很多頁,DexLayout就是把熱點類集中放到一起。這里FaceBook推出了ReDex工具,可以參考一下。
PS:關于DexLayout
三、線程創建在AndroidStudio的Memory Profiler中沒有線程數這個維度。但是運行中,主進程的線程數量通常會在100個左右,這是個驚人的數字,要知道Mac版的AndroidStudio也不過77個線程。。。。請自行體會一下。
關于線程的創建和內存占用,請參考筆者的另一篇文章:《Android 創建線程源碼與OOM分析》 。
這里分析用的自制工具,dump下載所有running的線程,和他們創建時的堆棧。
結果是:
● X5:25個線程(簡直。。。)
● IMSDK:17個線程
● StackBlur:8個線程
● WNS:7個線程
● ImageLoader:6個線程
● magnifiersdk:5個線程
需要注意,這里的棧和線程名,是創建線程的時候的調用棧,以及對應的線程名(而不是子線程名)
事實上,用同樣的方法,還可以分析一下進程歷史中所有創建過的線程,統計哪里創建線程最多。
通常來說,所有線程應該有應用統一的線程池來管理,sdk內部需要線程池,應該有外部注入一個線程池來提供給sdk使用。
如果有其他情況,如:不是在線程池創建的線程,在sdk自己的線程池里創建的線程,這種都可能導致線程數量的野蠻增長,需要聯系sdk的開發人員杜絕這種情況。
四、總結以上就是這5天的工作結果:
java內存占用基本合理,靜息態 內存占用可以優化20M,MainActivity運行時的內存占用可以優化5-10M。
code內存占用太多,其中X5內核占用29M實在太多,需要考慮優化。
應用內的線程數量主要有X5內核,IMSDK和WNS貢獻,外網線程創建的OOM crash 系WNS的bug,需要聯系相關sdk開發人員。
最后是Native內存占用還沒有詳細分析,暫時看不到使用情況。但是可以知道目前的結論是:Native內存占用很多,且應該存在內存泄漏。
PS: 實際效果反饋
按照上述分析結果,進行了相關的代碼調整。
執行的點包括:
1、IntelliShowList pageSize 50->20
2、IntelliShowList 公用Album結構
3、Afc-db clearBinding after insert, 數據庫
4、Afc-FileCacheService cache Entry with fileName not full path, 文件緩存
5、 fix onTrimMemory bug,退后臺清空圖片內存緩存
6、播放頁相關控件,退后臺之后清掉icon,釋放bitma引用
未執行的點包括:
1、播放頁的bottomPannel部分icon因為邏輯較為復雜,暫時未進行處理。預計內存占用1M
2、PlayLogic的historyList邏輯復雜暫時未處理,預計內存占用500K
3、24h直播間LiveRoomShowListManager -- 287k
4、DanmuManager — 510k
5、經過ice提醒,下載節目的record也會全部加載進內存。每個ShowInfo 22k,內存占用取決于用戶下載的節目數。
效果對比:
before:39.32M
after:12.88M
優化內存占用 26.44M!
UPA—— 一款針對Unity游戲/產品的深度性能分析工具,由騰訊WeTest和unity官方共同研發打造,可以幫助游戲開發者快速定位性能問題。旨在為游戲開發者提供更完善的手游性能解決方案,同時與開發環節形成閉環,保障游戲品質。
目前,限時內測正在開放中,點擊http://wetest.qq.com/cube/ 即可預約。
對UPA感興趣的開發者,歡迎加入QQ群:633065352
如果對使用當中有任何疑問,歡迎聯系騰訊WeTest企業QQ:800024531
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/70873.html
摘要:直到有一天你會碰到線上奇奇怪怪的問題,如線程執行一個任務遲遲沒有返回,應用假死。正好這次借助之前的一次生產問題來聊聊如何排查和解決問題。本地模擬上文介紹的是線程相關問題,現在來分析下內存的問題。盡可能的減少多線程競爭鎖。 showImg(https://segmentfault.com/img/remote/1460000015568421?w=2048&h=1150); 前言 之前或...
摘要:原文鏈接本篇是專家系列的第三篇。但是,請記住調優是不得已時的選擇。縮短耗時的單次執行與相比,耗時有較明顯的增加。創建文件過程中,進程會中斷,因此不要在正常運行時系統上做此操作。因此校驗結果并根據具體的服務需要,決定是否要進行調優。 原文鏈接:http://www.cubrid.org/blog/dev-platform/how-to-tune-java-garbage-collecti...
摘要:編譯參見深入理解虛擬機節走進之一自己編譯源碼內存模型運行時數據區域根據虛擬機規范的規定,的內存包括以下幾個運運行時數據區域程序計數器程序計數器是一塊較小的內存空間,他可以看作是當前線程所執行的字節碼的行號指示器。 點擊進入我的博客 1.1 基礎知識 1.1.1 一些基本概念 JDK(Java Development Kit):Java語言、Java虛擬機、Java API類庫JRE(...
閱讀 2922·2021-11-24 09:39
閱讀 3599·2021-11-22 13:54
閱讀 3409·2021-11-16 11:45
閱讀 2432·2021-09-09 09:33
閱讀 3194·2019-08-30 15:55
閱讀 1290·2019-08-29 15:40
閱讀 920·2019-08-29 15:19
閱讀 3396·2019-08-29 15:14