如何在Docker容器中配置Java堆大小
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 .
Dockerfile
的CMD
行是我們運行容器時默認執行的過程。由於我們沒有提供-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 Maven和Gradle插件可以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_OPTS
或JAVA_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應用程序的容器化。