ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

MAT内存分析深度指南:从Leak Suspects到Dominator Tree实战

MAT内存分析深度指南:从Leak Suspects到Dominator Tree实战 1. 这不是“点开就能看”的工具而是一把需要校准的手术刀很多人第一次听说MATMemory Analyzer Tool是在 Android 开发群里看到一句“OOM 前先跑个 MAT 看看”。接着下载 Eclipse MAT拖进一个 hprof 文件点开“Leak Suspects”看到红色感叹号和“128 MB retained heap”就以为找到了罪魁祸首——然后改了代码发版OOM 还是照常发生。我去年在做一款车载中控应用时也这么干过。当时内存占用从启动后 80MB 涨到 320MBGC 频繁卡顿。用 MAT 打开 hprofLeak Suspects 报告里赫然写着“android.view.ViewRootImpl$RunQueue → Handler → Message → Callback → MainActivity”路径清晰、引用链完整、retained heap 216MB。我们立刻把 MainActivity 里那个匿名内部类 Handler 改成静态 WeakReference信心满满地合入主干。结果上线三天后监控平台报警内存峰值反而上升了 15%。问题出在哪不是 MAT 错了而是我们把它当成了“自动诊断仪”却忘了它本质是一把需要理解组织结构、掌握解剖逻辑、能识别伪影与干扰项的手术刀。MAT 不输出结论它只呈现快照中对象的拓扑关系它不判断业务逻辑是否合理只忠实反映 GC Roots 的可达性它不区分“该留的缓存”和“该杀的泄漏”只告诉你“这个对象为什么没被回收”。这正是绝大多数人用不好 MAT 的根本原因跳过了内存模型认知→hprof 生成时机选择→视图语义解读→泄漏根因反推这四个不可省略的环节。你看到的“Leak Suspects”报告其实是 MAT 基于预设启发式规则比如持有 Activity/Context 的非静态内部类、未注销的监听器等做的概率性提示不是司法鉴定书。它可能漏掉真正的泄漏比如弱引用被意外强持也可能把合法的长生命周期对象误判为泄漏比如全局图片缓存池。所以本文不讲“如何下载 MAT”或“点击哪几个按钮”而是带你回到泄漏发生的现场从 Java 堆内存的底层布局讲起拆解 hprof 文件的真实结构手把手还原 MAT 是如何从二进制字节流中重建出对象图谱的再以一个真实车载导航模块的泄漏案例为线索演示如何绕过 Leak Suspects 的“第一印象”用 Dominator Tree 定位真正阻断 GC 的节点用 Path to GC Roots 验证引用链的业务合理性最后用 OQLObject Query Language写定制查询揪出那个藏在 EventBus 订阅表里的“幽灵引用”。你不需要记住所有菜单路径但必须理解MAT 的每一个视图都是对 JVM 内存快照的一次特定投影每一次点击都在施加一个隐含的过滤条件而真正的泄漏永远藏在“条件之外”的空白区域里。2. hprof 文件不是日志而是堆内存的“CT 断层扫描胶片”很多开发者把 hprof 文件当成普通日志文件对待——双击打开、搜索关键词、复制堆栈。这是最危险的误用。hprofHeap Profile本质上是一份JVM 堆内存的二进制快照snapshot其结构与你在代码里操作的对象实例、引用关系、类定义完全一一对应但它不包含任何运行时上下文如线程状态、方法调用栈、变量名含义。你可以把它想象成医院 CT 设备拍出的断层扫描胶片每一张胶片都精确记录了某个瞬间人体横截面的密度分布但单看一张胶片你无法判断这是心脏还是肝脏更无法知道患者此刻是否在呼吸。2.1 hprof 的物理结构三段式二进制编码一个标准 hprof 文件由三个逻辑段组成MAT 解析时必须严格按序处理Header头部固定 24 字节包含 magic numberJAVA PROFILE 1.0\0、版本号、时间戳、全局字符串表偏移量等元信息。这里没有业务数据但决定了整个文件的解析协议。曾遇到一个客户提供的 hprof 在 Win11 上打不开用xxd -l 32 file.hprof查看发现 magic number 被截断为JAVA PROFILE 1.\0——原来是 Windows 资源管理器默认隐藏了.hprof后缀用户实际保存的是file.hprof.txt系统自动添加了.txt后缀却未修改内容导致 MAT 读取 header 失败。String Table字符串表紧随 header 之后存储所有类名、字段名、方法名、常量字符串的 UTF-8 编码。每个字符串以 4 字节长度前缀开头。MAT 解析时所有后续的 class ID、field name ID 都是指向这个表的索引。这意味着如果你在 OQL 中写SELECT * FROM java.lang.String s WHERE s.value.toString().contains(token)MAT 实际上是在遍历整个字符串表做匹配而非在堆对象上执行方法调用——这是 OQL 性能瓶颈的根源之一。Record Stream记录流主体部分由一系列变长 record 组成。每个 record 以 1 字节 tag 标识类型如TAG_HEAP_DUMP_START0x0C表示堆转储开始后跟长度和具体内容。关键 record 类型包括HEAP_DUMPtag0x1C定义堆的起始地址范围CLASS_DUMPtag0x20记录每个类的 static 字段、instance 字段、父类 ID、ClassLoader IDINSTANCE_DUMPtag0x21记录每个对象实例的 class ID、对象地址、所有 instance 字段的值对于引用类型存的是目标对象地址OBJECT_ARRAY_DUMPtag0x22记录对象数组的元素地址列表PRIMITIVE_ARRAY_DUMPtag0x23记录基本类型数组的原始字节。提示MAT 的 “Parse Heap Dump” 过程本质就是将这些二进制 record 流按 tag 解码构建出内存中的 Class、Object、Array 三类节点并用地址作为唯一 ID 建立引用边reference edge。这个过程耗时取决于 record 数量而非文件大小——一个 500MB 的 hprof 如果只包含少量大数组解析快一个 80MB 的 hprof 如果包含数百万小对象解析可能长达 10 分钟。2.2 为什么“dump 时机”比“dump 工具”更重要Android 开发者常问“用adb shell dumpsys meminfo和adb shell am dumpheap有什么区别”答案直指核心前者是统计摘要后者才是原始胶片。dumpsys meminfo返回的是/proc/pid/status和/proc/pid/smaps的聚合计算结果包含 PSSProportional Set Size、Private Dirty 等指标但丢失了对象间的引用关系。它能告诉你“内存用了 320MB”但无法回答“这 320MB 里有多少是被一个未注销的 LocationListener 持有的”。am dumpheap生成的是标准 hprof但必须在泄漏已发生、且尚未被 GC 清理的窗口期抓取。我们曾调试一个 GPS 定位泄漏App 启动后定位服务开启30 秒后用户关闭界面理论上应释放。但dumpheap抓到的 hprof 显示 retained heap 仅 12MB。后来用adb shell ps | grep your.package查 PID再adb shell cat /proc/PID/status | grep VmRSS发现 RSS 持续在 280MB 波动。最终发现LocationManager的removeUpdates()调用被放在onDestroy()但 Activity 因配置变更如横竖屏切换被重建onDestroy()未执行removeUpdates()永远不会被调用。此时正确的 dump 时机是在用户关闭界面后、Activity 尚未被销毁前即onPause()到onStop()之间用adb shell kill -10 PID触发 ANR 并自动生成 hprof需开启debuggabletrue。注意adb shell am dumpheap -n参数-n表示不压缩至关重要。很多团队为了传输方便用gzip压缩 hprof再传给 MAT。但 MAT 的解析器要求原始二进制流若直接打开.hprof.gz会报错 “Invalid HPROF header”。正确做法是先gunzip file.hprof.gz再用 MAT 打开file.hprof。2.3 Win11 下的特殊陷阱文件系统缓存与权限隔离Win11 的 NTFS 文件系统引入了更激进的缓存策略这在处理大 hprof1GB时会引发诡异问题。某次分析一个车载导航的 2.3GB hprofMAT 加载到 78% 时卡死任务管理器显示磁盘活动为 0。排查发现Win11 默认启用 “Windows Search Indexer”它会为新下载的大文件建立索引占用大量 I/O 资源。解决方案是临时禁用索引服务services.msc→ 找到 “Windows Search” → 右键停止。更彻底的做法是将 hprof 存放在C:\temp\避开用户文档库并在 MAT 启动参数中添加-Dorg.eclipse.mat.parser.indexfalse禁用 MAT 自身的索引优化。另一个 Win11 特有问题是UAC用户账户控制权限隔离。当 MAT 以管理员身份运行而 hprof 文件位于用户文档目录如C:\Users\Name\Documents\时由于 UAC 的虚拟化重定向MAT 实际访问的是C:\Users\Name\AppData\Local\VirtualStore\...下的副本导致解析失败。解决方法右键 hprof 文件 → “属性” → “安全” → 确保当前用户有“完全控制”权限或直接将文件移到非系统盘根目录如D:\hprof\。3. Leak Suspects 报告读懂它的“免责声明”而非盲信结论MAT 的 “Leak Suspects” 视图是新手最先接触的入口也是误解最深的模块。它并非泄漏检测引擎而是一个基于预设模式匹配的启发式报告生成器。它的核心逻辑是扫描所有对象找出那些符合“高 retained heap 持有 Context/Activity/View 等敏感类实例”组合的对象并按 retained heap 降序排列。这个过程不涉及任何业务逻辑判断纯粹是模式匹配。3.1 报告结构解密四层信息嵌套一个典型的 Leak Suspects 报告包含四个嵌套层级每一层都承载不同维度的信息Summary摘要区顶部的饼图和文字说明如 “112 objects were found with a total retained heap of 189 MB”。这里的关键是“were found”—— 它强调这是一个发现结果而非因果结论。饼图中不同颜色区块代表不同泄漏模式如 “Thread Local Storage”、“Static Field”、“Inner Class”但颜色本身无优先级仅作分类标识。Leak Suspect嫌疑对象列表主表格每行对应一个被标记的“泄漏嫌疑对象”。列包括DescriptionMAT 对该对象为何可疑的自然语言描述如 “A thread is holding a reference to a classloader”Retained Heap该对象及其所有不可达子对象的总内存占用Details超链接点击展开下一层。Details详情页点击Details后进入包含两大部分Path to GC Roots (excluding weak refs)这是最关键的证据链展示从该嫌疑对象到 GC Root 的完整引用路径。注意括号里的excluding weak refs—— MAT 默认忽略弱引用WeakReference路径因为弱引用不应阻止 GC。但现实中WeakReference 的 referent 若被其他强引用持有就会变成“假弱引用”此时此路径可能遗漏真凶。References列出该嫌疑对象直接持有的所有字段引用按 retained heap 降序排列。这是寻找“上游持有者”的起点。Objects in Accumulated Retained Heap累积保留堆对象在 Details 页底部列出所有被该嫌疑对象间接持有的对象实例。例如一个Bitmap对象的 retained heap 为 48MB其Objects in Accumulated Retained Heap可能包含 1200 个byte[]、32 个Canvas、8 个Paint。这揭示了内存消耗的构成而非泄漏源头。提示Leak Suspects 的Retained Heap计算基于Dominator Tree支配树。一个对象 A 的 retained heap A 自身大小 所有被 A 支配即只能通过 A 到达的对象大小之和。MAT 通过 Tarjan 算法构建支配树时间复杂度 O(ne)其中 n 是对象数e 是引用边数。因此对超大堆10M 对象首次计算 retained heap 可能耗时数分钟此时 MAT 界面会显示 “Calculating retained sizes…”。耐心等待不要强行中断否则后续所有视图如 Dominator Tree将失效。3.2 三大经典误判场景及验证方法场景一合法缓存被误判为泄漏现象Leak Suspects 报告指出com.yourapp.cache.ImageCache占用 156MB retained heap路径为static ImageCache.INSTANCE → HashMap → Bitmap。验证方法切换到Dominator Tree视图右键ImageCache→Merge Shortest Paths to GC Roots→with all references注意勾选with all references而非默认的excluding weak refs。如果路径中出现java.lang.ref.WeakReference或java.lang.ref.SoftReference说明这是弱/软引用缓存其 retained heap 属于正常设计。在OQL控制台执行SELECT COUNT(*) FROM com.yourapp.cache.ImageCache c WHERE c.size 1000。若返回1且c.size为 1200结合业务逻辑缓存上限设为 1200 张图即可确认为合法行为。场景二泄漏源头被“路径截断”现象Leak Suspects 报告android.app.Activity占用 89MB路径为Activity → View → Drawable → Bitmap。但修复Drawable泄漏后OOM 依旧。根因分析MAT 的Path to GC Roots默认只显示一条最短路径而真实泄漏可能有多个并行路径。Activity被持有未必是因为View更可能是Handler、BroadcastReceiver或AsyncTask。验证方法在Dominator Tree中找到该Activity实例右键 →Show Objects → with outgoing references查看它直接持有的所有对象。在References视图中展开Activity的mHandler字段检查mHandler的mCallback是否指向一个非静态内部类如MyActivity$1。这才是真正的泄漏源头View只是“共犯”。场景三第三方 SDK 的“幽灵引用”现象Leak Suspects 无任何报告但Dominator Tree显示com.google.android.gms.common.api.GoogleApiManager占用 210MB retained heap且Path to GC Roots显示其被java.lang.Thread持有。验证方法在OQL中执行SELECT * FROM com.google.android.gms.common.api.GoogleApiManager g WHERE g.mPendingCallbacks.size 0。若返回非空结果说明 Google Play Services 的 pending callback 队列堆积。结合Thread视图找到持有GoogleApiManager的线程名如GmsClientEvents再查该线程的stackTrace确认是否在执行长时间网络请求后未清理回调。注意Leak Suspects 的启发式规则是硬编码在 MAT 源码中的位于org.eclipse.mat.parser.internal.SuspectsFactory类。它无法识别自定义的泄漏模式如LiveData持有ViewModel导致Activity无法释放。此时必须放弃依赖报告直接进入Dominator Tree和OQL进行人工推理。4. Dominator Tree从“谁占得多”到“谁卡得死”的思维跃迁如果说 Leak Suspects 是“症状清单”那么 Dominator Tree支配树就是“解剖图谱”。它强制你从“哪个对象占用内存最多”的表层思维跃迁到“哪个对象是阻断 GC 的关键闸门”的深层逻辑。理解支配树是 MAT 从“工具”升级为“分析武器”的分水岭。4.1 支配关系的本质一个对象的“生死权”在图论中对象 B支配dominates对象 A当且仅当从 GC Root 到 A 的每一条路径都必须经过 B。这意味着如果 B 被回收A 必定也被回收因为 A 无法再被任何 Root 访问。B 就是 A 的“支配者dominator”A 是 B 的“被支配者dominated”。举个直观例子假设有一个Application对象它持有一个static MapString, Object该 Map 中存有 1000 个Bitmap。那么Application就支配着这 1000 个Bitmap——只要Application还活着这些Bitmap就绝不可能被 GC。Application的 retained heap 它自身大小 这 1000 个Bitmap的总大小。但现实更复杂。考虑一个Activity它被Application的Map持有非法同时也被Handler持有非法。此时Activity有两个支配者Application和Handler。MAT 的支配树会选择最近支配者immediate dominator即离Activity最近的那个支配者。如果Handler的路径更短如Application → Handler → Activity则Handler是Activity的 immediate dominator如果Application直接持有ActivityApplication → Activity则Application是 immediate dominator。4.2 构建支配树Tarjan 算法的工程实现MAT 使用 Tarjan 的深度优先搜索DFS算法构建支配树步骤如下简化版构建对象图将所有对象视为节点所有引用关系obj1.field obj2视为有向边形成一个有向图 G。选定根节点以所有 GC Roots 为超级源点super root添加一条从 super root 到每个 GC Root 的边。DFS 遍历从 super root 开始 DFS为每个节点分配一个dfs_number访问序号。计算支配边界对每个节点 u计算其semi(u)半支配点即所有能到达 u 的节点 v 中dfs_number[v]最小的那个 v。构建支配树根据semi(u)和idom(u)立即支配者的关系递归构建树。这个过程在 MAT 中是后台异步执行的。当你首次打开Dominator Tree视图时MAT 会显示 “Building dominator tree…”。对于 500 万个对象的 hprof此过程可能耗时 3-5 分钟。切勿在此期间关闭 MAT 或切换视图否则需重新计算。4.3 实战用支配树定位“真凶”而非“从犯”回到车载导航的泄漏案例。Leak Suspects 报告指向NavigationService占用 142MB路径为NavigationService → LocationClient → LocationListener。我们按常规思路将LocationListener改为静态内部类问题依旧。正确操作流程打开 Dominator Tree在 MAT 主菜单Histogram→Dominator Tree。排序与筛选点击Retained Heap列标题按降序排列。找到NavigationService实例假设为com.yourapp.service.NavigationService1a2b3c4d。查看支配者右键该实例 →Show In → Dominator Tree。此时视图聚焦于该节点其父节点即为它的 immediate dominator。我们发现NavigationService的 immediate dominator 竟然是java.lang.Thread线程名为NavigationThread。分析线程支配链右键NavigationThread→Show Outgoing References。展开threadLocals字段ThreadLocalMap发现其中table数组的第 17 个槽位table[17]指向一个java.lang.ThreadLocal$ThreadLocalMap$Entry其value字段是一个com.yourapp.util.LocationHelper实例。追溯源头右键LocationHelper→Path to GC Roots→with all references。路径显示NavigationThread → threadLocals → ThreadLocalMap → Entry → value → LocationHelper → mLocationRequest→LocationRequest持有PendingIntent而PendingIntent的mTarget持有Activity的Context。确认根因LocationHelper是一个工具类本应无状态但其mLocationRequest字段被错误地设置为PendingIntent.getActivity(context, ...)将Activity的 Context 传入。NavigationThread作为后台线程生命周期远长于Activity导致Activity被强持。修复方案将PendingIntent.getActivity(context, ...)改为PendingIntent.getActivity(context.getApplicationContext(), ...)使用 Application Context 替代 Activity Context。经验支配树的威力在于它揭示了“谁真正握有生杀大权”。NavigationService占内存多但NavigationThread才是卡住 GC 的闸门。Leak Suspects 只看到“占得多”的NavigationService而支配树直接指向了“卡得死”的NavigationThread。这就是从“症状”到“病灶”的关键跃迁。5. OQL用 SQL 思维驾驭对象图谱的终极武器当 Leak Suspects 和 Dominator Tree 都无法给出明确答案时OQLObject Query Language就是你的最后一道防线。它不是简单的对象搜索而是在内存快照这个“数据库”上执行的 SQL 查询。你需要像 DBA 一样理解表结构类、字段属性、索引引用关系才能写出高效、精准的查询。5.1 OQL 基础语法与 SQL 的异同OQL 语法高度借鉴 SQL但针对对象图谱做了关键适配SQL 元素OQL 对应说明SELECTSELECT支持*,class,field,method call有限制FROMFROM指定类名如java.lang.String或带通配符java.*WHEREWHERE条件表达式支持,!,,,LIKE,IN,IS NULL等JOINOBJECTS/IN REFERENCEOQL 不支持传统 JOIN但可通过SELECT ... FROM class1 c1, class2 c2 WHERE c1.field c2实现隐式连接GROUP BYGROUP BY支持用于聚合统计ORDER BYORDER BY支持可按字段或计算值排序关键差异无主键概念OQL 中每个对象实例是唯一的但没有显式主键。SELECT * FROM java.lang.String返回所有String实例。方法调用受限SELECT s.toString() FROM java.lang.String s是非法的因为toString()可能触发副作用如初始化 lazy 字段。OQL 只允许调用final方法或static方法如java.lang.String.valueOf(123)。引用字段访问s.value访问String的char[] value字段但s.value.length是合法的因为length是char[]的 public final 字段。5.2 高阶技巧从“找对象”到“挖关系”技巧一定位“被强持的弱引用”弱引用本不该阻止 GC但如果其referent被其他强引用持有就失效了。查找此类“幽灵弱引用”SELECT r, r.referent FROM java.lang.ref.WeakReference r WHERE r.referent ! null AND r.referent.retainedHeap 1000000此查询找出所有referent非空且 retained heap 1MB 的WeakReference。结果中若r.referent是一个Activity则需进一步查r.referent的Path to GC Roots确认其被谁强持。技巧二追踪“静态集合的膨胀”静态集合是泄漏高发区。监控HashMap的 sizeSELECT h, h.size, h.table.length FROM java.util.HashMap h WHERE h.size 1000 ORDER BY h.size DESC若发现h.size为 5000h.table.length为 128则说明哈希桶严重冲突get()效率暴跌这虽非内存泄漏却是性能瓶颈。技巧三识别“循环引用链”循环引用A→B→A本身不导致泄漏GC 可处理但若链中任一节点被 GC Root 持有则整条链都无法释放。查找深度为 2 的循环SELECT a, b FROM com.yourapp.model.Node a, com.yourapp.model.Node b WHERE a.next b AND b.next a5.3 实战破解 EventBus 的“订阅幽灵”EventBus 3.x 的StickyEvent和Subscriber注册表是泄漏重灾区。标准 Leak Suspects 很难捕获因其引用链常绕过 Activity。场景Activity 关闭后内存未释放Dominator Tree显示org.greenrobot.eventbus.EventBus占用巨大。OQL 排查定位 EventBus 实例SELECT * FROM org.greenrobot.eventbus.EventBus e WHERE e.defaultInstance true找到defaultInstance的EventBus对象。查询其 subscriber 集合SELECT s, s.subscriberMethod, s.subscriber FROM org.greenrobot.eventbus.Subscription s IN REFERENCES OF (SELECT * FROM org.greenrobot.eventbus.EventBus e WHERE e.defaultInstance true)此查询利用IN REFERENCES OF语法找出所有被EventBus直接或间接引用的Subscription。过滤出持有 Activity 的订阅者SELECT s, s.subscriber, s.subscriberMethod FROM org.greenrobot.eventbus.Subscription s WHERE s.subscriber.className LIKE %Activity% OR s.subscriber.className LIKE %Fragment%验证未注销检查s.subscriber的Path to GC Roots若路径中出现EventBus→subscriptionsByEventType→CopyOnWriteArrayList→Subscription则确认该Activity未调用unregister()。修复在Activity.onDestroy()中确保EventBus.getDefault().unregister(this)被执行且this是注册时传入的同一实例。经验OQL 的力量在于其“组合查询”能力。单个查询可能信息有限但通过SELECT ... FROM class1 WHERE ... IN (SELECT ... FROM class2)的嵌套可以像侦探一样沿着一条线索层层剥茧直至真相。它不提供答案但赋予你提问的权力——而所有泄漏都怕被正确地提问。6. 从分析到闭环建立可持续的内存治理工作流MAT 分析的终点不是生成一份报告而是推动一次有效的代码修复并建立防止同类问题复发的机制。一个成熟的内存治理工作流必须覆盖“事前预防→事中监控→事后分析→持续改进”全链路。6.1 事前用 Lint 和 Static Analysis 筑起第一道墙依赖人工 MAT 分析是被动的。应在开发阶段就植入防护Android Lint 规则启用SyntheticAccessor、HandlerLeak、StaticFieldLeak等内置规则。在build.gradle中android { lintOptions { abortOnError true check HandlerLeak, StaticFieldLeak, ResourceType } }自定义 Lint Check针对公司 SDK编写Detector检查YourSDK.init(Context)是否传入ActivityContext。原理是扫描 AST查找Context参数的resolve()结果是否为Activity子类。6.2 事中在 CI/CD 中嵌入自动化内存基线测试将 MAT 分析能力集成到流水线录制基准场景用 UI Automator 录制一段标准操作如“启动 App → 进入首页 → 滚动列表 10 次 → 返回”。自动 dump在操作前后执行adb shell am dumpheap -n /data/local/tmp/before.hprof和after.hprof。MAT CLI 分析使用 MAT 的命令行工具ParseHeapDump.shLinux/Mac或ParseHeapDump.batWinParseHeapDump.bat before.hprof org.eclipse.mat.api:top_components ParseHeapDump.bat after.hprof org.eclipse.mat.api:top_components生成top_components.csv提取Retained Heap最大的 10 个类。 4.基线比对脚本比对before和after的Retained Heap差值若Activity类的差值 5MB或Bitmap类的差值 20MB则构建失败并邮件通知负责人。6.3 事后构建公司级泄漏模式知识库将每次 MAT 分析的成果沉淀为可复用的知识模式编号模式名称触发条件MAT 识别特征修复方案相关 PR 链接MEM-001Activity Context 误传PendingIntent.getActivity(activity, ...)Path to GC Roots中PendingIntent→Activity改用getApplicationContext()#PR-1234MEM-002EventBus 未注销Activity注册后未unregister()Dominator Tree中EventBus→Subscription→ActivityonDestroy()中调用unregister()#PR-5678MEM-003静态集合无清理static Mapkey, value持续 putOQL查询size threshold添加clear()或LruCache#PR-9012此知识库应与公司内部 Wiki 和 Code Review Checklist 关联新成员入职时必须学习MEM-*模式。6.4 持续用 MAT 插件扩展分析维度MAT 本身可扩展。我们开发了一个轻量插件MatLeakInspector它在Dominator Tree右键菜单中增加Analyze Context Leakage自动扫描所有Context子类实例生成Path to GC Roots报告。Compare Two Dumps加载两个 hprof高亮新增的、retained heap 增长 1MB 的对象。Export Leak Report一键导出 PDF 报告包含截图、OQL 查询、修复建议。插件源码开源在公司内网 GitLab所有 Android 开发者均可安装将 MAT 从个人工具升级为团队标准。最后分享一个小技巧在 MAT 中按CtrlShiftTWindows/Linux或CmdShiftTMac可以快速打开任意视图如Dominator Tree、OQL、Thread无需在菜单栏中层层查找。这个快捷键是我每天节省 5 分钟的秘诀——而一年下来就是整整 30 小时足够你深入研究透一个复杂的泄漏案例。
返回列表