拨开荷叶行,寻梦已然成。仙女莲花里,翩翩白鹭情。
IMG-LOGO
主页 文章列表 如何在Docker容器中配置Java堆大小

如何在Docker容器中配置Java堆大小

白鹭 - 2021-11-24 635 1 0

1.概述

当我们在容器中运行Java时,我们可能希望对其进行调整以充分利用可用资源。

在本教程中,我们将看到如何在运行Java进程的容器中设置JVM参数。尽管以下内容适用于任何JVM设置,但我们将重点介绍常见的-Xmx-Xms标志。

我们还将研究将某些版本的Java运行的程序容器化的常见问题,以及如何在一些流行的容器化Java应用程序中设置标志。

2. Java容器中的默认堆设置

JVM非常擅长确定适当的默认内存设置。

过去, JVM并不知道分配给容器的内存和CPU 。因此,Java 10引入了一个新设置: +UseContainerSupport (默认情况下启用)以解决根本原因,并且开发人员将修复程序反向移植到8u191中的Java 8。 JVM现在基于分配给容器的内存来计算其内存。

但是,在某些应用程序中,我们仍可能希望更改其默认设置。

2.1。自动内存计算

当我们不设置-Xmx-Xmx参数时,JVM将根据系统规范来调整堆大小

让我们看一下堆大小:

$ java -XX:+PrintFlagsFinal -version | grep -Ei "maxheapsize|maxram"

输出:

openjdk version "15" 2020-09-15

 OpenJDK Runtime Environment AdoptOpenJDK (build 15+36)

 OpenJDK 64-Bit Server VM AdoptOpenJDK (build 15+36, mixed mode, sharing)

 size_t MaxHeapSize = 4253024256 {product} {ergonomic}

 uint64_t MaxRAM = 137438953472 {pd product} {default}

 uintx MaxRAMFraction = 4 {product} {default}

 double MaxRAMPercentage = 25.000000 {product} {default}

 size_t SoftMaxHeapSize = 4253024256 {manageable} {ergonomic}

在这里,我们看到JVM将其堆大小设置为大约可用RAM的25%。在此示例中,它在具有16GB的系统上分配了4GB。

为了进行测试,我们创建一个程序来打印堆大小(以兆字节为单位):

public static void main(String[] args) {

 int mb = 1024 * 1024;

 MemoryMXBean memoryBean = ManagementFactory.getMemoryMXBean();

 long xmx = memoryBean.getHeapMemoryUsage().getMax() / mb;

 long xms = memoryBean.getHeapMemoryUsage().getInit() / mb;

 LOGGER.log(Level.INFO, "Initial Memory (xms) : {0}mb", xms);

 LOGGER.log(Level.INFO, "Max Memory (xmx) : {0}mb", xmx);

 }

让我们将该程序放在一个空目录中,位于一个名为PrintXmxXms.java的文件中。

假设我们已经安装了JDK,则可以在主机上对其进行测试。在Linux系统中,我们可以编译程序并从该目录上打开的终端上运行该程序:

$ javac ./PrintXmxXms.java

 $ java -cp . PrintXmxXms

在具有16Gb RAM的系统上,输出为:

INFO: Initial Memory (xms) : 254mb

 INFO: Max Memory (xmx) : 4,056mb

现在,让我们在一些容器中尝试一下。

2.2。在JDK 8u191之前

让我们在包含我们的Java程序的文件夹中Dockerfile

FROM openjdk:8u92-jdk-alpine

 COPY *.java /src/

 RUN mkdir /app \

 && ls /src \

 && javac /src/PrintXmxXms.java -d /app

 CMD ["sh", "-c", \

 "java -version \

 && java -cp /app PrintXmxXms"]

在这里,我们使用的容器使用Java 8的较早版本,该容器早于最新版本中可用的容器支持。让我们来建立它的形象:

$ docker build -t oldjava .

DockerfileCMD行是我们运行容器时默认执行的过程。由于我们没有提供-Xmx-Xms JVM标志,因此将默认使用内存设置。

让我们运行该容器:

$ docker run --rm -ti oldjava

 openjdk version "1.8.0_92-internal"

 OpenJDK Runtime Environment (build 1.8.0_92-...)

 OpenJDK 64-Bit Server VM (build 25.92-b14, mixed mode)

 Initial Memory (xms) : 198mb

 Max Memory (xmx) : 2814mb

现在让我们将容器内存限制为1GB。

$ docker run --rm -ti --memory=1g oldjava

 openjdk version "1.8.0_92-internal"

 OpenJDK Runtime Environment (build 1.8.0_92-...)

 OpenJDK 64-Bit Server VM (build 25.92-b14, mixed mode)

 Initial Memory (xms) : 198mb

 Max Memory (xmx) : 2814mb

如我们所见,输出完全相同。这证明了较早的JVM不遵守容器内存分配。

2.3。在JDK 8u130之后

使用相同的测试程序,让我们通过更改Dockerfile的第一行来使用最新的JVM 8:

FROM openjdk:8-jdk-alpine

然后,我们可以再次对其进行测试:

$ docker build -t newjava .

 $ docker run --rm -ti newjava

 openjdk version "1.8.0_212"

 OpenJDK Runtime Environment (IcedTea 3.12.0) (Alpine 8.212.04-r0)

 OpenJDK 64-Bit Server VM (build 25.212-b04, mixed mode)

 Initial Memory (xms) : 198mb

 Max Memory (xmx) : 2814mb

同样,它使用整个docker主机内存来计算JVM堆大小。但是,如果我们为容器分配1GB的RAM:

$ docker run --rm -ti --memory=1g newjava

 openjdk version "1.8.0_212"

 OpenJDK Runtime Environment (IcedTea 3.12.0) (Alpine 8.212.04-r0)

 OpenJDK 64-Bit Server VM (build 25.212-b04, mixed mode)

 Initial Memory (xms) : 16mb

 Max Memory (xmx) : 247mb

这次,JVM根据容器可用的1GB RAM计算了堆大小。

现在,我们了解了JVM如何计算其默认值以及为什么需要最新的JVM才能获取正确的默认值,让我们来看一下自定义设置。

3.常用基本映像中的内存设置

3.1。 OpenJDK和采用OpenJDK

与其直接在容器的命令中直接对JVM标志进行硬编码,不如使用JAVA_OPTS这样的环境变量。 Dockerfile使用该变量,但是在启动容器时可以对其进行修改:

FROM openjdk:8u92-jdk-alpine

 COPY src/ /src/

 RUN mkdir /app \

 && ls /src \

 && javac /src/com/baeldung/docker/printxmxxms/PrintXmxXms.java \

 -d /app

 ENV JAVA_OPTS=""

 CMD java $JAVA_OPTS -cp /app \

 com.baeldung.docker.printxmxxms.PrintXmxXms

现在,我们来构建图像:

$ docker build -t openjdk-java .

JAVA_OPTS环境变量在运行时选择内存设置:

$ docker run --rm -ti -e JAVA_OPTS="-Xms50M -Xmx50M" openjdk-java

 INFO: Initial Memory (xms) : 50mb

 INFO: Max Memory (xmx) : 48mb

我们应该注意, -Xmx参数与JVM报告的最大内存之间存在细微差别。这是因为Xmx设置了内存分配池的最大大小,该池包括堆,垃圾回收器的幸存者空间和其他池。

3.2。tomcat9

Tomcat 9容器具有自己的启动脚本,因此要设置JVM参数,我们需要使用这些脚本。

bin/catalina.sh脚本要求我们**在环境变量CATALINA_OPTS**设置内存参数。

首先让我们创建一个war文件,以将其部署到Tomcat。

然后,我们将使用简单的Dockerfile对其进行容器化,在其中声明CATALINA_OPTS环境变量:

FROM tomcat:9.0

 COPY ./target/*.war /usr/local/tomcat/webapps/ROOT.war

 ENV CATALINA_OPTS="-Xms1G -Xmx1G"

然后我们构建容器映像并运行它:

$ docker build -t tomcat .

 $ docker run --name tomcat -d -p 8080:8080 \

 -e CATALINA_OPTS="-Xms512M -Xmx512M" tomcat

我们应该注意,在运行此代码时,我们正在将新值传递给CATALINA_OPTS.但是,如果不提供此值, Dockerfile第3行中提供一些默认值。

我们可以检查应用的运行时参数,并验证我们的选项-Xmx-Xms是否存在:

$ docker exec -ti tomcat jps -lv

 1 org.apache.catalina.startup.Bootstrap <other options...> -Xms512M -Xmx512M

4.使用构建插件

Maven和Gradle提供了插件,使我们可以在没有Dockerfile情况下创建容器映像。生成的图像通常可以在运行时通过环境变量进行参数化。

让我们看几个例子。

4.1。使用Spring Boot

从Spring Boot 2.3开始,Spring Boot MavenGradle插件可以Dockerfile的高效容器

使用Maven,我们将它们添加到spring-boot-maven-plugin configuration>

<?xml version="1.0" encoding="UTF-8"?>

 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

 <groupId>com.baeldung.docker</groupId>

 <artifactId>heapsizing-demo</artifactId>

 <version>0.0.1-SNAPSHOT</version>

 <!-- dependencies... -->

 <build>

 <plugins>

 <plugin>

 <groupId>org.springframework.boot</groupId>

 <artifactId>spring-boot-maven-plugin</artifactId>

 <configuration>

 <image>

 <name>heapsizing-demo</name>

 </image>

 <!--

 for more options, check:

 https://docs.spring.io/spring-boot/docs/2.4.2/maven-plugin/reference/htmlsingle/#build-image

 -->

 </configuration>

 </plugin>

 </plugins>

 </build>

 </project>

要构建项目,请运行:

$ ./mvnw clean spring-boot:build-image

这将产生一个名为<artifact-id>:<version>.在此示例中, demo-app:0.0.1-SNAPSHOT 。在后台,Spring Boot使用Cloud Native Buildpacks作为底层容器化技术。

该插件对JVM的内存设置进行硬编码。但是,我们仍然可以通过设置环境变量JAVA_OPTSJAVA_TOOL_OPTIONS:

$ docker run --rm -ti -p 8080:8080 \

 -e JAVA_TOOL_OPTIONS="-Xms20M -Xmx20M" \

 --memory=1024M heapsizing-demo:0.0.1-SNAPSHOT

输出将类似于以下内容:

Setting Active Processor Count to 8

 Calculated JVM Memory Configuration: [...]

 [...]

 Picked up JAVA_TOOL_OPTIONS: -Xms20M -Xmx20M

 [...]

4.2。使用Google JIB

就像Spring Boot maven插件一样,Google JIB无需Dockerfile即可创建高效的Docker映像。 Maven和Gradle插件以类似的方式配置。 Google JIB还使用环境变量JAVA_TOOL_OPTIONS作为JVM参数的覆盖机制。

我们可以在任何能够生成可执行jar文件的Java框架中使用Google JIB Maven插件。例如,可以在Spring Boot应用程序中使用它代替spring-boot-maven插件来生成容器映像:

<?xml version="1.0" encoding="UTF-8"?>

 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">



 <!-- dependencies, ... -->



 <build>

 <plugins>

 <!-- [ other plugins ] -->

 <plugin>

 <groupId>com.google.cloud.tools</groupId>

 <artifactId>jib-maven-plugin</artifactId>

 <version>2.7.1</version>

 <configuration>

 <to>

 <image>heapsizing-demo-jib</image>

 </to>

 </configuration>

 </plugin>

 </plugins>

 </build>

 </project>

该映象是使用maven的jib:DockerBuild目标构建的:

$ mvn clean install && mvn jib:dockerBuild

现在,我们可以像往常一样运行它:

$ docker run --rm -ti -p 8080:8080 \

 -e JAVA_TOOL_OPTIONS="-Xms50M -Xmx50M" heapsizing-demo-jib

 Picked up JAVA_TOOL_OPTIONS: -Xms50M -Xmx50M

 [...]

 2021-01-25 17:46:44.070 INFO 1 --- [ main] c.baeldung.docker.XmxXmsDemoApplication : Started XmxXmsDemoApplication in 1.666 seconds (JVM running for 2.104)

 2021-01-25 17:46:44.075 INFO 1 --- [ main] c.baeldung.docker.XmxXmsDemoApplication : Initial Memory (xms) : 50mb

 2021-01-25 17:46:44.075 INFO 1 --- [ main] c.baeldung.docker.XmxXmsDemoApplication : Max Memory (xmx) : 50mb

5.结论

在本文中,我们介绍了使用最新的JVM获取在容器中正常工作的默认内存设置的需求。

然后,我们研究了-Xms-Xmx最佳实践,以及如何与现有Java应用程序容器一起在其中设置JVM选项。

最后,我们看到了如何利用构建工具来管理Java应用程序的容器化。

标签:

1 评论

发表评论

您的电子邮件地址不会被公开。 必填的字段已做标记 *