Android内存优化之极致清理

阅读这篇文章你将了解到以下内容

  1. Android app 内存信息的获取方法以及各种方法终究的获取原理。
  2. 如何解析smaps文件自定义核算愈加详细的内存占用信息。
  3. .dex mmap内存占用优化,如何经过简略的hook 获取到类加载信息,加载类所在dex文件地址。
  4. .dex mmap内存优化终极计划 3行代码优化。

布景

小米小组件进程内存占用PSS必须小于35M dev.mi.com/console/doc…

Android内存优化之极致清理

优化

了解android 内存指标获取原理

优化前进程内存信息dump

Android内存优化之极致清理
上图咱们能够看出pss total占用了135M超出了100M

简略优化

针对上面状况咱们很容易想到 在子进程application阶段 移除非必要功用。经过裁剪能裁剪的代码和功用咱们得到了以下优化作用。

Android内存优化之极致清理

提交小米测验后很快被打回了,测验反馈运用内存超过了51M ,看了下测验脚本是1秒取一次内存,结果取最大值,小米这边测验51M是关了主进程测验的,而咱们自己测验是开着主进程。 这是因为Pss是平摊核算后的运用内存(有些内存会和其他进程同享,例如mmap进来的),咱们开多个进程后同享内存就会被平均到其他进程。 关闭主进程后测验内存占用能到56M

Android内存优化之极致清理

持续剖析优化

上面咱们发现Code部分占用了37M,Java Heap部分占用了10M

Java heap问题剖析

Java heap = dalvik private_dirty(772)+art mmap private_dirty(10132) +art mmap private_clean(84)

结合hprof文件剖析,app heap占用仅1.2M 首要在image heap上,能够剖分出Class加载是首要原因。

Android内存优化之极致清理

Android内存优化之极致清理

Code 问题剖析

Code问题首要剖析.dex mmap 34680/56526 = 61%

了解android 内存指标获取原理后,咱们手动解析一些smaps文件 看下.dex 散布。

从之前的测验包数据看 内存占用首要在base.vdex上,这部分是dex加载也和class加载相关

Android内存优化之极致清理
vdex文件

首要目的:下降dex2oat履行耗时 1、当体系OTA后,关于安装在data分区下的app,因为它们的apk都没有任何变化,那么在初次开机时,关于这部分app假如有vdex文件存在的话,履行dexopt时就能够直接越过verify流程,进入compile dex的流程,然后加速初次开机速度; 2、当app的jit profile信息变化时,background dexopt会在后台重新做dex2oat,因为有了vdex,这个时分也能够直接越过

vdex便是verified dex,包括 raw dex +(quicken info)

优化思路

  1. 经过applicaion 修正,除去widgetProvider进程不必要的类加载 从Hprof文件剖析。还是有加载一些不必要的类。

  2. 对dex次序做调整 ,类名混杂。

因为dex文件在生成时按字母次序摆放。因为4KB页面加载的原因,实践运行时会加载许多相邻但不会被用到的数据。例如在代码中运用了A1类,虚拟机就需求加载包括A1类数据的页面。但因为A1的数据只有1KB,那在加载的4KB页面中,还会有A2A3A4类,总共占用了4KB内存。

假设咱们的代码里在用到A1类后,还会用到B1、C1、D1类,那么假如能在dex文件中将A1、B1、C1、D1类放在一同,虚拟机就只需求加载一个4KB页面,优化的思路便是调整dex文件中数据的次序,将能够用到的数据紧密摆放在一同,Proguard东西能够对类名进行修正,依据程序运行的逻辑将那些会相互调用的类改为同一个packag名,这样就能够使他们的数据摆放在一同。

优化实践

代码修正
  1. 经过对比hprof文件和smali文件发现 BaseApplicatoin中存在aspectj, Fastjson等代码,子进程中移除。
  2. gson代码转为jsonobject完成

定论根本没什么下降

包名重混杂测验
  1. 排查到四个组件下keep规则 影响到类名混杂。
  2. 参加包名类名反混杂后包体减少1.6M

内存测验定论根本没什么下降

深化优化

这一步咱们首要便是去要研讨base.vdex 里边的内存占用是如何核算的。

vdex格式介绍

Android内存优化之极致清理
这边hook打印下打开.vdex文件的堆栈。然后从这附近去检查相关源码OpenAtAddress

Android内存优化之极致清理

ART 类加载日志hook

base.vdex是经过mmap的方法加载的,那便是在实践拜访的时分也便是类加载的时分才会触发内存加载,现阶段咱们想知道的是加载了哪些类之后导致内存占用上去了 ,其实art中自身有类加载的一些日志

Android内存优化之极致清理
不过这class_linker开关默许是关的, 咱们经过xdl找到这个全局特点后修正class_linker为true

Android内存优化之极致清理
Class load 日志。 拿到class load日志后很容易能够看出某个类什么时分从哪个dex加载的。(额定提一下,这个日志在咱们排查framework的问题时也特别有用,咱们能够依据问题类拿到jar包或apk进行反编译排查问题)

所以咱们的优化思路便是减少类加载的dex数量。 对load类进行移除测验内存 ,对一些其他dex类进行移除后能够发现,如下图内存占用确实降了下来。

定论:测验包code内存占用显着下降

testflight打包后 内存未显着下降

开始发现线程监控 和神策有代码注入 导致dex加载个数又变多了。

测验包发现一段时间后 未加载类 code占用也提升了。

主张内核开释vdex rss

盯梢源码发现 体系或许会有运用madvise预载操作。

Android内存优化之极致清理
测验经过madvise_dontneed直接discard filed-backed VMA数据

Android内存优化之极致清理

Android内存优化之极致清理

定论:vdex峰值下降10M 一段时间后.dex mmap 内存pss占用显著下降

终究计划

咱们在小米机型上运用madvise_dontneed计划开释base.vdex内存。

  1. 代码仅在小米组件进程生效,不影响主进程。
  2. 经过art 类加载日志能够知道组件进程加载的类很少,所以运用时再经过mmap load到内存对组件进程功能影响也很小。
  3. 小米侧内存测验Pass

Android内存指标的获取原理

常用的检查内存信息方法

  1. 检查android studio 的profile面板
  2. 运用多啦A梦等客户端东西
  3. 运用 adb shell dumpsys meminfo <pid_of_app>命令

Android studio profile 完成

android.googlesource.com/platform/to…

获取代码在
profiler/native/perfd/memory/memory_usage_reader_impl.cc
经过
dumpsys meminfo --local --checkin pid 完成

客户端代码调用获取api

private int getPss(int pid){
ActivityManager am =(ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE)
MemoryInfo meminfo = am.getProcessMemoryInfo(pid)[0];
return meminfo.getTotalPss();
}
Debug.MemoryInfo memInfo = new Debug.MemoryInfo();
Debug.getMemoryInfo(memInfo);

Adb dumpsys meminfo

Android内存优化之极致清理

内存获取方法的原理

这里就不逐个去剖析各种获取方法的源码了,先给出定论再结合adb dumpsys计划剖析

跟源码后每种指标获取方法都指向了一个文件proc/pid/smaps

读取进程smaps的节点, /proc/pid/smaps 咱们app中这个文件有9w+行

Android内存优化之极致清理
/proc/PID/smaps 文件是基于 /proc/PID/maps 的扩展,他展示了一个进程的内存耗费,比同一目录下的maps文件更为详细。

(图片来历网络)

Android内存优化之极致清理
依据特点的不同,进程的虚拟地址空间被划分红若干个vma,每一个vma经过vm_next和vm_prev组成双向链表,链表头坐落进程的task->mm->mmap.当经过proc接口读取进程的smaps文件时,内核会首先找到该进程的vma链表头,遍历链表中的每一个vma, 经过walk_page_vma统计这块vma的运用状况,最后显示出来。

Android内存优化之极致清理

smaps字段介绍.
  • size 是进程运用内存空间,并不一定实践分配了内存(VSS)
  • Rss是实践分配的内存(不需求缺页中断就能够运用的)
  • Pss是平摊核算后的运用内存(有些内存会和其他进程同享,例如mmap进来的)
  • Shared_Clean 和其他进程同享的未改写页面
  • Shared_Dirty 和其他进程同享的已改写页面
  • Private_Clean 未改写的私有页面页面
  • Private_Dirty 已改写的私有页面页面
  • Referenced 标记为拜访和运用的内存巨细
  • Anonymous 不来自于文件的内存巨细
  • Swap 存在于交流分区的数据巨细(假如物理内存有限,或许存在一部分在主存一部分在交流分区)
  • KernelPageSize 内核页巨细
  • MMUPageSize MMU页巨细,根本和Kernel页巨细相同

一般来说内存占用巨细有如下规律:VSS >= RSS >= PSS >= USS

VSS – Virtual Set Size 虚拟耗用内存(包括同享库占用的内存)

RSS – Resident Set Size 实践运用物理内存(包括同享库占用的内存)

PSS – Proportional Set Size 实践运用的物理内存(比例分配同享库占用的内存)

USS – Unique Set Size 进程独自占用的物理内存(不包括同享库占用的内存

Android内存优化之极致清理

Adb dumpsys part 1

smap中内存分类枚举 load_maps源码地址,这里会依照name的命名规则来过滤不同类型的内存 进行分组。我这边把过滤规则贴到了类型后面。adb dumpsys图中第一部分便是运用分组后的数据核算而来。

Adb dumpsys part 2
  long nativeMax = Debug.getNativeHeapSize() / 1024;
            long nativeAllocated = Debug.getNativeHeapAllocatedSize() / 1024;
            long nativeFree = Debug.getNativeHeapFreeSize() / 1024;
            Runtime runtime = Runtime.getRuntime();
            runtime.gc();  // Do GC since countInstancesOfClass counts unreachable objects.
            long dalvikMax = runtime.totalMemory() / 1024;
            long dalvikFree = runtime.freeMemory() / 1024;
            long dalvikAllocated = dalvikMax - dalvikFree;
Adb dumpsys part 3

gpu这部分内存需求经过gpu 文件节点获取。

Android内存优化之极致清理

Adb dumpsys part 4
  pw.println(" App Summary");
        printRow(pw, TWO_COUNT_COLUMN_HEADER, "", "Pss(KB)", "", "Rss(KB)");
        printRow(pw, TWO_COUNT_COLUMN_HEADER, "", "------", "", "------");
        printRow(pw, TWO_COUNT_COLUMNS,
                "Java Heap:", memInfo.getSummaryJavaHeap(), "", memInfo.getSummaryJavaHeapRss());
        printRow(pw, TWO_COUNT_COLUMNS,
                "Native Heap:", memInfo.getSummaryNativeHeap(), "",
                memInfo.getSummaryNativeHeapRss());
        printRow(pw, TWO_COUNT_COLUMNS,
                "Code:", memInfo.getSummaryCode(), "", memInfo.getSummaryCodeRss());
        printRow(pw, TWO_COUNT_COLUMNS,
                "Stack:", memInfo.getSummaryStack(), "", memInfo.getSummaryStackRss());
        printRow(pw, TWO_COUNT_COLUMNS,
                "Graphics:", memInfo.getSummaryGraphics(), "", memInfo.getSummaryGraphicsRss());
        printRow(pw, ONE_COUNT_COLUMN,
                "Private Other:", memInfo.getSummaryPrivateOther());
        printRow(pw, ONE_COUNT_COLUMN,
                "System:", memInfo.getSummarySystem());
        printRow(pw, ONE_ALT_COUNT_COLUMN,
                "Unknown:", "", "", memInfo.getSummaryUnknownRss());
        pw.println(" ");
        if (memInfo.hasSwappedOutPss) {
            printRow(pw, THREE_COUNT_COLUMNS,
                    "TOTAL PSS:", memInfo.getSummaryTotalPss(),
                    "TOTAL RSS:", memInfo.getTotalRss(),
                    "TOTAL SWAP PSS:", memInfo.getSummaryTotalSwapPss());
        } else {
            printRow(pw, THREE_COUNT_COLUMNS,
                    "TOTAL PSS:", memInfo.getSummaryTotalPss(),
                    "TOTAL RSS:", memInfo.getTotalRss(),
                    "TOTAL SWAP (KB):", memInfo.getSummaryTotalSwap());
  • Java Heap

    • // dalvik private_dirty dalvikPrivateDirty // art mmap private_dirty + private_clean + getOtherPrivate(OTHER_ART);
  • Native Heap

    • libc_malloc
  • Code

    • // so mmap private_dirty + private_clean getOtherPrivate(OTHER_SO) // jar mmap private_dirty + private_clean + getOtherPrivate(OTHER_JAR) // apk mmap private_dirty + private_clean + getOtherPrivate(OTHER_APK) // ttf mmap private_dirty + private_clean + getOtherPrivate(OTHER_TTF) // dex mmap private_dirty + private_clean + getOtherPrivate(OTHER_DEX) // oat mmap private_dirty + private_clean + getOtherPrivate(OTHER_OAT);
    • 一切私有静态资源求和
  • Stack

    • // stack private_dirty getOtherPrivateDirty(OTHER_STACK);
    • 进程自身栈占用的巨细。
  • Graphic

    • //Gfx Dev private_dirty + private_clean getOtherPrivate(OTHER_GL_DEV) // EGL mtrack private_dirty + private_clean + getOtherPrivate(OTHER_GRAPHICS) // GL mtrack private_dirty + private_clean + getOtherPrivate(OTHER_GL);
  • System

    • Pss Total 的和- Private Dirty 和Private Clean 的和

体系占用的内存,例如一些同享的字体,图画资源等。