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 → 對象) | 設計復雜度的直接體現 |
二、真實案例分析
📁 場景:用戶詳情頁展示頭像
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
:name(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 層(微軟認知心理學研究結論)
-
問題案例:
GC 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 壓縮 Bitmap2. JNI 代碼配對釋放 ( DeleteLocalRef ) | adb shell showmap <pid> 查看 Native 塊 |
過大 Depth | 重構為扁平結構(例:用ViewModel 替代多層 Presenter) | Android Studio 的 Memory Profiler 直接顯示 Depth |
🔧 工具操作指南
-
Android Studio Memory Profiler
- 步驟:捕獲堆轉儲 → 點擊對象 → 查看 Depth/Shallow/Native Size
-
MAT 關鍵操作
-
Dominator Tree
:按 Retained Size 排序 → 定位內存大戶 -
OQL 查詢
:SELECT * FROM "com.example.User" WHERE @retainedHeapSize > 1024 * 1024
-
五、總結:核心優化思維
核心原則:
- 80/20法則:優化 Retained Size 占比前 5% 的對象效果顯著
- Native 優先:Java 層的 OOM 可預警,Native 層 OOM 直接崩潰(
signal 11 (SIGSEGV)
) - 深度即風險:對象的 Depth 每增加 1,維護成本和泄漏風險翻倍