[轉帖]2024-4-23 羣討論:Java堆空間OutOfMemoryError該怎麼辦

https://juejin.cn/post/7361234872780898316

 

 

以下來自本人拉的一個關於 Java 技術的討論羣。關注公衆號:hashcon,私信進羣拉你

1. 爲什麼不建議打開 HeapDumpOnOutOfMemoryError?

1.1. 打開 HeapDumpOnOutOfMemoryError,哪些 OutOfMemoryError 會觸發 HeapDumpOnOutOfMemoryError?

打開 HeapDumpOnOutOfMemoryError 之後,不是所有的 OutOfMemoryError 都會觸發 HeapDumpOnOutOfMemoryError,不同的 OutOfMemoryError 包括(如果對這些異常拋出的原理詳情感興趣,請參考:zhuanlan.zhihu.com/p/265039643 ):

  1. OutOfMemoryError: Java heap space 和 OutOfMemoryError: GC overhead limit exceeded:這兩個都是 Java 對象堆內存不夠了,一個是分配的時候發現剩餘空間不足,一個是到達某一界限。這兩個都會觸發 HeapDumpOnOutOfMemoryError
  2. OutOfMemoryError: unable to create native thread:無法創建新的平臺線程,這個不會觸發 HeapDumpOnOutOfMemoryError
  3. OutOfMemoryError: Requested array size exceeds VM limit:當申請的數組大小超過堆內存限制,就會拋出這個異常。這個會觸發 HeapDumpOnOutOfMemoryError
  4. OutOfMemoryError: Compressed class space 和 OutOfMemoryError: Metaspace:這兩個都和元空間相關(底層原理說明參考:juejin.cn/post/722587… ),這兩個都會觸發 HeapDumpOnOutOfMemoryError
  5. OutOfMemoryError: Cannot reserve xxx bytes of direct buffer memory (allocated: xxx, limit: xxx):在 DirectByteBuffer 中,首先向 Bits 類申請額度,Bits 類有一個全局的 totalCapacity 變量,記錄着全部 DirectByteBuffer 的總大小,每次申請,都先看看是否超限,可用 -XX:MaxDirectMemorySize 限制。這個不會觸發 HeapDumpOnOutOfMemoryError
  6. OutOfMemoryError: map failed:這個是 File MMAP(文件映射內存)時,如果系統內存不足,就會拋出這個異常。這個不會觸發 HeapDumpOnOutOfMemoryError

還有一些其他的:

  1. Shenandoah 分配區域位圖,內存的時候,觸發的 OutOfMemoryError,這個會觸發 HeapDumpOnOutOfMemoryError
  2. OutOfMemoryError: Native heap allocation failed,這個 Message 可能不同操作系統不一樣,但是一般都有 native heap。這個就和 Java 對象堆一般沒關係,而是其他塊內存無法申請導致的,這些不會觸發HeapDumpOnOutOfMemoryError

1.2. 爲什麼不打開 HeapDumpOnOutOfMemoryError

HeapDumpOnOutOfMemoryError 的原理:

  1. 進入安全點,所有應用線程暫停,針對 HeapDumpOnOutOfMemoryError,單線程(如果是 jcmd jmap 可以多線程)dump 堆爲線程個數個文件。退出安全點。
  2. 將上面的多個文件,合併爲一個,壓縮。

這裏的瓶頸主要在於第一步寫入,並且,主要瓶頸再磁盤 IO,我們來看下現在雲服務的磁盤 IO 標準:

  1. AWS EFS(普通存儲):docs.aws.amazon.com/efs/latest/…
  2. 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"指定腳本,腳本執行:

  1. 微服務的下線
  2. 微服務的重啓

針對 spring boot,可以考慮開啓允許本地訪問 /actuator/shutdown 來關閉微服務(有羣友反應拋出 OutOfMemoryError 的時候調用這個會卡死,這是因爲 1.2 說的原因,你可能開啓了 HeapDumpOnOutOfMemoryError 導致的️),k8s 會自動拉起一個新的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章