gc的老年代內存高居不下,導致最後full gc的發生,我們需要通過分析gc dump文件來解決biggest objects過多的問題
生成dump文件
在keycloak容器中安裝輕量級工具
microdnf install -y wget
microdnf install -y procps-ng #包含ps命令
ps -aux # 找到keycloak的pid,默認是712
wget https://github.com/apangin/jattach/releases/download/v2.0/jattach
chmod +x jattach
# 使用jattach執行內存命令
./jattach 712 dumpheap /tmp/heap.hprof
下載生成的hprof文件,使用jprofile分析它
我們可以從biggest objects裏面,查看哪些大對象沒有被釋放,或者頻繁被創建
HProf文件和Dump文件
1. Dump 文件(轉儲文件)
廣義概念:程序在某個時間點的狀態快照。
Java 中的 Dump 文件主要類型:
| 類型 | 描述 | 常用擴展名 |
|---|---|---|
| Heap Dump | JVM 堆內存快照,包含所有對象實例 | .hprof, .bin, .dmp |
| Thread Dump | 線程狀態快照,包含調用棧、鎖信息 | .tdump, .txt |
| Core Dump | 進程崩潰時的完整內存鏡像 | .core, .dmp |
2. HProf 文件
具體格式:Java 專用的堆轉儲文件格式。
特點:
- 標準格式:Oracle/OpenJDK 的標準堆轉儲格式
- 二進制格式:包含完整的堆內存數據結構
- 包含內容:
- 所有對象實例及其數據
- 對象之間的引用關係
- 類元數據(Class metadata)
- GC Root 引用鏈
- 靜態變量
3. 兩者關係
Dump文件(概念)
├── Heap Dump(堆轉儲)
│ ├── HProf 格式(標準格式)
│ ├── JProfiler 格式(.snapshot)
│ └── YourKit 格式(.snapshot)
├── Thread Dump(線程轉儲)
└── Core Dump(核心轉儲)
4. 生成方式對比
Heap Dump (HProf) 生成:
# 1. 使用 jmap 命令(最常用)
jmap -dump:format=b,file=heap.hprof <pid>
# 2. 使用 jcmd 命令
jcmd <pid> GC.heap_dump /path/to/heap.hprof
# 3. JVM 啓動參數(內存溢出時自動生成)
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/path/to/dumps
# 4. 使用 VisualVM、JConsole 等工具
Thread Dump 生成:
# 1. 使用 jstack 命令
jstack <pid> > thread.dump
# 2. 使用 jcmd
jcmd <pid> Thread.print > thread.dump
# 3. 發送信號
kill -3 <pid> # 輸出到標準輸出
5. 文件結構解析
HProf 文件內容結構:
文件頭
↓
記錄類型1 (如:UTF8字符串表)
↓
記錄類型2 (如:類加載信息)
↓
記錄類型3 (如:實例數據)
↓
記錄類型4 (如:對象引用)
↓
...
關鍵數據段:
- 字符串表 - 所有字符串常量的映射
- 類信息 - 加載的類、父類、接口
- 實例數據 - 每個對象實例的字段值
- 對象數組 - 數組類型和元素
- 根引用 - GC Roots 集合
- 原始數據 - 堆棧幀、本地變量等
6. 分析方法工具
分析 HProf 文件的工具:
| 工具 | 特點 | 適用場景 |
|---|---|---|
| Eclipse MAT | 功能強大,內存分析專業 | 深入內存泄漏分析 |
| VisualVM | JDK 自帶,輕量級 | 快速查看概覽 |
| JProfiler | 商業工具,可視化好 | 性能分析和監控 |
| YourKit | 商業工具,CPU/Memory分析 | 生產環境分析 |
| Java Mission Control | Oracle 官方工具 | Flight Recorder 數據分析 |
示例:使用 MAT 分析步驟:
# 1. 生成轉儲文件
jmap -dump:live,format=b,file=keycloak.hprof <pid>
# 2. 用 MAT 打開,常見視圖:
- Histogram(直方圖): 按類統計對象數量
- Dominator Tree(支配樹): 找出內存佔用最大的對象
- Leak Suspects(泄漏嫌疑): 自動分析疑似泄漏
- Path to GC Roots(到GC根的路徑): 查看對象引用鏈
7. 實際案例分析
你遇到的 QueryPlanCache 問題分析:
// HProf 分析中可能看到:
org.hibernate.engine.query.spi.QueryPlanCache
├── queryPlanCache (ConcurrentHashMap)
│ ├── key: "from User where realmId = ? order by createdTimestamp"
│ ├── value: QueryPlan對象 (包含解析後的SQL、參數映射等)
│ └── size: 可能數千個條目
└── parameterMetadataCache
└── 存儲參數元數據
文件大小參考:
- 小型應用:幾百 MB
- 中型應用(如 Keycloak):1-4 GB
- 大型應用:10 GB+
8. 使用技巧
減小 HProf 文件大小:
# 1. 只轉儲存活對象(推薦)
jmap -dump:live,format=b,file=heap.hprof <pid>
# 2. 過濾不需要的內容
# 使用MAT的OQL或過濾器功能
# 3. 壓縮存儲
jmap -dump:live,format=b,file=heap.hprof.gz <pid>
自動化分析腳本:
#!/bin/bash
PID=$(jps | grep keycloak | awk '{print $1}')
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
# 生成堆轉儲
jmap -dump:live,format=b,file=/dump/keycloak_${TIMESTAMP}.hprof $PID
# 生成線程轉儲
jstack $PID > /dump/thread_${TIMESTAMP}.dump
# 基本分析
echo "=== 堆轉儲生成完成 ==="
echo "文件: /dump/keycloak_${TIMESTAMP}.hprof"
echo "大小: $(du -h /dump/keycloak_${TIMESTAMP}.hprof | cut -f1)"
9. 注意事項
- 性能影響:生成堆轉儲會暫停 JVM(Stop-The-World),生產環境謹慎使用
- 磁盤空間:確保有足夠的磁盤空間(通常是堆內存的1-1.5倍)
- 安全考慮:轉儲文件可能包含敏感數據(如密碼、密鑰)
- 版本兼容性:HProf 文件與 JVM 版本相關,高版本工具可分析低版本文件
10. 最佳實踐
- 定期收集基線:在應用穩定時收集作為對比基準
- 配置自動轉儲:配置 OOM 時自動生成轉儲
- 保留多個時間點:對於內存泄漏,保留多個時間點的轉儲對比
- 配合日誌分析:轉儲文件結合 GC 日誌、應用日誌分析
總結:HProf 是 Java 堆轉儲的標準格式文件,屬於 Dump 文件的一種。通過分析這種文件,可以定位內存泄漏、優化內存使用,就像你發現的 QueryPlanCache 問題一樣。