https://juejin.cn/post/7361234872780898316
以下來自本人拉的一個關於 Java 技術的討論羣。關注公衆號:hashcon,私信進羣拉你
1. 爲什麼不建議打開 HeapDumpOnOutOfMemoryError?
1.1. 打開 HeapDumpOnOutOfMemoryError,哪些 OutOfMemoryError 會觸發 HeapDumpOnOutOfMemoryError?
打開 HeapDumpOnOutOfMemoryError 之後,不是所有的 OutOfMemoryError 都會觸發 HeapDumpOnOutOfMemoryError,不同的 OutOfMemoryError 包括(如果對這些異常拋出的原理詳情感興趣,請參考:zhuanlan.zhihu.com/p/265039643 ):
OutOfMemoryError: Java heap space
和OutOfMemoryError: GC overhead limit exceeded
:這兩個都是 Java 對象堆內存不夠了,一個是分配的時候發現剩餘空間不足,一個是到達某一界限。這兩個都會觸發HeapDumpOnOutOfMemoryError
OutOfMemoryError: unable to create native thread
:無法創建新的平臺線程,這個不會觸發HeapDumpOnOutOfMemoryError
OutOfMemoryError: Requested array size exceeds VM limit
:當申請的數組大小超過堆內存限制,就會拋出這個異常。這個會觸發HeapDumpOnOutOfMemoryError
OutOfMemoryError: Compressed class space
和OutOfMemoryError: Metaspace
:這兩個都和元空間相關(底層原理說明參考:juejin.cn/post/722587… ),這兩個都會觸發HeapDumpOnOutOfMemoryError
OutOfMemoryError: Cannot reserve xxx bytes of direct buffer memory (allocated: xxx, limit: xxx)
:在 DirectByteBuffer 中,首先向 Bits 類申請額度,Bits 類有一個全局的 totalCapacity 變量,記錄着全部 DirectByteBuffer 的總大小,每次申請,都先看看是否超限,可用-XX:MaxDirectMemorySize
限制。這個不會觸發HeapDumpOnOutOfMemoryError
OutOfMemoryError: map failed
:這個是 File MMAP(文件映射內存)時,如果系統內存不足,就會拋出這個異常。這個不會觸發HeapDumpOnOutOfMemoryError
還有一些其他的:
- Shenandoah 分配區域位圖,內存的時候,觸發的
OutOfMemoryError
,這個會觸發HeapDumpOnOutOfMemoryError
。 OutOfMemoryError: Native heap allocation failed
,這個 Message 可能不同操作系統不一樣,但是一般都有 native heap。這個就和 Java 對象堆一般沒關係,而是其他塊內存無法申請導致的,這些不會觸發HeapDumpOnOutOfMemoryError
1.2. 爲什麼不打開 HeapDumpOnOutOfMemoryError
?
HeapDumpOnOutOfMemoryError
的原理:
- 進入安全點,所有應用線程暫停,針對 HeapDumpOnOutOfMemoryError,單線程(如果是 jcmd jmap 可以多線程)dump 堆爲線程個數個文件。退出安全點。
- 將上面的多個文件,合併爲一個,壓縮。
這裏的瓶頸主要在於第一步寫入,並且,主要瓶頸再磁盤 IO,我們來看下現在雲服務的磁盤 IO 標準:
- AWS EFS(普通存儲):docs.aws.amazon.com/efs/latest/…
- AWS EBS(對標 SSD):docs.aws.amazon.com/ebs/latest/…
對於一個 4G 大小的堆內存,如果是 EFS,對標的應該是 100G 以內的磁盤,寫入最少也需要大概 4 * 1024 / 300 = 13.65
秒(注意,這個是峯值性能),如果當時峯值性能被用完了,那麼需要:4 * 1024 / 15 = 273
秒。如果用 EBS,那麼也需要 4 * 1024 / 1000 = 4
秒。注意,這個計算的時間,是應用線程個完全處於安全點(即 Stop-the-world)的時間,還沒有還是沒考慮一個機器上部署多個容器實例的情況,考慮成本我們也不能堆每個微服務都使用 AWS EBS 這種(對標 SSD)。
所以,建議還是不要打開 HeapDumpOnOutOfMemoryError
2. 不使用 HeapDumpOnOutOfMemoryError 用什麼?
2.1. 定位內存泄漏問題靠 JFR
我這邊定位 OutOfMemoryError 一般通過 JFR 的 Object Allocation Sample 以及 Old Object Sample 裏面的對象去定位,只有這些都定位不出來,纔會考慮 Heap Dump。
2.2. 爲什麼拋出 OutOfMemoryError 的微服務最好下線重啓?
因爲包括 JDK 的源碼在內,都沒有在每一個分配內存的代碼的地方考慮會出現 OutOfMemoryError,這樣會導致代碼狀態不一致,例如 hashmap 的 rehash,如果裏面某行拋出 OutOfMemoryError,前面更新的狀態就不對了。還有其他很多庫,就不用說了,都很少有 catch Throwable 的,大部分是 catch Exception 的。並且,在每一個分配內存的代碼的地方考慮會出現 OutOfMemoryError 也是不現實的,所以爲了防止 OutOfMemoryError 帶來意想不到的一致性問題,還是下線重啓比較好。
2.3. 如何實現拋出 OutOfMemoryError 的微服務下線重啓?
一般通過 -XX:OnOutOfMemoryError="/path/to/script.sh"
指定腳本,腳本執行:
- 微服務的下線
- 微服務的重啓
針對 spring boot,可以考慮開啓允許本地訪問 /actuator/shutdown
來關閉微服務(有羣友反應拋出 OutOfMemoryError 的時候調用這個會卡死,這是因爲 1.2 說的原因,你可能開啓了 HeapDumpOnOutOfMemoryError 導致的️),k8s 會自動拉起一個新的。