Java 容器内存参数的最佳实战方式

当您在物理服务器中运行 Java 应用程序时,应该会使用“-Xmx” JVM 参数来指定 Java 堆内存大小。如果您正在将程序移植到容器中,或许您会想知道如何在容器中配置 Java 堆内存的大小。还有,是不是有什么最佳的实战方式呢?在本文中,我们将讨论可用于指定 Java 堆内存大小的可能 JVM 参数,以及可供选择的最佳选项。

这里有 3 个不同的选项可用于指定容器中的最大 Java 堆内存大小。分别是:

1. -XX:MaxRAMFraction,-XX:MinRAMFraction

2. -XX:MaxRAMPercentage, -XX:MinRAMPercentage

3. -Xmx

下面就让我们来讨论一下这些 JVM 参数及其优缺点。

1. -XX:MaxRAMFraction-XX:MinRAMFraction

支持版本

“XX:MaxRAMFraction”,“XX:MinRAMFraction” JVM 参数仅在 Java 8 update 131 至 Java 8 update 190 之间的版本中支持。因此,如果您使用的是任何其他版本的 JDK,就无法使用此选项。

工作原理?

假设您已经为容器分配了 1 GB 的内存,那么如果配置 -XX:MaxRAMFraction=2,则会有大约 512 MB(即:1 GB 的 1/2)被分配给 Java 堆内存。

如果您要使用“-XX:MaxRAMFraction” JVM 参数,请确保同时传递这两个额外的 JVM 参数“-XX:+UnlockExperimentalVMOptions”、“-XX:+UseCGroupMemoryLimitForHeap”。只有传递了这两个参数,JVM 才会根据容器内存大小确定出堆内存大小,否则其会根据底层主机的内存大小来确定堆内存大小。

#  docker run -m 1GB openjdk:8u131 java -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -XX:MaxRAMFraction=2 -XshowSettings:vm -version VM settings:
Max.Heap Size (Estimated): 494.94M

这里您可以看到,docker 容器的内存设置为了“1 GB”(即:-m 1GB)且“‘-XX:MaxRAMFraction=2”。基于此设置,JVM 将最大堆内存的大小分配为了 494.9MB(大约是 1 GB 的一半)。

注意:需同时使用“-XX:MaxRAMFraction”与“-XX:MinRAMFraction”才能确认最大 Java 堆内存大小。JDK 研发团队其实应该给“-XX:MinRAMFraction”取一个更好的名字。现在的名字“-XX:MinRAMFraction”可能会让我们觉得这个参数是用来配置最小堆内存大小的。但实际上却并不是这样。如需了解更多有关各种区别的信息,请查阅这篇文章

其局限性是什么?

下面就是这一方法的缺点。

a.如果要将堆内存大小配置为 docker 内存大小的 40%,那么我们必须设置“-XX:MaxRAMFraction=2.5”。当您传递“2.5”这个值时,JVM 是不会启动的。这是因为“-XX:MaxRAMFraction”只能接收整数值,不接收小数值。下面的例子就展示了 JVM 启动失败的情形。

# docker run -m 1GB openjdk:8u131 java -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -XX:MaxRAMFraction=2.5 -XshowSettings:vm -version VM
Improperly specified VM option 'MaxRAMFraction=2.5'
Error: Could not create the Java Virtual Machine.
Error: A fatal exception has occurred.Program will exit.

b.在此选项中,Java 应用程序的堆内存大小将根据容器的内存大小确定(该选项设置的是划分成多少等份)。果您的程序需要 1GB 的堆内存才能实现最佳性能,但在容器的运行配置中使用了少于 1GB 的内存,此时程序仍将运行,不过其性能会受到影响。

c.该参数已在现代 Java 版本中弃用。只有 Java 8 update 131 至 Java 8 update 190 之间的版本支持该参数。

2. -XX:MaxRAMPercentage-XX: MinRAMPercentage

支持版本

 Java 8 update 191 及以上版本支持“-XX:MaxRAMPercentage”、“‘-XX: MinRAMPercentage” JVM 参数。所以如果您运行的是旧版本的 JDK,则无法使用此 JVM 版本。

工作方式?

假设您已经为容器分配了 1GB 的内存,那么如果配置 -XX:MaxRAMPercentage=2,则会有大约 512 MB(即:1 GB 的 1/2)被分配给 Java 堆内存。

# docker run -m 1GB openjdk:10 java -XX:MaxRAMPercentage=50 -XshowSettings:vm -version
VM settings:
Max.Heap Size (Estimated): 494.94M
Using VM: OpenJDK 64-Bit Server VM

这里您可以看到,docker 容器的内存设置为了“-m 1GB”且“-XX:MaxRAMPercentage=50”。基于此设置,JVM 将最大堆内存的大小分配为了 494.9MB(大约是 1GB 的一半)。

注意:需同时使用“-XX:MaxRAMPercentage”与“-XX:MinRAMPercentage”才能确认最大 Java 堆内存大小。JDK 研发团队其实应该给“-XX:MinRAMPercentage”取一个更好的名字。现在的名字“-XX:MinRAMPercentage”可能会让我们觉得这个参数是用来配置最小堆内存大小的。但实际上却并不是这样。如需了解更多有关各种区别的信息,请查阅这篇文章

注意:网上的几篇文章中提到,当您传递“XX:MaxRAMPercentage”、“XX:InitialRAMPercentage”以及“XX:MinRAMPercentage”时,需要同时传递“-XX:+UseContainerSupport” JVM 参数。实际上这并不完全准确。“-XX:+UseContainerSupport”会在 JVM 中通过默认参数传递,因此您不需要对其进行显式配置。

其局限性是什么?

下面就是这一方法所受的限制。

a.该参数不受旧版本 Java 的支持。仅 Java 8 update 191 及以上版本中支持此参数。

b. 在此选项中,Java 应用程序的堆内存大小将根据容器的内存大小确定(该选项设置的占多少百分比)。果您的程序需要 1GB 的堆内存才能实现最佳性能,但在容器的运行配置中使用了少于 1GB 的内存,此时程序仍将运行,不过其性能会受到影响。

3. -Xmx

支持版本:

所有版本的 Java 均支持“-Xmx”

工作方式?

您可以使用“-Xmx” JVM 参数来指定特定的大小,例如 512MB、1024MB。

在这里您可以看到非容器环境中支持的 -Xmx(传统物理服务器):

# java -Xmx512m -XshowSettings:vm -version
VM settings:
Max.Heap Size: 512.00M
Ergonomics Machine Class: client
Using VM: OpenJDK 64-Bit Server VM

在这里您可以看到容器环境 Java 8 update 131 中支持的 -Xmx:

# docker run -m 1GB openjdk:8u131 java -Xmx512m -XshowSettings:vm -version VM
VM settings:
Max.Heap Size: 512.00M
Ergonomics Machine Class: client
Using VM: OpenJDK 64-Bit Server VM

在这里您可以看到容器环境 Java 10 中支持的 -Xmx:

# docker run -m 1GB openjdk:10 java -Xmx512m -XshowSettings:vm -version
VM settings:
Max.Heap Size: 512.00M
Using VM: OpenJDK 64-Bit Server VM

其局限性是什么?

a.如果您要分配的“-Xmx”超过了容器内存大小,那么程序将出现“java.lang.OutOfMemoryError:  kill process or sacrifice child”错误

最佳实践方式

  1. 无论您使用什么选项来配置堆内存大小(比如:-XX:MaxRAMFraction、-XX:MaxRAMPercentage、-Xmx),请始终确保为容器(即:“-m”)分配至少比堆内存大小值多 25% 的内存。假设您已将 -Xmx 值配置为 2GB,那么容器的内存大小就至少需要配置为 2.5GB。即使您的 Java 应用程序是唯一要在容器中运行的进程,此操作也有必要执行。因为很多工程师认为,Java 程序不会消耗比 -Xmx 设定值更多的内存。但这并不准确。除了堆内存空间之外,您的程序还需要为 Java 线程、垃圾回收、元空间、原生内存以及套接字缓冲区留出空间。所有的这些元素都需要在堆内存大小之外额外分配内存。除此之外,其他的一些小进程(如:APM Agent、splunk 脚本等)也将需要内存。如需进一步了解这些内容,可观看这一简短的“JVM 内存”视频
  2. 如果在容器中,您*只需要运行自己的 Java 程序*,那么可将初始堆内存大小(即:使用“-XX:InitialRAMFraction”、“-XX:InitialRAMPercentage”或“-Xms”)与最大堆内存大小设置为相同的值。设置初始堆内存大小和最大堆内存大小是有一定好处的。其中之一就是:垃圾回收停顿时间会降低。因为当堆内存从最初分配的大小开始出现增长时,其都会使 JVM 出现停顿。当您将初始和最大堆大小设置为相同的值时,则可以绕过该停顿。除此之外,如果您的容器内存大小不足,那么 JVM 甚至都不会启动(这比在事务运行时遭遇 OutOfMemoryError 要好一些)。
  3. 就我个人而言,我更喜欢使用 -Xmx 选项(而不是 XX:MaxRAMFraction、-XX:MaxRAMPercentage)来指定容器中的 Java 堆内存大小,原因如下:
    • 我不想让 Java 堆内存的大小受到容器大小的限制。就像衣服一样,是买“小号”、“中号”还是“大号”的 T 恤,这应该是根据我们的体型来决定,而不是反过来。相信您也不会想把一个一米八的大汉塞进“小号” T 恤的。在决定程序性能方面,内存的大小有着至关重要的作用。其会对垃圾回收的行为和性能特征产生影响。相信您也不会想把这些因素交由容器的内存设置来确定。
    • 借助“-Xmx”,我可以设置精确的内存值(如:512MB、256MB)。
    • 所有版本的 Java 都支持 -Xmx。
  4. 您应该对容器的新设置是否会对程序的垃圾回收和性能特征造成影响进行研究。而在这一过程中,您可使用 GCeasyIBM GC & Memory VisualizerHP jmeter 等免费工具来了解垃圾回收方面的行为。

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

Blog at WordPress.com.

Up ↑

%d bloggers like this: