jmap (Java Memory Map) là công cụ chẩn đoán quan trọng của JDK, được sử dụng để **xem xét tình trạng sử dụng bộ nhớ của tiến trình Java, tạo bản ghi đống (Heap Dump), và phân tích phân bố đối tượng**. Công cụ này dựa vào cơ chế Attach của JVM và thường được dùng để xác định rò rỉ bộ nhớ hoặc các vấn đề OOM.
1. Ngữ pháp cơ bản
jmap [options] <pid>
jmap [options] <executable <core>
jmap [options] [server_id@]<remote server IP or hostname>
️ **Trong môi trường sản xuất, cách sử dụng phổ biến nhất là dạng đầu tiên: `jmap [options] <pid>`**
2. Giải thích chi tiết các tùy chọn chính (theo mục đích)
A. Xem tổng quan về bộ nhớ đống (An toàn, không STW)
`jmap -heap <pid>`
- Hiển thị cấu hình và trạng thái sử dụng hiện tại của đống JVM.
- **Không kích hoạt GC, không dừng (An toàn)**.
- Nội dung đầu ra bao gồm:
- Thuật toán GC (ví dụ G1, Parallel, CMS).
- Kích thước đống (Xmx/Xms).
- Dung lượng và mức sử dụng của từng thế hệ (Eden, Survivor, Old).
- Sử dụng Metaspace (JDK 8+).
Ví dụ đoạn đầu ra:
using thread-local object allocation.
Garbage Collector(s) detected: PS Scavenge, PS MarkSweep
Heap Configuration:
MinHeapFreeRatio = 0
MaxHeapFreeRatio = 100
MaxHeapSize = 2147483648 (2048.0MB)
NewSize = 1431306240 (1365.0MB)
MaxNewSize = 1431306240 (1365.0MB)
Heap Usage:
PS Young Generation
Eden Space:
capacity = 1398235136 (1333.5MB)
used = 1207959552 (1152.0MB)
...
**Mục đích**: Nhanh chóng xác nhận xem đống có sắp đầy hay không, thuật toán GC có phù hợp với kỳ vọng hay không.
B. Histogram đối tượng (Có thể chọn có kích hoạt Full GC hay không)
`jmap -histo[:live] <pid>`
- Thống kê số lượng thực thể và bộ nhớ chiếm dụng bởi các đối tượng trong đống.
- **`:live` là tùy chọn then chốt!**
| Tùy chọn | Hành động | Có kích hoạt Full GC? | Mục đích |
|---|---|---|---|
| -histo | Thống kê tất cả các đối tượng (bao gồm những đối tượng chờ thu dọn) | Không | Xem nhanh (nhưng có thể chứa rác) |
| -histo:live | Chỉ thống kê các đối tượng **có thể truy cập được** (đối tượng sống) | Có | **Phân tích chính xác rò rỉ bộ nhớ** |
Định dạng đầu ra:
num #instances #bytes class name
----------------------------------------------
1: 100000 16000000 java.util.HashMap$Node
2: 50000 4000000 com.example.CacheEntry
3: 10000 1200000 byte[]
️ **Cảnh báo**: `-histo:live` sẽ buộc thực thi **Full GC** (Stop-The-World), cần cẩn thận khi sử dụng trong môi trường sản xuất!
Gợi ý sử dụng (lấy 20 dòng đầu):
jmap -histo:live 12345 | head -20
C. Tạo tệp ghi đống (Heap Dump)
`jmap -dump:[live,]format=b,file=<filename> <pid>`
- Tạo bản ghi đống ở định dạng HPROF tiêu chuẩn, để phân tích bằng MAT, VisualVM, v.v.
- **`live` quyết định chỉ ghi lại các đối tượng tồn tại**.
| Tùy chọn | Giải thích |
|---|---|
| live | Chỉ ghi lại **các đối tượng có thể truy cập được** (khuyến nghị, tệp nhỏ hơn, phân tích chính xác hơn) |
| format=b | Định dạng nhị phân (định dạng duy nhất hỗ trợ) |
| file=xxx.hprof | Đường dẫn tệp đầu ra |
Lệnh khuyến nghị:
jmap -dump:live,format=b,file=/tmp/heap_$(date +%s).hprof 12345
️ **Tác động quan trọng**:
- Sẽ kích hoạt **Full GC** (vì phải đánh dấu các đối tượng tồn tại).
- Ứng dụng sẽ **hoàn toàn tạm dừng (STW)**, thời gian kéo dài = kích thước đống / tốc độ quét.
- 1GB đống ≈ vài giây đến vài chục giây tạm dừng, đống lớn (>10GB) có thể mất hàng phút!
**Khuyến nghị trong sản xuất**:
- Không sử dụng trừ khi cần thiết.
- Đảm bảo có đủ không gian ổ đĩa (tệp HPROF ≈ kích thước các đối tượng tồn tại trong đống).
- Xem xét sử dụng `-XX:+HeapDumpOnOutOfMemoryError` để tự động ghi lại.
D. Xem ánh xạ bộ nhớ (các đoạn bộ nhớ cấp thấp)
`jmap -permstat <pid>` (JDK 8 và thấp hơn)
- Hiển thị thông tin thống kê về các tải lớp trong PermGen.
- **Bị loại bỏ từ JDK 8+** (Metaspace thay thế PermGen).
`jmap -finalizerinfo <pid>`
- Hiển thị số lượng đối tượng đang chờ finalize trong hàng đợi finalization.
- Có thể dùng để xác định các vấn đề bộ nhớ do phương thức `finalize()` gây ra.
️ Một số tùy chọn đã bị loại bỏ trong JDK 11+.
3. Các tùy chọn bị loại bỏ hoặc không khuyến nghị
| Tùy chọn | Trạng thái | Giải thích |
|---|---|---|
| -F | Hỗ trợ hạn chế | Chế độ ép buộc (giống như jstack -F), sử dụng ptrace, thường thất bại trong container. |
| -d64 | Bị loại bỏ | JVM 64 bit đã bật mặc định. |
| -clstats | Bị loại bỏ | Thống kê tải lớp, chức năng bị `-histo` và JMX thay thế. |
**Không nên sử dụng `jmap -F`**: Trong container, Alpine, và các môi trường an ninh tăng cường gần như luôn thất bại.
4. Vấn đề phổ biến và hạn chế
1. Lỗi "Unable to open socket file"
- Nguyên nhân: Cơ chế Attach thất bại (/tmp không thể viết, quyền người dùng, ảnh Alpine, v.v.).
- Giải pháp:
- Sử dụng `jcmd <pid> GC.run_finalization` (xem tổng quan đống).
- Sử dụng `kill -3 <pid>` để lấy ngăn xếp luồng (nhưng không thay thế được heap dump).
- Đảm bảo `/tmp` trong container có thể viết.
2. Tạo tệp dump quá chậm/hang
- Nguyên nhân: Đống quá lớn, hoặc IO đĩa chậm.
- Gợi ý:
- Dùng `jmap -histo:live` để nhanh chóng xác định các đối tượng lớn.
- Xem xét sử dụng **Async-Profiler + JFR** (JDK 11+) làm mẫu chi phí thấp.
3. Không thể phân tích tệp dump?
- Đảm bảo sử dụng cùng hoặc phiên bản JDK cao hơn để phân tích.
- Công cụ khuyến nghị:
- **Eclipse MAT** (Memory Analyzer): Miễn phí, mạnh mẽ.
- **VisualVM**: Phân tích cơ bản tích hợp sẵn.
- **JProfiler / YourKit**: Thương mại, trải nghiệm tương tác tốt.
5. Khuyến nghị thực hành tốt nhất
| Trường hợp | Lệnh khuyến nghị | Lưu ý |
|---|---|---|
| Xem nhanh việc sử dụng đống | `jmap -heap <pid>` | An toàn, không dừng |
| Nghi ngờ rò rỉ bộ nhớ | `jmap -histo:live <pid> | head -30` | Sẽ Full GC, tránh giờ cao điểm |
| Cần phân tích sâu | `jmap -dump:live,format=b,file=heap.hprof <pid>` | Thông báo trước, đảm bảo không gian đĩa |
| Attach trong container thất bại | Sử dụng `jcmd <pid> VM.flags` + nhật ký ứng dụng | Hoặc thêm `-XX:+HeapDumpOnOutOfMemoryError` khi khởi động |
6. So sánh với các công cụ khác
| Chức năng | `jmap` | `jcmd` | Giải thích |
|---|---|---|---|
| Tổng quan đống | `jmap -heap` | `jcmd <pid> GC.run_finalization` | `jcmd` hiện đại hơn |
| Histogram đối tượng | `jmap -histo` | Không hỗ trợ | `jmap` vẫn là lựa chọn duy nhất |
| Heap Dump | `jmap -dump` | `jcmd <pid> GC.run_finalization` (không thể dump) | `jmap` không thể thay thế |
| Ngăn xếp luồng | Không hỗ trợ | `jcmd <pid> Thread.print` | Sử dụng `jcmd` hoặc `jstack` |
**Kết luận**: `jmap` vẫn là công cụ cốt lõi không thể thay thế trong **thống kê đối tượng** và **gắn kết đống**.