SystemUI匿名页java堆内存分析

来源飞书 wiki(obj_token: GuEydn8pXo5omjxxh04c0KsanOr)

背景与目标

目标:Pss 降低 200MB

开机内存数据(不同oomAdj进程内存对比,SystemUI开机内存338272KB,与挑战值差距200822KB)

测试场景:开机内存

机型对比:小米P1 vs VIVO X200U

内存差距:156M(156056KB)

嵌入表格(飞书 sheet,token: WaHYsiJ4DhtEOktiRBQc1dRgnOc / sheetId: 7ueGu0)—— 本文档无法直接渲染,请前往飞书原文查看。

Top 3 为:

文件页(diff = 108MB

匿名页-Native(diff = 53MB

匿名页-Java(diff = 12.4MB

Java MemFunc 函数级拆解 (增量统计)

按竞品diff值降序:

嵌入表格(飞书 sheet,token: WaHYsiJ4DhtEOktiRBQc1dRgnOc / sheetId: T6icqm)—— 本文档无法直接渲染,请前往飞书原文查看。

理想优化上限:20MB

类加载相关

dalvik.system.BaseDexClassLoader.findClass

类查找。

dalvik.system.DexPathList.toString

3.1.1 类查找函数中的 子方法,当 ClassNotFound 的时候,就会转String,构造异常抛出。

DexPathList.toString 内存分析

libcore.io.ClassPathURLStreamHandler$ClassPathURLConnection.getInputStream

先介绍:java.util.ServiceLoader$LazyClassPathLookupIterator

Java 核心懒加载迭代器,负责 SPI 实现类的懒扫描(类路径)+ 懒加载(类)

此方法为其子方法,从「类路径(ClassPath)」中读取资源(如 assets、res、jar 内文件)的输入流获取方法。

业务侧Func 1:

com.miui.keyguard.shortcuts.manager.ShortcutManager.init (锁屏-快捷方式)

业务侧Func1: ShortcutManager.init 内存分析(锁屏-快捷方式,占1.6MB)

业务侧Func 2:

miui.systemui.dynamicisland.window.DynamicIslandWindowViewCreator.createView(通知-灵动岛)

业务侧Func2: DynamicIslandWindowViewCreator.createView 调用链(占比8.9%)

sun.security.util.SignatureFileVerifier.processImpl

此方法同为 java.util.ServiceLoader$LazyClassPathLookupIterator 的子方法,完成 Jar 文件的安全性校验。

sun.misc.IOUtils.readFully

此方法同为 java.util.ServiceLoader$LazyClassPathLookupIterator 的子方法,更具体的说,为java.util.jar.JarFile.getManifest 的子方法,完成AndroidManifest文件内容的读取。

sun.security.util.ManifestDigester.

此方法同为 java.util.ServiceLoader$LazyClassPathLookupIterator 的子方法,更具体的说,为java.util.jar.JarVerifier 的子方法,为 JAR 文件的 META-INF/MANIFEST.MF完成摘要生成与校验。

sun.security.util.ManifestDigester.<init> 内存分析(194142字节)

dalvik.system.DexPathList$Element.maybeInit

此方法同为 java.util.ServiceLoader$LazyClassPathLookupIterator 的子方法,更具体的说,为java.lang.ClassLoader.getResources 的子方法,作用是「懒加载 / 延迟初始化」单个 Element 对应的 Dex 文件(APK/JAR/DEX 文件),仅在首次使用该 Element 时完成 Dex 优化、内存映射等初始化操作,避免应用启动时一次性加载所有 Dex 文件导致的性能开销。

DexPathList$Element.maybeInit 内存分析(占比8.48%)

原因分析:

由于注入依赖框架Dagger的使用,编译阶段会生成大量辅助类,它会加载辅助类的依赖链上的所有类,再配合Speed编译模式,小米 systemui 应用在初始化时加载(类查找+ 类加载 行为)了所有dagger生成的辅助类与其依赖类(对于odex文件的数据访问,占整个文件大小的90+%),而VIVO采用speed-profile且未使用dagger,启动时仅会加载少量的类,引起此项内存消耗GAP。

解决方案:

TODO

日志打印 android.util.Log.getStackTraceString

android.util.Log.getStackTraceString 内存分析(含调用函数及占比)

小窗模式 setAnimationParam 代码(new Throwable() 黄色高亮)

android.util.Slog.java 源码(@UnsupportedAppUsage 静态方法)

原因分析:

在小窗模式代码中,使用了带Throwable的3参数 android.util.Slog.d 打印日志,引起此项内存消耗。

解决方案:

  1. 应用侧,替换为不带Throwable的2参数 android.util.Slog.d 接口;@苗健
  2. 系统侧,调整外发user版本的LogLevel 输出日志等级为 Info 级别(目前为 Debug 级别)

Perfetto 抓取Trace相关

android.util.proto.EncodedBuffer.

android.util.proto.EncodedBuffer.<init> 调用栈

android.util.proto.ProtoInputStream.readRawString

android.util.proto.ProtoInputStream.readRawString Perfetto 界面

原因分析:

Perfetto 抓trace引入,此项可忽略!

解决方案:

关闭所有非必要trace点。

View创建相关

android.view.LayoutInflater.createView

View的创建与加载(上游)

涉及业务

控制中心:

LayoutInflater.createView 调用栈(控制中心 updateContent 占比6.3%)

锁屏:

LayoutInflater.createView 调用栈(锁屏 KeyguardPanelViewSection.addView 占0.85%)

通知:

LayoutInflater.createView 调用栈(通知 NotificationDismissViewController 占8.89%)

android.view.View.

View的创建与加载(下游)

原因分析:

各模块View的加载与预加载。

解决方案:

对于显示实时性要求不高的部分布局,改为懒加载(Lazy)模式。

android.view.InsetsSource.

InsetsSource.<init> 内存分析(PipController 占比6.41%)

启动场景下,android.view.InsetsSource 的创建个数,远多于竞品VIVO。 相关业务侧代码:com.android.wm.shell.pip.phone.PipController (画中画)

—责任人 @苗健

org.json.JSONTokener.nextString

Json解析。

JSONTokener.nextString 内存分析

原因分析:

小米SystemUI中的MIUI小窗模式(miuiFreeForm),在启动阶段会解析云控下发的JSON配置文件(内容较多),引起此项内存开销。竞品VIVO没有类似逻辑。

—责任人 @苗健

解决方案:

TODO

基于HPROF的存量Java堆分析 (存量统计)

对比机型小米P1 VS 荣耀Magic8 Pro 场景:开机启动场景,稳态后,触发GC 结果(基于hprof):小米 57.6MB vs 荣耀 49.3MB 差距:8.3 MB

基于 HPROF 分析GC后,依旧保持的java对象,对比 小米与荣耀的差异:

小米P1 VS 荣耀Magic8 Pro Java堆对象 Shallow Heap 差距表

小米VS荣耀:Java堆对象Shallow Heap差距降序排列表:

小米VS荣耀 Java堆对象 Shallow Heap 差距降序排列表(kotlin.atomicfu.AtomicRef 等)

详情见:

然后按照Heap差距由多到少,就可以通过MAT工具,逐项查看各个遗留对象的强引用链,进而定位到具体的业务侧class对象,最后结合业务代码进一步分析得出解决方案:有无必要性?是否可以减少?

MAT 工具查看遗留对象强引用链(SubscriptionManagerFixHolder 等)

具体定位方法可以参考文档: