Systemui Java堆-Hprof对比分析
来源:飞书 wiki(obj_token: JEW9dhbtOobKE8xUFHecVs2Tnxb)
附件说明:文中 7 个文件附件(2 个 hprof dump、MAT 安装包、hprof-conv.exe、HprofMatOpener 安装器+使用说明、showmap 数据 zip)已保存至
attachments/JEW9dhbtOobKE8xUFHecVs2Tnxb/。文档中图表/截图为飞书 authcode 内联图片,无法外链访问,已转为文字描述。
Showmap 统计 java 堆,gc 之后,P1 44M / magic 25M,相差约 19M。
📷 图片描述:P1daily - iter2 - 匿名页 - Java RSS Change Map 图表,展示了匿名页 Java RSS 值随 Round 变化的情况。横轴为 Round(01
12),纵轴为匿名页 Java RSS 值(KB,0225000)。蓝色折线代表 P1daily (iter2) - 匿名页 - Java 的 RSS 值,在 102400 ~ 520436 KB 之间波动,整体呈下降趋势。
📷 图片描述:systemui_traces_MagicBPro(iter2)- 圈名页 - Java RSS Change Map 折线图,展示 Java RSS 值随 Round 变化。蓝色折线数值在 28000KB ~ 68704KB 之间波动。
Hprof 统计到的堆内存,P1 57.6M / magic 49.3M,相差约 8.3M。
虽然数据无法对上,但是 java 堆总体趋势 P1 确实差于 magic,目前建议通过 hprof 分析。
附件:
👀 为什么 HPROF(57.6M)> showmap(44M)?
showmap 中统计规则有问题,后续会和 meminfo 中的 java heap 大致保持一致。 最终使用 hprof 分析,业务侧可以以 hprof 展示的堆内存占用为准。
对比分析 P1 和 magic 的 Hprof,并附上详细步骤。
预置条件
使用自动化工具(推荐):
- 安装 JDK,配置环境变量:MAT 要依赖 java 环境
- 安装 HprofMatOpener-Installer-Bundled:
附件:HprofMatOpener-Installer-Bundled.exe
-
具体使用可查看:HprofMatOpener-使用说明.md
-
安装完成之后,双击任何从手机中 pull 出的 hprof 即可在 MAT 中打开,无需环境依赖和格式转换
PS:之前的 hprof_manager.exe 存在一些信息丢失的问题,会造成 hprof 文件较小,请使用上述最新版本
或者手动一步步安装环境依赖:
- 安装 MAT:
- 安装 JDK
- adb 抓取 hprof:
adb shell am dumpheap 包名/PID(建议使用pid) /data/local/tmp/test.hprof- 格式转换:
hprof-conv.exe 是 Android SDK 自带的工具,作用是将 Android 设备上生成的原始 hprof 文件转换为标准 JVM 格式。
hprof-conv.exe test.hprof test.hprof对比分析步骤
1. 同时打开两个 hprof 文件
📷 图片描述:在 Eclipse Memory Analyzer 工具中同时打开
p1.hprof和magic.hprof两个 hprof 文件的界面。下方的数据表格按照类名对堆中对象进行了分组统计,列出了各对象的数量、浅堆内存占用(Shallow Heap)和深堆内存占用(Retained Heap)等信息。
2. 打开 Histogram(堆直方图)
在 MAT 中,Histogram(堆直方图) 是最基础且常用的内存分析工具之一,它会按照类名对堆中所有对象进行分组统计,直观展示每个类的对象数量、内存占用情况。
定位大对象 / 高频对象:找出占用内存最多、对象数量最多的类,缩小问题范围。
📷 图片描述:Eclipse Memory Analyzer 中 hprof 文件的 Histogram(堆直方图)界面,按类名对堆中所有对象进行分组统计,显示类名、对象数量、浅堆内存占用、保留内存占用等信息。其中
com.example.MyActivity类对象数量为 193,327,浅堆内存占用 19,017,360 字节,保留内存占用 19,017,480 字节。
补充说明(Histogram 字段含义):
| Class Name | 类的全限定名(如 byte[]、java.lang.String、com.example.MyActivity) | 区分系统类和自定义类,优先关注自定义类的异常。 |
|---|---|---|
| Objects | 该类在堆中的对象数量 | 数值过大(如几十万)可能是对象堆积;对比快照时的增量是关键。 |
| Shallow Heap | 该类所有对象自身占用的内存总和(不包含引用对象的内存) | 反映对象本身的大小,比如 byte[] 的 Shallow Heap 直接对应数组长度。 |
| Retained Heap | 该类所有对象及其引用链中对象占用的总内存(即该类对象被回收后能释放的内存) | Retained Heap 过大说明该类对象是内存泄漏的核心载体。 |
和 Dominator Tree(支配树)的区别:Histogram 是 “类” 维度的统计,而 Dominator Tree 是 “单个对象” 维度的统计。
- 如果你想知道 “哪个类的对象整体占用内存最多”,用 Histogram;
- 如果你想知道 “哪个具体对象占用内存最多”,用 Dominator Tree。
3. 结合 Compare Tables 功能,对比两个快照的 Histogram
结合 Compare Tables 功能,对比两个快照的 Histogram,发现类的对象 / 内存变化,定位差别对象。
分别将两个 hprof 的 Histogram 添加到 Compare Basket。
📷 图片描述:MAT 工具中 Overview Pane 的右键菜单,“Add to Compare Basket” 选项被红色框突出显示。
📷 图片描述:Memory Profiler 工具界面,“Results to be compared” 区域分别显示了对比的两个 hprof 文件路径:
C:\Users\bobo.wang\Desktop\hprof\p1.hprof和C:\Users\bobo.wang\Desktop\hprof\magic.hprof。右上角有红色框突出的”红色叹号”按钮,点击可生成 Compared Tables。
点击”红色叹号”,生成 Compared Tables。
📷 图片描述:“Compare Tables” 功能生成的对比表格界面,包含 Class Name、Objects #、Objects -#、Shallow He、Shallow He -#、Retained He、Retained He -# 等列。其中
Java.lang.String类在 Objects -#、Shallow He -#、Retained He -# 列数值较大,分别为 -22,821、-1,077,337、-1,088,456。
为了方便对比,可以导出到 csv 进行 diff 排序,选取 top 差异的类对象进行分析:
📷 图片描述:两个 hprof 快照的 Compare Tables 对比结果,列出了类名、对象数、引用数等信息。红色框突出显示了 “Retained Heap” 和 “Retained Heap 2” 两列数据,其中 “Retained Heap” 列数值较大,表明这些类的对象在内存占用方面存在明显差异。
4. MAT 继续定位类对象引用链,进而定位业务侧具体逻辑
以 com.android.systemui.statusbar.StatusBarIconView 此类对象为例。
📷 图片描述:Compare Tables 功能下的对比表格,以
com.android.systemui.statusbar.StatusBarIconView类对象为例,其对象数量为 244,内存占用变化为 -170,总内存占用为 267,424,总内存占用变化为 -188,688。
P1 上存在大量额外的 StatusBarIconView 对象,比对比机多 170 个 StatusBarIconView 对象。
进一步分析:
- Merge Shortest Paths to GC Roots:把所有 StatusBarIconView 实例到 GC 根引用的最短路径合并,找出它们的共同根引用,快速定位这一类对象的统一持有者。
- exclude all phantom/weak/soft etc. references:排除虚引用、弱引用、软引用等引用类型,只保留强引用,这样能精准定位那些真正导致对象无法被回收的根引用。
📷 图片描述:Eclipse Memory Analyzer 工具界面,呈现
com.android.systemui.statusbar.StatusBarIconView类的对象相关信息。“Merge Shortest Paths to GC Roots” 和 “exclude all phantom/weak/soft etc. references” 两项被红色框突出显示。
- 测试机 244 个 StatusBarIconView 实例,最终被 9 个不同的根引用持有
- 对比机 74 个 StatusBarIconView 实例,最终被 2 个不同的根引用持有
📷 图片描述:MAT 工具中 “Merge Shortest Paths to GC Roots” 功能的分析结果,列出多个类名及对应的 Referenced Objects、Shallow Heap、Ref. Shallow Heap、Retained Heap 等数据。其中 StatusBarIconView 类的 Referenced Objects 为 42,Shallow Heap 为 40,532,Ref. Shallow Heap 为 40,532,Retained Heap 为 40。
📷 图片描述:Eclipse Memory Analyzer 中 Compare Tables 界面。
com.android.systemui.statusbar.HwCommandQueue类的 Referenced Objects 为 43,Shallow Heap 为 88,Ref. Shallow Heap 为 45,752,Retained Heap 为 2,168;com.android.systemui.keyguard.HwKeyguardViewMediator$CoverViewDelegate类的 Referenced Objects 为 31,Shallow Heap 为 40,Ref. Shallow Heap 为 32,984,Retained Heap 为 40。
进一步展开上述第一个结果:展示了 StatusBarIconView 到根引用(ContentObserver$Transport)的最短路径,以及路径上的关键节点(如 CollapsedStatusBarFragment、MuiStatusIconContainer)。
📷 图片描述:从 StatusBarIconView 到根引用(
ContentObserver$Transport)的最短路径关键节点信息,列出了 Class Name、Referenced Objects、Shallow Heap、Ref. Shallow Heap、Retained Heap 等数据,关键节点包括 mContentObserver、this$0、mNtViewBinder、mNotificationIconAreaRenderer 等,部分节点数值被红色框突出显示。
对于这种批量分析差异的情况,比较适合最短路径的根引用,进一步展开:即可看到 37 个 StatusBarIconView 详细信息和地址。
📷 图片描述:从 StatusBarIconView 到根引用(
ContentObserver$Transport)的最短路径上的关键节点信息,列出了节点名称、地址、引用数、引用类型等数据,如 mContentObserver、mStatusIconContainer 等节点,其引用数均为 42 或 37,引用类型多为 View。
5. 竞品机 magic 同样的操作展开相关引用链
竞品机 magic 同样的操作展开相关引用链,即可定位到差异根源。
👀 最终结论:
与业务讨论,多状态栏 / 怀疑对比机相关图标进行了懒加载(lazy mode),导致状态栏 / 下拉通知栏的图标数量 / 堆内存差异,后续业务模块会进一步给出修改方案。
另外一个分析案例可以查看:Systemui hprof分析实操(doc-id: I8Q4wkLu5iucqXkCYRVcosK4nkc)
发现的外链引用(去重):
| doc-id | file-type | title |
|---|---|---|
| I8Q4wkLu5iucqXkCYRVcosK4nkc | wiki | Systemui hprof分析实操 |