當 JVM 在運行時分配的記憶體不足時會發生什麼?
1. 概述
為 JVM 應用程式定義適當的堆疊大小是至關重要的一步。這可能有助於我們的應用程式進行記憶體分配和處理高負載。然而,低效率的堆大小(太小或太大)都可能會影響其性能。
在本教程中,我們將了解OutOfMemoryErrors
的原因及其與堆大小的關聯。此外,我們將檢查我們可以對此錯誤採取哪些措施以及如何調查根本原因。
2. Xmx
和Xms
我們可以使用兩個專用的 JVM 標誌來控制堆疊分配。第一個-Xms,
幫助我們設定堆的初始大小和最小大小。另一項-Xmx,
設定最大堆大小。其他幾個標誌可以幫助更動態地分配,但它們總體上執行類似的工作。
讓我們檢查一下這些標誌如何相互關聯以及OutOfMemoryError
以及它們如何導致或防止它。首先,讓我們澄清一個顯而易見的事情: -Xms
不能大於-Xmx
。如果我們不遵循這個規則,JVM 將在啟動時使應用程式失敗:
$ java -Xms6g -Xmx4g
Error occurred during initialization of VM
Initial heap size set to a larger value than the maximum heap size
現在,讓我們考慮一個更有趣的場景。如果我們嘗試分配比實體 RAM 更多的內存,會發生什麼情況?這取決於JVM版本、架構、作業系統等。有些作業系統,如Linux,允許過度使用,並直接配置過度使用。其他人允許過度承諾,但根據他們的內部啟發法這樣做:
同時,即使我們有足夠的實體內存,由於碎片過多,我們可能無法啟動應用程式。假設我們有 4 GB 實體 RAM,其中可用空間約為 3 GB。分配 2 GB 的堆疊可能是不可能的,因為 RAM 中沒有此大小的連續段:
某些版本的 JVM,尤其是較新的版本,沒有這樣的要求。但是,它可能會影響運行時的物件分配。
3. 運行時出現OutOfMemoryError
假設我們啟動應用程式時沒有任何問題。由於多種原因,我們仍然有機會遇到OutOfMemoryError
。
3.1.耗盡堆疊空間
記憶體消耗的增加可能是由自然原因引起的,例如,節日期間我們的網上商店的活動增加。此外,它也可能是由於記憶體洩漏而發生的。我們一般可以透過檢查GC活動來區分這兩種情況。同時,可能存在更複雜的情況,例如終結延遲或緩慢的垃圾收集線程。
3.2.過度投入
由於交換空間的原因,可能會出現過度使用的情況。我們可以透過將一些資料轉儲到光碟上來擴展 RAM。這可能會導致速度顯著減慢,但同時應用程式不會失敗。但是,這可能不是解決此問題的最佳或理想的解決方案。此外,交換記憶體的極端情況是系統抖動,這可能會凍結系統。
我們可以將過度承諾視為部分準備金銀行。 RAM 沒有向應用程式承諾的所有所需記憶體。但是,當應用程式開始佔用它們所承諾的記憶體時,作業系統可能會開始殺死不重要的應用程序,以確保其餘應用程式不會失敗:
3.3.收縮堆
這個問題與過度使用有關,但罪魁禍首是試圖最小化佔用空間的垃圾收集啟發式方法。即使應用程式在生命週期的某個時刻成功聲明了最大堆大小,也不代表下次就能獲得它。
垃圾收集器可能會從堆中返回一些未使用的內存,作業系統可以將其重新用於不同的目的。同時,當應用程式嘗試取回它時,RAM 可能已經分配給其他應用程式。
我們可以透過將-Xms
和-Xmx
設定為相同的值來控制它。這樣,我們可以獲得更可預測的記憶體消耗並避免堆收縮。然而,這可能會影響資源利用率;因此,應謹慎使用。此外,不同的 JVM 版本和垃圾收集器在堆疊收縮方面的行為可能有所不同。
4. OutOfMemoryError
並非所有OutOfMemoryErrors
都是相同的。我們有很多口味,了解它們之間的差異可能有助於我們找出根本原因。我們將只考慮那些與前面描述的場景相關的內容。
4.1. Java堆疊空間
我們可以在日誌中看到以下訊息: java.lang.OutOfMemoryError: Java heap space.
這清楚地描述了問題:堆中沒有空間。造成這種情況的原因可能是記憶體洩漏或應用程式負載增加。創建率和刪除率的顯著差異也可能導致此問題。
4.2. GC 開銷超出限制
有時,應用程式可能會失敗並顯示: java.lang.OutOfMemoryError: GC Overhead limit exceeded.
當應用程式將 98% 的時間用於垃圾收集時,就會發生這種情況,這意味著吞吐量僅為 2%。這種情況描述了垃圾收集顛簸:應用程式處於活動狀態,但沒有做有用的工作。
4.3.交換空間不足
另一種類型的OutOfMemoryError
是: java.lang.OutOfMemoryError: request size bytes for reason. Out of swap space?
這通常表示作業系統端過度使用。在這種情況下,堆中我們仍然有容量,但作業系統無法為我們提供更多記憶體。
5.根本原因
當我們遇到OutOfMemoryError
時,我們在應用程式中幾乎無能為力。儘管不建議捕獲錯誤,但在某些情況下,出於清理或記錄目的可能是合理的。有時,我們可以看到處理try-catch
區塊來處理條件邏輯的程式碼。這是一種相當昂貴且不可靠的駭客行為,在大多數情況下應該避免。
5.1.垃圾收集日誌
雖然OutOfMemoryError
提供了有關問題的信息,但不足以進行更深入的分析。最簡單的方法是使用垃圾收集日誌,它不會產生太多開銷,同時提供有關正在運行的應用程式的基本資訊。
5.2.堆轉儲
堆轉儲是瀏覽應用程式的另一種方式。雖然我們可以定期捕獲它,但這可能會影響應用程式的效能。使用它的最便宜的方法是在OutOfMemoryError
上自動執行堆轉儲。幸運的是,JVM 允許我們使用-XX:+HeapDumpOnOutOfMemoryError
進行設定。此外,我們可以使用-XX:HeapDumpPath
標誌來設定堆轉儲的路徑。
5.3.在OutOfMemoryError
上執行腳本
為了增強我們對OutOfMemoryError
體驗,我們可以使用-XX:OnOutOfMemoryError
並將其定向到應用程式記憶體不足時將運行的腳本。這可用於實作通知系統、將堆轉儲傳送到某些分析工具或重新啟動應用程式。
六,結論
在本文中,我們討論了OutOfMemoryError
,它指示我們的應用程式外部的問題,就像其他錯誤一樣。處理這些錯誤可能會產生更多問題,並使我們的應用程式不一致。處理這種情況的最好方法是從一開始就防止它發生。
仔細的記憶體管理和 JVM 配置可以幫助我們做到這一點。此外,分析垃圾收集日誌可以幫助我們找出問題的原因。在不了解根本問題的情況下向應用程式分配更多記憶體或使用其他技術來確保其保持活動並不是正確的解決方案,並且可能會導致更多問題。