那么最值得关注的是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提取使用的字体。