SystemUI匿名页java堆内存分析
来源:飞书 wiki(obj_token: GuEydn8pXo5omjxxh04c0KsanOr)
背景与目标
目标:Pss 降低 200MB

测试场景:开机内存
机型对比:小米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,构造异常抛出。

libcore.io.ClassPathURLStreamHandler$ClassPathURLConnection.getInputStream
先介绍:java.util.ServiceLoader$LazyClassPathLookupIterator
Java 核心懒加载迭代器,负责 SPI 实现类的懒扫描(类路径)+ 懒加载(类)
此方法为其子方法,从「类路径(ClassPath)」中读取资源(如 assets、res、jar 内文件)的输入流获取方法。
业务侧Func 1:
com.miui.keyguard.shortcuts.manager.ShortcutManager.init (锁屏-快捷方式)

业务侧Func 2:
miui.systemui.dynamicisland.window.DynamicIslandWindowViewCreator.createView(通知-灵动岛)

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完成摘要生成与校验。

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

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



原因分析:
在小窗模式代码中,使用了带Throwable的3参数 android.util.Slog.d 打印日志,引起此项内存消耗。
解决方案:
- 应用侧,替换为不带Throwable的2参数 android.util.Slog.d 接口;@苗健
- 系统侧,调整外发user版本的LogLevel 输出日志等级为 Info 级别(目前为 Debug 级别)
Perfetto 抓取Trace相关
android.util.proto.EncodedBuffer.

android.util.proto.ProtoInputStream.readRawString

原因分析:
Perfetto 抓trace引入,此项可忽略!
解决方案:
关闭所有非必要trace点。
View创建相关
android.view.LayoutInflater.createView
View的创建与加载(上游)
涉及业务:
控制中心:

锁屏:

通知:

android.view.View.
View的创建与加载(下游)
原因分析:
各模块View的加载与预加载。
解决方案:
对于显示实时性要求不高的部分布局,改为懒加载(Lazy)模式。
android.view.InsetsSource.

启动场景下,android.view.InsetsSource 的创建个数,远多于竞品VIVO。 相关业务侧代码:com.android.wm.shell.pip.phone.PipController (画中画)
—责任人 @苗健
org.json.JSONTokener.nextString
Json解析。

原因分析:
小米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对象,对比 小米与荣耀的差异:

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

详情见:
- SystemUI开机场景Java堆存量内存对比(wiki:
K0X6wzbrMiiR7YkBsmpcaGKXnwb) - java堆分析数据(sheets:
VSpasO5MohWYKYtnHPGc2C9vntb)
然后按照Heap差距由多到少,就可以通过MAT工具,逐项查看各个遗留对象的强引用链,进而定位到具体的业务侧class对象,最后结合业务代码进一步分析得出解决方案:有无必要性?是否可以减少?

具体定位方法可以参考文档:
- Systemui hprof分析实操(kotlinx.atomicfu.AtomicRef)(wiki:
I8Q4wkLu5iucqXkCYRVcosK4nkc)