Shallow Size是对象自身内存大小,不包含引用对象;Retained Size是该对象被GC回收后可释放的总内存,等于其自身大小加所有仅通过它可达的对象大小之和。

Shallow Size 看的是对象“自身体重”,不是它带的包袱
Shallow Size 就是对象在堆里占的“裸体积”:对象头(64位压缩指针下通常 12 字节)、成员变量字段(int 占 4 字节、long 占 8 字节、引用字段在压缩指针下占 4 字节)、以及为对齐补的 padding。它不包含任何被该对象引用的其他对象——哪怕它持有一个 10MB 的 Uint8Array,Shallow Size 也只算那个 4 字节的引用本身。
常见误判点:
- 把
Shallow Size当成“实际内存开销”,结果忽略真正吃内存的引用目标 - 在 MAT 或 Chrome DevTools 中看到某个
ArrayBuffer的 Shallow Size 很小(比如 32 字节),就认为它不重要 - 用
jmap -histo:live输出的bytes列直接估算总内存压力——那全是 Shallow Size,严重低估真实占用
Retained Size 才是你能“松手”后真正回收的量
Retained Size = 当前对象自身大小 + 所有仅通过它才能到达的对象大小之和。换句话说:如果这个对象被 GC 回收,且没有其他 GC Root 或强引用指向它的子图,那么 Retained Size 就是这次回收能腾出的全部堆空间。
关键逻辑在于“独家支配”(Dominator):若对象 B 只能从 A 出发到达,而无法从其他 GC Root 到达,那么 A 就支配 B,B 就计入 A 的 Retained Size。
实操建议:
AI 舌诊面诊问诊医疗系统 + 诊疗商城系统源码是一套结合 AI 辅助诊断与商品转化的健康类程序,基于浩创云、ThinkPHP(PHP)开发,支持微信服务号与 H5 端,适合做中医舌诊面诊问答、健康建议推送以及配套诊疗商品的运营。
- 在 Chrome DevTools 的 Memory 面板中,选中一个疑似泄漏对象,右键 →
Reveal in Dominators view,看它在支配树里的位置 - 重点关注 Retained Size 排名靠前但业务上“早该销毁”的对象,比如已卸载组件的
React.Component实例、关闭后的WebSocket实例 - 注意 Retained Size 会重复统计:若两个对象共同引用同一个大缓存
Map,该Map的大小会被分别计入两者的 Retained Size —— 这不是 bug,是支配关系导致的自然结果
闭包 + 大数组 = Retained Size 暴增的典型组合
闭包本身 Shallow Size 极小(可能就几十字节),但它捕获的自由变量如果是一个未释放的 ArrayBuffer 或 ImageBitmap,其 Retained Size 就会飙升。这类泄漏往往不会报错,但内存曲线持续爬升。
识别方法:
- 在堆快照中筛选
Closure类型,按 Retained Size 降序排列 - 展开闭包的
Captured values,检查是否有意外持有的大对象(如data、cache、buffer等变量名) - 对比同组件多个实例的闭包 Retained Size:若某次操作后,新实例的 Retained Size 明显高于旧实例,说明有状态累积
- 注意:箭头函数不自动创建命名闭包,在快照中可能显示为
(anonymous),需结合调用栈和上下文判断
DOM 引用链常让 Retained Size “虚高”却不真实可回收
一个已从 DOM 树移除但被 JS 闭包或全局变量意外保留的 HTMLDivElement,其 Retained Size 可能高达几 MB(含子元素、样式、事件监听器等)。但它是否真构成泄漏,取决于你是否还有意使用它。
排查要点:
- 搜索
Detached DOM tree节点(Chrome DevTools 中勾选 “Objects allocated before last gc” 后更容易暴露) - 检查该节点的 retaining path:终点是不是
window、document或某个长期存活的模块级Map/Set - 特别留意通过
addEventListener绑定但未removeEventListener的监听器,它们会让整个子树无法释放 -
URL.createObjectURL()创建的 blob URL 必须配对调用URL.revokeObjectURL(),否则对应Blob会一直被 retain
setInterval 回调,才是你该动手的地方。

















