共用方式為


Java 記憶體管理

注意

基本標準和企業方案將從 2025 年 3 月中旬開始淘汰,並停用 3 年。 建議您轉換至 Azure Container Apps。 如需詳細資訊,請參閱 Azure Spring Apps 淘汰公告

標準 耗用量和專用 方案將從 2024 年 9 月 30 日起淘汰,並在六個月後完成關閉。 建議您轉換至 Azure Container Apps。 如需詳細資訊,請參閱 將 Azure Spring Apps 標準取用和專用方案遷移至 Azure Container Apps

本文適用於:✅ 基本/標準❎企業

本文說明與 Java 記憶體管理相關的各種概念,以協助您瞭解 Azure Spring 應用程式所託管 Java 應用程式的行為。

Java 記憶體模型

Java 應用程式的記憶體具有多個部分,而且有不同方式可以分割。 本文說明由 Java 記憶體分割成的堆積記憶體、非堆積記憶體和直接記憶體。

堆積記憶體

堆積記憶體可儲存所有類別的執行個體和陣列。 每個 Java 虛擬機器 (JVM) 只有一個堆積區,由不同執行緒共用。

Spring Boot Actuator 可觀察堆積記憶體的值, 並將堆積值記錄於 jvm.memory.used/committed/max。 如需詳細資訊,請參閱記憶體問題疑難排解工具jvm.memory.used/committed/max 區段。

堆積記憶體分為新世代舊世代。 以下清單會說明這些詞彙及相關字詞。

  • 新世代 (young generation):所有新物件都會放到新世代,在此過時。

    • Eden 空間 (Eden space):新物件會放到 Eden 空間。
    • 存留者空間 (survivor space):如果物件在記憶體回收週期後存留下來,就會從 Eden 空間移到存留者空間。 存留者空間可分成 s1 和 s2 等兩個部分。
  • 舊世代 (old generation):也稱為續存空間。 長時間保留於存留者空間中的物件會移到舊世代。

在 Java 8 之前,還有另一個稱為永久世代 (permanent generation) 的區段也是堆積記憶體的一部分。 從 Java 8 開始,永久世代由非堆積記憶體中的中繼空間取代。

非堆積記憶體

非堆積記憶體可分為以下部分:

  • 從 Java 8 開始,永久世代 (或稱為 permGen) 便由非堆積記憶體取代。 Spring Boot Actuator 會觀察此區段,並記錄於 jvm.memory.used/committed/max。 換句話說,jvm.memory.used/committed/max 是堆積記憶體和非堆積記憶體前身 permGen 的總和。 先前的永久世代是由以下部分組成:

    • 中繼空間會儲存類別載入器所載入的類別定義。
    • 壓縮類別空間主要適用於壓縮類別指標。
    • 程式碼快取會儲存 JIT 編譯的原生程式碼。
  • 至於其他記憶體 (例如執行緒堆疊),則不在 Spring Boot Actuator 的觀察範圍內。

直接記憶體

直接記憶體是由 java.nio.DirectByteBuffer 配置的原生記憶體,用於 nio 和 gzip 等協力廠商程式庫。

Spring Boot Actuator 不會觀察直接記憶體的值。

下圖摘要說明上一節所述的 Java 記憶體模型。

顯示 Java 記憶體模型的圖表。

Java 記憶體回收

Java 記憶體回收 (GC) 有三個相關詞彙,分別是「次要 GC」、「主要 GC」和「完整 GC」。 JVM 規格中並未明確定義這些詞彙。 在這裡,我們將「主要 GC」和「完整 GC」視為相同概念。

次要 GC 會在 Eden 空間耗盡時執行。 此作業會移除新世代中的所有死亡物件,並將仍在使用的物件從 Eden 空間移到存留者空間的 s1,或從 s1 移到 s2。

完整 GC 或主要 GC 會在整個堆積中執行記憶體回收作業。 完整 GC 也可以回收中繼空間和直接記憶體等部分,這些部分只能由完整 GC 清除。

堆積大小上限會影響次要 GC 和完整 GC 的執行頻率。 中繼空間和直接記憶體的大小上限會影響完整 GC。

若您將堆積的大小上限設為較低的值,記憶體回收作業會更頻繁地執行,這會導致應用程式變慢一些,但較能限制記憶體的使用量。 若您將堆積的大小上限設為較高的值,記憶體回收作業的執行頻率較低,可能造成記憶體不足 (OOM) 的風險。 如需詳細資訊,請參閱記憶體不足造成應用程式重新啟動的問題記憶體不足問題類型一節。

中繼空間和直接記憶體只能由完整 GC 回收。 一旦中繼空間或直接記憶體耗盡,就會執行完整 GC。

Java 記憶體設定

下列各節說明 Java 記憶體設定的重要面向。

Java 容器化

應用程式會在容器環境中的 Azure Spring 應用程式內執行。 如需詳細資訊,請參閱將 Java 應用程式容器化

重要 JVM 選項

您可以使用 JVM 選項來設定記憶體各個部分的大小上限。 您可以使用 Azure CLI 命令或透過 Azure 入口網站設定 JVM 選項。 如需詳細資訊,請參閱記憶體問題疑難排解工具修改設定以修正問題一節。

以下清單說明 JVM 選項:

  • 堆積大小設定

    • -Xms 以絕對值設定初始堆積大小。
    • -Xmx 以絕對值設定堆積大小上限。
    • -XX:InitialRAMPercentage 以堆積大小/應用程式記憶體大小的百分比設定初始堆積大小。
    • -XX:MaxRAMPercentage 以堆積大小/應用程式記憶體大小的百分比設定堆積大小上限。
  • 直接記憶體大小設定

    • -XX:MaxDirectMemorySize 以絕對值設定直接記憶體大小上限。 如需詳細資訊,請參閱 Oracle 文件的 MaxDirectMemorySize
  • 中繼空間大小設定

    • -XX:MaxMetaspaceSize 以絕對值設定中繼空間大小上限。

預設記憶體大小上限

以下各節說明如何設定預設的記憶體大小上限。

預設堆積大小上限

Azure Spring 應用程式會將 Java 應用程式的預設堆積記憶體大小上限設定在應用程式記憶體的 50% 至 80% 左右。 確切來說,Azure Spring 應用程式採用以下設定:

  • 如果應用程式記憶體不到 1 GB,預設的堆積大小上限會是應用程式記憶體的 50%。
  • 如果應用程式記憶體介於 1 GB (含) 到 2 GB 之間,預設的堆積大小上限會是應用程式記憶體的 60%。
  • 如果應用程式記憶體介於 2 GB (含) 到 3 GB 之間,預設的堆積大小上限會是應用程式記憶體的 70%。
  • 如果應用程式記憶體超過 3 GB (含),預設的堆積大小上限會是應用程式記憶體的 80%。

預設的直接記憶體大小上限

如果未使用 JVM 選項設定直接記憶體的大小上限,JVM 會自動將直接記憶體大小上限設為 Runtime.getRuntime.maxMemory() 傳回的值。 該值大約等於堆積記憶體的大小上限。 如需詳細資訊,請參閱 JDK 8 VM.java 檔案

記憶體使用量配置

堆積大小會受輸送量影響。 基本上,您可以在設定時保留堆積大小的預設上限,這能為其他部分保留合理的記憶體。

中繼空間大小取決於程式碼的複雜程度,例如類別數。

直接記憶體大小取決於您的輸送量,以及使用 nio 和 gzip 等協力廠商程式庫的情形。

以下清單提供 2 GB 應用程式的常見記憶體配置範例, 您可以參考這份清單來完成記憶體大小設定。

  • 記憶體總容量 (2048M)
  • 堆積記憶體:Xmx 為 1433.6M (記憶體總容量的 70%)。 每日記憶體使用量的參考值為 1200M。
    • 新世代
      • 存留者空間 (S0、S1)
      • Eden 空間
    • 舊世代
  • 非堆積記憶體
    • 觀察部分 (由 Spring Boot Actuator 觀察)
      • 中繼空間:每日使用量參考值為 50M 至 256M
      • 程式碼快取
      • 壓縮的類別空間
    • 非觀察部分 (不在 Spring Boot Actuator 的觀察範圍):每日使用量參考值為 150M 至 250M。
      • 執行緒堆疊
      • GC、內部符號和其他
  • 直接記憶體:每日使用量參考值為 10M 至 200M。

以上資訊彙整為下圖, 灰色數字是記憶體每日使用量的參考值。

2 GB 應用程式的常見記憶體配置圖。

整體而言,設定記憶體的大小上限時,您應考量記憶體中各部分的使用量,而且所有大小上限的總和不應超過可供使用的記憶體總容量。

Java OOM

OOM 意指應用程式的記憶體不足, 可分為兩種不同的概念:容器 OOM 和 JVM OOM。 如需詳細資訊,請參閱記憶體不足造成應用程式重新啟動的問題

另請參閱