那麼最值得關注的是PSS和USS,咱們能夠用dumpsys meminfo來查詢(無需root權限)java
重點字段解讀:python
經過上面圖片可得launcher app佔用的內存是250M,大部份內存在Native Heap、code、graphics,那如何分析和解決,咱們下面講。linux
JMM 分類android
注意:git
Java內存優化 | 內存泄漏 | 內存抖動 | 大內存對象使用 |
---|---|---|---|
發生的場景 | 單例、匿名內部類、接口忘記釋放 ... | String拼接、循環內重複生成對象 ... | HashMap、ArrayList ... |
LeakCanary能夠檢查Activity Fragment View界面的泄漏問題。經過接入LeakCanary跑上monkey接着靜等java內存泄漏的出現:github
經過上圖能夠知道SearchActivity被HistorySource.mContext持有,HistorySource是一個單例,而後最頂層的Thread.contextClassLoader就是GC root(注意:靜態變量不是GC root),Thread.contextClassLoader是PathClassLoader類,只要把 SearchActivity的context換成Application那就解決了。web
分析內存完成以上步驟以後的結果圖。shell
爲了不查看太多並非強相關的對象,直接從本應用的java 類入手,MAT 也提供正則式過濾,直接輸入.com.vd.(本應用 packageName)去過濾,結果就很是明顯,整個應用本身寫的對象佔用的內存都在這裏。從大的對象下手,是否這個對象有存在的意義,是否須要佔這麼大的一個內存。是否能夠對其作相應的處理。 json
MAT提供了更加方便的OQL查詢,能夠找到指定一個名字的對象,包括能夠根據自己java對象的成員屬性來作條件語句。譬如上圖我找長寬都大於100px的圖片都有哪些。能夠把大圖片揪出來。windows
native 內存優化 | malloc_debug | heapsnap | DDMS |
---|---|---|---|
root權限 | 須要 | 須要 | 不須要 |
環境 | python | jni | 須要使用sdk18 的 tools/ddms.bat(sdk 18以後就被剔除了) |
malloc_debug是官方推薦的一種方法,目前效果還不錯
heapsnap 是一個能夠跑在Adnroid的C庫github開源庫 ,目前只能查詢內存泄漏。並且編譯不過,緣由是缺乏了一些庫。在它基礎上我整合了一份編譯成功,有興趣點擊這裏
DDMS目前被遺棄,在android 9.0沒整成功,放棄。
malloc_debug步驟開啓malloc debug模式,打開cmd窗口輸入。
//查詢全部內存
adb shell setprop wrap.packagename '"LIBC_DEBUG_MALLOC_OPTIONS=backtrace logwrapper"'
//查詢內存泄漏
adb shell setprop wrap.packagename '"LIBC_DEBUG_MALLOC_OPTIONS=leak_track logwrapper"'
複製代碼
經過adb shell am dumpheap -n <PID_TO_DUMP> /data/local/tmp/heap.txt把文件抓取出來到/data/local/tmp/heap.txt。
把native內存文件拷貝出來,等下分析。
修改python代碼修改native_heapdump_viewer.py 代碼中NDK配置地方:
resByte = subprocess.check_output(["G:/AndroidNDK/android-ndk-r17/toolchains/aarch64-linux-android-4.9/prebuilt/windows-x86_64/bin/aarch64-linux-android-objdump", "-w", "-j", ".text", "-h", sofile])
複製代碼
p = subprocess.Popen(["G:/AndroidNDK/android-ndk-r17/toolchains/aarch64-linux-android-4.9/prebuilt/windows-x86_64/bin/aarch64-linux-android-addr2line", "-C", "-j", ".text", "-e", sofile, "-f"], stdout=subprocess.PIPE, stdin=subprocess.PIPE)
複製代碼
替換def init(self):函數中的部分代碼,把下面代碼:
if len(extra_args) != 1:
print(self._usage)
sys.exit(1)
複製代碼
替換爲:
self.symboldir = "C:/Users/chaojiong.zhang/Documents/AndroidStudio/DeviceExplorer/xiaomi-mi_8-4b429b4"
extra_args.append("dump.txt")
複製代碼
self.symboldir - 就是dump.txt 裏面的內存地址都須要 經過so庫來查找對應的是哪個函數。而so存放的父路徑地址就是self.symboldir,那麼也就是說須要把 手機上的/system/lib6四、/vendor/lib64/整個 文件夾pull 下來到電腦上,譬如這裏是pull到C:/Users/chaojiong.zhang/Documents/AndroidStudio/DeviceExplorer/xiaomi-mi_8-4b429b4。
在def main():函數插入部分代碼在函數第一行插入和最後一行插入如下代碼,目的是直接把結果log輸出到test.txt能夠直接查看。
def main(): sys.stdout= open("test.txt", "w")
//...
sys.stdout.close()
複製代碼
跑起來看看。
10285756 58.29% 99.95% 49 eac0b276 /system/lib/libhwui.so android::Bitmap::allocateHeapBitmap(SkBitmap*)
複製代碼
BitmapFactory.decodeResource -> BitmapFactory.nativeDecodeStream ->BitmapFactory.cpp 中 nativeDecodeStream() -> doDecode() -> SkBitmap.tryAllocPixels() -> ... -> android::Bitmap::allocateHeapBitmap()
複製代碼
Bitmap.createBitmap -> nativeCreate() -> Bitmap.cpp 中的 nativeCreate() -> GraphicsJNI.cpp zhong de allocateJavaPixelRef() -> ... -> android::Bitmap::allocateHeapBitmap()
複製代碼
也就是說java層的bitmap 建立都會跑到allocateHeapBitmap這個函數。那麼上面這個佔用了10M的 allocateHeapBitmap,到底是java層哪一個類調用下來的,這個目前是無解(包括最近華爲的方舟環境平臺DevEco也不行),只能在java層去全盤查詢了,哪些圖片使用了較多的內存。內存信息分析二
若應用沒有本身接入OpenGL/ GL surfaces/ GL textures開源庫,來繪製圖形,可沒必要理會。畢竟已經超出android應用工程師的範圍了。
private void memoryShake() {
ArrayList<Integer> shakes = new ArrayList<>();
for (int i = 0; i < 100; i++) {
Integer shake = new Integer(i);
shakes.add(shake);
}
}
private void memoryShake1() {
ArrayList<Integer> shakes = new ArrayList<>();
Integer shake;
for (int i = 0; i < 100; i++) {
shake = new Integer(i);
shakes.add(shake);
}
}
複製代碼
memoryShake()會在循環內生成100個shake局部變量+100個局部變量的引用,memoryShake1()會在循環內生成100個shake局部變量+1個局部變量的引用,一個對象引用在64bit的環境是8byte 。100*8 = 800 byte = 0.8KB。
String使用問題
循環內字符的拼接不要使用+符號,(使用+符號,編譯成字節碼後,循環內會生成StringBuilder對象去拼接)。正確應該使用StringBuffer(線程安全)或者StringBuilder(線程不安全)。
code內存消耗主要是:so庫,dex,ttf。
以上三種文件都是要加載到運行內存才能被解析運行,因此它們的體積要算進自身的應用內存中。so庫,能夠經過STRIP去掉一些符號表和調試信息,在Android.mk加入 LOCAL_STRIP_MODULE:= true,便可。
dex,是java代碼編譯成的字節碼,沒混淆的apk中的dex會大不少,混淆後的dex 會小不少,因此debug包的內存佔用會大於release包。Android Studio 3.3帶了了一個新特性R8壓縮,能夠在gradle.properties加入 android.enableR8=true ,減少dex包的體積(完美兼容現有混淆)。固然還要剔除自身應用的無用代碼,可以使用Android Studio Menu > Refactor > Remove Unused Resources進行排查,這裏再也不詳細展開。
ttf - 若是應用中只用到部分字體,可經過FontZip提取使用的字體。