博客 / 詳情

返回

jvm~分析gc老年代內存過高的原因

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 (如:對象引用)
↓
...

關鍵數據段

  1. 字符串表 - 所有字符串常量的映射
  2. 類信息 - 加載的類、父類、接口
  3. 實例數據 - 每個對象實例的字段值
  4. 對象數組 - 數組類型和元素
  5. 根引用 - GC Roots 集合
  6. 原始數據 - 堆棧幀、本地變量等

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. 注意事項

  1. 性能影響:生成堆轉儲會暫停 JVM(Stop-The-World),生產環境謹慎使用
  2. 磁盤空間:確保有足夠的磁盤空間(通常是堆內存的1-1.5倍)
  3. 安全考慮:轉儲文件可能包含敏感數據(如密碼、密鑰)
  4. 版本兼容性:HProf 文件與 JVM 版本相關,高版本工具可分析低版本文件

10. 最佳實踐

  1. 定期收集基線:在應用穩定時收集作為對比基準
  2. 配置自動轉儲:配置 OOM 時自動生成轉儲
  3. 保留多個時間點:對於內存泄漏,保留多個時間點的轉儲對比
  4. 配合日誌分析:轉儲文件結合 GC 日誌、應用日誌分析

總結:HProf 是 Java 堆轉儲的標準格式文件,屬於 Dump 文件的一種。通過分析這種文件,可以定位內存泄漏、優化內存使用,就像你發現的 QueryPlanCache 問題一樣。

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.