Depth、Native Size、Shallow Size、Retained Size 解析
一、指标定义与对比
指标 | 定义 | 计算逻辑 | 重要性 |
---|---|---|---|
Shallow Size | 对象自身实例占用的内存 | 基本类型字段大小 + 引用指针 + 内存对齐 | 对象的基础内存成本 |
Retained Size | 回收该对象可释放的总内存量(含所有依赖对象) | Shallow Size + 所有可达对象的 Retained Size | 内存优化的核心目标 |
Native Size | 对象关联的 Native 层(C/C++)分配的内存 | 由 Native 代码分配的实际物理内存大小 | Java GC 无法回收的隐患 |
Depth | 从 GC Root 到对象的最短引用路径层级数 | 引用链跳转次数 (GC Root → 对象) | 设计复杂度的直接体现 |
二、真实案例分析
📁 场景:用户详情页展示头像
arduino
class User {
String name; // Shallow: 4字节 (引用指针)
Bitmap avatar; // Shallow: 48字节
Address address; // Shallow: 8字节 (引用指针)
}
class Address {
String city; // Shallow: 4字节
}
Bitmap avatarBitmap = BitmapFactory.decodeResource(R.drawable.avatar); // Native Size: 12MB (1920x1080 ARGB_8888)
User user = new User(name, avatarBitmap, address);
🔍 内存指标计算结果
对象 | Shallow Size | Native Size | Depth (GC Root → 对象) | Retained Size (依赖链) |
---|---|---|---|---|
User实例 | ≈ 56 字节 | 0 | 2 (GC Root → Activity → user) | 56B + avatarBitmap的总占用 + address的总占用 |
Bitmap实例 | ≈ 48 字节 | 12MB | 3 (GC Root → Activity → user → avatar) | ≈12MB (像素数据主导) |
Address实例 | ≈ 32 字节 | 0 | 3 (GC Root → Activity → user → address) | 32B + city对象大小 |
String city | ≈ 24 字节 | 0 | 4 | 24B |
三、指标深度解析
1. Shallow Size 计算规则
-
基本类型:int(4B), boolean(1B), long(8B)
-
引用类型:固定 4 或 8 字节(32/64位系统)
-
内存对齐:JVM 按 8 字节对齐(示例中的 User:
scssname(4) + avatar(4) + address(4) + 对齐填充(4) = 16字节 对象头(12字节) + 字段(12字节) = 24字节 → 实际 ≈ 56 字节
2. Retained Size 的临界特性
-
GC Root 排除规则:
若对象被多个 GC Root 引用,不计入 Retained Size
- 对象B 的 Retained Size = B.shallow
- 对象A 的 Retained Size = A.shallow + C.shallow(B 被 GC Root 直接引用,不计入)
3. Native Size 的高风险场景
对象类型 | Native Size 来源 | 内存回收策略 |
---|---|---|
Bitmap | 像素缓冲区 (pixel buffer) | recycle()(API < 23) /BitmapPool |
AudioTrack | PCM 音频数据缓冲区 | 手动调用release() |
ByteBuffer.allocateDirect() | 堆外内存分配 | 依赖System.gc()触发 Cleaner |
4. Depth 与设计缺陷的关系
-
安全阈值:≤ 7 层(微软认知心理学研究结论)
-
问题案例:
iniGC Root → App → MainActivity → Presenter → Adapter → ViewHolder → ImageLoader → Bitmap (Depth=7)
风险:嵌套过深导致维护困难,易引发内存泄漏
四、优化策略与工具实操
✅ 优化目标:降低 Retained Size / Native Size
问题类型 | 优化方案 | 工具验证方式 |
---|---|---|
高 Retained Size | 1. 用WeakReference替换 Context 引用 2. 及时移除监听器(onDestroy) | MAT 的Path to GC Roots→ 检查 Depth |
高 Native Size | 1. 使用inSampleSize压缩 Bitmap 2. JNI 代码配对释放 (DeleteLocalRef) | adb shell showmap 查看 Native 块 |
过大 Depth | 重构为扁平结构(例:用ViewModel替代多层 Presenter) | Android Studio 的 Memory Profiler 直接显示 Depth |
🔧 工具操作指南
-
Android Studio Memory Profiler
- 步骤:捕获堆转储 → 点击对象 → 查看 Depth/Shallow/Native Size
-
MAT 关键操作
-
Dominator Tree:按 Retained Size 排序 → 定位内存大户
-
OQL 查询:
sqlSELECT * FROM "com.example.User" WHERE @retainedHeapSize > 1024 * 1024
-
五、总结:核心优化思维

核心原则:
- 80/20法则:优化 Retained Size 占比前 5% 的对象效果显著
- Native 优先:Java 层的 OOM 可预警,Native 层 OOM 直接崩溃(signal 11 (SIGSEGV))
- 深度即风险:对象的 Depth 每增加 1,维护成本和泄漏风险翻倍