Java 内存管理

注意

基本、标准和企业计划将从 2025 年 3 月中旬开始弃用,停用期为 3 年。 建议转换到 Azure 容器应用。 有关详细信息,请参阅 Azure Spring Apps 停用公告

标准消耗和专用计划将于 2024 年 9 月 30 日开始弃用,并在六个月后完全关闭。 建议转换到 Azure 容器应用。

本文介绍与 Java 内存管理相关的各种概念,以帮助你了解托管在 Azure Spring Apps 中的 Java 应用程序的行为。

Java 内存模型

Java 应用程序的内存有多个部分,并且有不同的方法来划分这些部分。 本文将 Java 内存分为堆内存、非堆内存和直接内存。

堆内存

堆内存存储所有类实例和数组。 每个 Java 虚拟机 (JVM) 仅有一个堆区域,该区域在线程之间共享。

Spring Boot Actuator 可以观察堆内存的值。 Spring Boot Actuator 将堆值作为 jvm.memory.used/committed/max 的一部分。 有关详细信息,请参阅用于排查内存问题的工具jvm.memory.used/committed/max 部分。

堆内存分为“新生代”和“老年代”。 以下列表中介绍了这些术语以及相关术语。

  • 新生代:所有新对象都在新生代中分配和老化。

    • 伊甸园:新对象在伊甸园中分配。
    • 幸存者区:在经过一个垃圾回收周期后,对象将从伊甸园移动到幸存者区。 幸存者区可以分为两部分:s1 和 s2。
  • 老年代:也称为“养老区”。 在幸存者区中保留很长时间的对象将被移动到老年代。

在 Java 8 之前,另一个称为“永久代”的部分也是堆的一部分。 从 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 在伊甸园已满时执行。 它删除新生代中的所有死对象,并将存活对象从伊甸园移动到幸存者区的 s1,或从 s1 移动到 s2。

完全 GC 或主 GC 在整个堆中进行垃圾回收。 完全 GC 还可以收集元空间和直接内存等部分,这些部分只能通过完全 GC 进行清理。

最大堆大小会影响副 GC 和完全 GC 的频率。 最大元空间和最大直接内存大小会影响完全 GC。

将最大堆大小设置为较低的值时,垃圾回收发生的频率会增加,这会稍微降低应用的速度,但会更好地限制内存使用。 将最大堆大小设置为较高的值时,垃圾回收发生的频率会降低,这可能会产生更多的内存不足 (OOM) 风险。 有关详细信息,请参阅内存不足问题导致的应用重启问题内存不足问题的类型部分。

元空间和直接内存只能由完全 GC 回收。 当元空间或直接内存已满时,会发生完全 GC。

Java 内存配置

以下部分介绍了 Java 内存配置的重要方面。

Java 容器化

Azure Spring Apps 中的应用程序在容器环境中运行。 有关详细信息,请参阅将 Java 应用程序容器化

重要的 JVM 选项

可以使用 JVM 选项配置每个内存部分的最大大小。 可以使用 Azure CLI 命令或通过 Azure 门户设置 JVM 选项。 有关详细信息,请参阅用于排查内存问题的工具修改配置来解决问题部分。

以下列表介绍了 JVM 选项:

  • 堆大小配置

    • -Xms 按绝对值设置初始堆大小。
    • -Xmx 按绝对值设置最大堆大小。
    • -XX:InitialRAMPercentage 按堆大小/应用内存大小的百分比设置初始堆大小。
    • -XX:MaxRAMPercentage 按堆大小/应用内存大小的百分比设置最大堆大小。
  • 直接内存大小配置

    • -XX:MaxDirectMemorySize 按绝对值设置最大直接内存大小。 有关详细信息,请参阅 Oracle 文档中的 MaxDirectMemorySize
  • 元空间大小配置

    • -XX:MaxMetaspaceSize 按绝对值设置最大元空间大小。

默认最大内存大小

以下部分介绍了如何设置默认最大内存大小。

默认最大堆大小

Azure Spring Apps 将默认最大堆内存大小设置为 Java 应用内存的大约 50%-80%。 具体而言,Azure Spring Apps 使用以下设置:

  • 如果应用内存 < 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)
      • 伊甸园
    • 老年代
  • 非堆内存
    • 观测到的部分(通过 Spring Boot Actuator 观察)
      • 元空间:每日使用量参考值为 50M-256M
      • 代码缓存
      • 压缩类空间
    • 未观察到的部分(Spring Boot Actuator 未观察到):每日使用量参考值为 150M-250M。
      • 线程堆栈
      • GC、内部符号等
  • 直接内存:每日使用量参考值为 10M-200M。

下图显示了相同的信息。 灰色数字为每日内存量参考值。

2-GB 应用的典型内存布局图。

总体而言,配置最大内存大小时,应考虑内存中各个部分的使用量,并且所有最大大小的总和不应超过总可用内存。

Java OOM

OOM 表示应用程序内存不足。 有两个不同的概念:容器 OOM 和 JVM OOM。 有关详细信息,请参阅内存不足问题导致的应用重启问题

请参阅