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:MaxRAMPercentage
和 XX: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
可以帮助我们更好地调整应用程序并提高性能。