裁剪 JVM/Android HPROF 内存快照文件

已经很久没有写文章了,这期间多年的同事也被裁了,公司风雨飘摇,自己投的简历也是石沉大海,未来也是一片迷茫,感叹之际也是不能忘记学习。来补一下前面的坑,我在前面的文章 Android HPROF 内存快照文件详解 中介绍了内存快照文件的基本格式,其中的测试代码性能也不好,大文件会有 OOM 的情况,因为我当时只是针对学习 HPROF 文件格式而没有在意性能,当然那个代码也是没有办法真实使用的,还有人提到想要实操如何裁剪 HPROF 文件的大小,所以有了本篇文章。

分析 HPROF 文件的各种 Record 大小占比

我们想要裁剪 HPROF 文件,当然想要知道大致的文件中的各种内容的大小占比,这样我们动刀才有方向。我给一个我的分析结果(这只是我的这个文件的结果,千万不要生搬硬套,不同的应用有少许的不太一样,大致的方向差不多):

text 复制代码
AllSize: 39.59 MB
Records:
StringRecord: Size=4.10 MB, Count=140194, SizeInPrecents=10.36 %
LoadClassRecord: Size=465.52 KB, Count=29793, SizeInPrecents=1.15 %
UnloadClassRecord: Size=0 B, Count=0, SizeInPrecents=0.00 %
StackFrameRecord: Size=0 B, Count=0, SizeInPrecents=0.00 %
StackTraceRecord: Size=12 B, Count=1, SizeInPrecents=0.00 %
UnknownRecord: Size=0 B, Count=0, SizeInPrecents=0.00 %
SubRecords: 
RootUnknownSubRecord: Size=32 B, Count=8, SizeInPrecents=0.00 %
RootJniGlobalSubRecord: Size=3.80 KB, Count=486, SizeInPrecents=0.01 %
RootJniLocalSubRecord: Size=516 B, Count=43, SizeInPrecents=0.00 %
RootJavaFrameSubRecord: Size=8.46 KB, Count=722, SizeInPrecents=0.02 %
RootNativeStackSubRecord: Size=64 B, Count=8, SizeInPrecents=0.00 %
RootStickyClassSubRecord: Size=95.57 KB, Count=24465, SizeInPrecents=0.24 %
RootThreadBlockSubRecord: Size=0 B, Count=0, SizeInPrecents=0.00 %
RootMonitorUsedSubRecord: Size=0 B, Count=0, SizeInPrecents=0.00 %
RootThreadObjectSubRecord: Size=624 B, Count=52, SizeInPrecents=0.00 %
RootInternedStringSubRecord: Size=168.33 KB, Count=43093, SizeInPrecents=0.00 %
RootFinalizingSubRecord: Size=0 B, Count=0, SizeInPrecents=0.00 %
RootDebuggerSubRecord: Size=0 B, Count=0, SizeInPrecents=0.00 %
RootReferenceCleanupSubRecord: Size=0 B, Count=0, SizeInPrecents=0.00 %
RootVmInternalSubRecord: Size=973.11 KB, Count=249116, SizeInPrecents=2.40 %
RootJniMonitorSubRecord: Size=0 B, Count=0, SizeInPrecents=0.00 %
RootUnreachableSubRecord: Size=0 B, Count=0, SizeInPrecents=0.00 %
HeapDumpInfoSubRecord: Size=65.35 KB, Count=8365, SizeInPrecents=0.16 %
ClassDumpSubRecord: Size=9.99 MB, Count=29793, SizeInPrecents=25.24 %
InstanceDumpSubRecord: Size=7.38 MB, Count=202221, SizeInPrecents=18.64 %
PrimitiveArrayDumpSubRecord: Size=14.03 MB, Count=148756, SizeInPrecents=35.44 %
ObjectArrayDumpSubRecord: Size=2.34 MB, Count=30099, SizeInPrecents=5.92 %
ClassDumpFields: 
ConstField: Size=0 B, Count=0, SizeInPrecents=0.00 %
StaticRefField: Size=2.80 MB, Count=326259, SizeInPrecents=7.07 %
StaticPrimitiveField: Size=5.70 MB, Count=637875, SizeInPrecents=14.41 %
MemberRefField: Size=185.48 KB, Count=37986, SizeInPrecents=0.46 %
MemberPrimitiveField: Size=117.30 KB, Count=24024, SizeInPrecents=0.29 %

我这里总结一下占比比较高的部分:

  1. 基本类型数组 35.44%
  2. Class 对象实例 25.24%
  3. 普通对象实例 18.64%
  4. 字符串 10.36%
  5. 对象应用数组 5.92%

分析的代码在这里,你也可以替换成你们自己的文件试试。

当有一个大概的分析结果了,然后我们再考虑如何裁剪。

裁剪 HPROF 文件

根据自己的不同的需求,可以有不同的裁剪方式,像我自己的需求就是想知道哪些对象占用了比较大的内存,同时想要知道这些对象到 GCRoot 的路径,而对这些对象中的内容没有那么多的关心。根据我们自己的需求就有了裁剪的方案。

裁剪基本类型的数组

占比最高的部分就是它,很多时候实际情况比我上面测试的比例可能更高。所以裁剪它的收益会很高。我对它的裁剪方案是将所有的基本类型数组中的值全部设置为 0。 为什么要设置为 0 呢?而不是直接删除,如果直接删除的话对内存大小的计算就有误了,如果设置为 0,然后再对后续的结果 zip 压缩,就能够获取到小很多的文件。我对所有的基本类型的成员变量,静态变量都是这么处理的,只保留引用类型的值,因为我们想要知道 GCRoot 的路径。

裁剪 Class 对象实例

Class 对象实例中能够裁剪的部分只有静态变量,我的方法是将非引用类型的静态变量的值全部设置为 0。

裁剪普通对象实例

普通对象实例中同样是只保留类型为引用类型的成员变量,其他类型的变量设置为 0。 这里我还要说一个小问题,当我将非引用类型的成员变量全部设置为 0 后,在 Android Profiler 中无法获取 Native 的内存占用大小,比如 BitmapAndroid 8 以后它是占用 Native 的内存)和 Binder 所占用的 Native 内存大小。我暂时不知道是哪个成员变量保存的 Native 内存大小,如果后续知道了,可以优化一下这个部份。

裁剪字符串

Android 中我们需要保留类名,类中的变量名和 DumpInfoSubRecord 中的名字,就好了,其他的字符串可以全部删除;Android 中的 HPROF 文件中还保留了每个线程的栈信息,每个栈帧都有对应的方法名,方法签名,还有方法源文件名字,如果你不需要也可以删除。

最后

说了半天理论也没有说服力,Show me your code. 我自己实测,原来大小为 43.86MBAndroid 环境的 HPROF 文件,处理后大小变成了 7.05MB (如果激进点可以压到 6MB 一下,不过会丢失一些大小数据);原来大小 419MBJVM 环境的 HPROF 文件,处理后大小变成 49MB。上面的 JVM HPROF 文件内存占用大小为 250MB,如果大小目前大部份虚拟机的内存大小上限 512MBHPROF 的文件大小最大也在 1GB 左右,处理完也就是在 100MB 左右。裁剪的测试代码

当然你也不要那么死心眼儿,你也可以完全根据自己的需求对裁剪做出调整,比如我想要保留 Bitmap 实例中的全部信息;也可以完全不按照我的方案,但是你还是可以通过我的库 tHprofParser来实现你的裁剪方案,我是模仿 JVM 字节码修改库 ASM 来设计接口的,如果你觉得不错,希望得到你的 Star.

相关推荐
一起搞IT吧34 分钟前
Camera相机人脸识别系列专题分析之十九:MTK ISP6S平台FDNode传递三方FFD到APP流程解析
android·图像处理·人工智能·数码相机·计算机视觉
wyjcxyyy1 小时前
打靶日记-RCE-labs
android
测试者家园3 小时前
慢查询日志在性能优化中的价值
sql·性能优化·性能测试·慢查询·持续测试·智能化测试
鼠鼠我捏,要死了捏7 小时前
基于Spring WebFlux的高并发响应式系统性能优化实践指南
性能优化·reactive·spring webflux
一笑的小酒馆10 小时前
Android12去掉剪贴板复制成功的Toast
android
一笑的小酒馆11 小时前
Android12App启动图标自适应
android
十盒半价11 小时前
React 性能优化秘籍:从渲染顺序到组件粒度
react.js·性能优化·trae
程序员江同学12 小时前
Kotlin 技术月报 | 2025 年 7 月
android·kotlin
某空m13 小时前
【Android】内容提供器
android
Greenland_1213 小时前
Android 编译报错 Null extracted folder for artifact: xxx activity:1.8.0
android