1. 概述

Java 应用程序的关键参数之一是堆空间大小。它直接影响我们能使用的内存,并间接影响应用程序的性能。例如,压缩指针的使用、垃圾回收循环的数量和持续时间等。

在这个教程中,我们将学习如何使用 --XX:MaxRAM 标志来为堆大小计算提供更多的调优机会。这对于在容器内或不同主机上运行的应用程序尤其重要。

2. 堆大小计算

配置堆的标志可以协同工作,也可能相互覆盖。理解它们之间的关系对于深入了解它们的目的至关重要。

2.1. 使用 -Xmx

控制堆大小的主要方式是 -Xmx-Xms 标志,它们分别控制最大和初始堆大小。这是一个强大的工具,但它不考虑机器或容器上的可用空间。假设我们在各种主机上运行应用程序,这些主机的可用内存范围从4GB到64GB。

如果没有 -Xmx,JVM 会自动为应用程序分配大约25%的可用RAM作为堆。然而,通常情况下,JVM 分配的初始堆大小取决于系统架构、JVM版本、平台等多个因素。

在某些情况下,这种行为可能不可取。根据可用内存的不同,它可能会分配出显著不同的堆。让我们检查一下在拥有24GB内存的机器上,JVM 默认分配了多少堆:

$ java -XX:+PrintFlagsFinal -version |\
grep -e '\bMaxHeapSize\|\bMinHeapSize\|\bInitialHeapSize' 
   size_t InitialHeapSize   = 402653184    {product} {ergonomic}
   size_t MaxHeapSize       = 6442450944   {product} {ergonomic}
   size_t MinHeapSize       = 8388608      {product} {ergonomic}

JVM 分配了大约6GB或25%,这可能对我们的应用程序过多。 设置特定的最大堆大小也可能产生问题。如果我们使用 -Xmx4g,对于内存不足的主机,它可能会失败,而且我们也不会获得额外的内存:

$ java -XX:+PrintFlagsFinal -Xmx4g -version |\
grep -e '\bMaxHeapSize\|\bMinHeapSize\|\bInitialHeapSize'
   size_t InitialHeapSize   = 402653184    {product} {ergonomic}
   size_t MaxHeapSize       = 4294967296   {product} {command line}
   size_t MinHeapSize       = 8388608      {product} {ergonomic}

在某些情况下,可以通过脚本动态计算 -Xmx 来解决这个问题。但这跳过了JVM的启发式算法,该算法可能更精确地了解应用程序的需求。

2.2. 使用 -XX:MaxRAM

-XX:MaxRAM 标志旨在解决上述问题。首先,它防止JVM在内存丰富的系统上过度分配内存。我们可以将此标志视为“运行应用,但假设你最多只有X数量的RAM”。

此外,-XX:MaxRAM 允许JVM使用标准的堆大小启发式。让我们回顾之前的例子,但使用 -XX:MaxRAM

$ java -XX:+PrintFlagsFinal -XX:MaxRAM=6g -version |\
grep -e '\bMaxHeapSize\|\bMinHeapSize\|\bInitialHeapSize'
   size_t InitialHeapSize   = 100663296    {product} {ergonomic}
   size_t MaxHeapSize       = 1610612736   {product} {ergonomic}
   size_t MinHeapSize       = 8388608      {product} {ergonomic}

在这种情况下,JVM 计算了堆的最大大小,但假设我们只有6GB的RAM。请注意,不应与 -Xmx 一起使用 -XX:MaxRAM。因为 -Xmx 更具体,它会覆盖 -XX:MaxRAM

$ java -XX:+PrintFlagsFinal -XX:MaxRAM=6g -Xmx6g -version |\ 
grep -e '\bMaxHeapSize\|\bMinHeapSize\|\bInitialHeapSize'
   size_t InitialHeapSize   = 100663296    {product} {ergonomic}
   size_t MaxHeapSize       = 6442450944   {product} {command line}
   size_t MinHeapSize       = 8388608      {product} {ergonomic}

这个标志可以改善资源利用率和堆分配。然而,我们仍然无法控制应为堆分配多少内存。

2.3. 使用 -XX:MaxRAMPercentage-XX:MinRAMPercentage

现在我们有了控制权,可以告诉JVM应考虑多少RAM。让我们定义分配堆的策略。-XX:MaxRAM 标志与 -XX:MaxRAMPercentage-XX:MinRAMPercentage 配合得很好。它们提供了更大的灵活性,特别是在容器化环境中。让我们尝试使用 -XX:MaxRAM 并将堆设置为可用RAM的50%:

$ java -XX:+PrintFlagsFinal -XX:MaxRAM=6g -XX:MaxRAMPercentage=50 -version |\
grep -e '\bMaxHeapSize\|\bMinHeapSize\|\bInitialHeapSize'
   size_t InitialHeapSize   = 100663296    {product} {ergonomic}
   size_t MaxHeapSize       = 3221225472   {product} {ergonomic}
   size_t MinHeapSize       = 8388608      {product} {ergonomic}

关于 -XX:MinRAMPercentage,存在一些常见的混淆。它并不像 -Xms 那样工作。尽管如此,假设它设置最小堆大小是有道理的。让我们检查以下设置:

$ java -XX:+PrintFlagsFinal -XX:MaxRAM=16g -XX:MaxRAMPercentage=10 -XX:MinRAMPercentage=50 -version |\
grep -e '\bMaxHeapSize\|\bMinHeapSize\|\bInitialHeapSize'
   size_t InitialHeapSize   = 268435456    {product} {ergonomic}
   size_t MaxHeapSize       = 1719664640   {product} {ergonomic}
   size_t MinHeapSize       = 8388608      {product} {ergonomic}

我们设置了 XX:MaxRAMPercentageXX:MinRAMPercentage,但很明显,只有 XX:MaxRAMPercentage 起作用。我们为堆分配了16GB内存的10%。但是,如果我们将可用内存减少到200MB,我们将看到不同的行为:

$ java -XX:+PrintFlagsFinal -XX:MaxRAM=200m -XX:MaxRAMPercentage=10 -XX:MinRAMPercentage=50 -version |\
grep -e '\bMaxHeapSize\|\bMinHeapSize\|\bInitialHeapSize' 
   size_t InitialHeapSize   = 8388608      {product} {ergonomic}
   size_t MaxHeapSize       = 109051904    {product} {ergonomic}
   size_t MinHeapSize       = 8388608      {product} {ergonomic}

在这种情况下,堆大小由 XX:MinRAMPercentage 控制。当可用内存低于200MB时,此标志生效。现在,我们可以将堆增加到75%:

$ java -XX:+PrintFlagsFinal -XX:MaxRAM=200m -XX:MaxRAMPercentage=10 -XX:MinRAMPercentage=75 -version |\
grep -e '\bMaxHeapSize\|\bMinHeapSize\|\bInitialHeapSize'
   size_t InitialHeapSize   = 8388608      {product} {ergonomic}
   size_t MaxHeapSize       = 134217728    {product} {ergonomic}
   size_t MinHeapSize       = 8388608      {product} {ergonomic}

如果我们继续为如此小的堆应用 XX:MaxRAMPercentage,我们将得到20MB的堆,这可能不足以满足我们的目的。这就是为什么我们有针对大堆和小堆的不同标志。-XX:MaxRAM 标志与两者配合良好,给了我们更多的控制权。

3. 总结

控制堆大小对Java应用程序至关重要。分配更多的内存并不总是好事;同样,分配不足的内存也不好。

使用 -Xmx-XX:MaxRAM-XX:MaxRAMPercentage-XX:MinRAMPercentage 可以帮助我们更好地调整应用程序并提高性能。