1. 概要

コンテナ内でJavaを実行する場合、利用可能なリソースを最大限に活用するようにJavaを調整することをお勧めします。

このチュートリアルでは、Javaプロセスを実行するコンテナーにJVMパラメーターを設定する方法を説明します。 以下はすべてのJVM設定に適用されますが、一般的な-Xmxおよび-Xmsフラグに焦点を当てます。

また、特定のバージョンのJavaで実行されるプログラムをコンテナー化する一般的な問題と、いくつかの一般的なコンテナー化されたJavaアプリケーションでフラグを設定する方法についても説明します。

2. Javaコンテナのデフォルトのヒープ設定

JVMは、適切なデフォルトのメモリ設定を決定するのに非常に優れています。

以前は、JVMはコンテナに割り当てられたメモリとCPUを認識していませんでした。 そのため、Java10は新しい設定+ UseContainerSupport (デフォルトで有効)を導入してルート原因を修正し、開発者は修正を8u191でJava8にバックポートしました。 。 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% ofに設定していることがわかります。 この例では、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. JDK8u191より前

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または-XmsJVMフラグを提供しなかったため、メモリ設定はデフォルトになります。

そのコンテナを実行してみましょう:

$ 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. JDK8u130以降

同じテストプログラムで、 Dockerfile の最初の行を変更して、より最新のJVM8を使用してみましょう。

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とAdoptOpenJDK

コンテナのコマンドで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. Tomcat 9

Tomcat 9コンテナには独自の起動スクリプトがあるため、JVMパラメータを設定するには、それらのスクリプトを操作する必要があります。

bin / catalina.sh スクリプトでは、環境変数CATALINA_OPTSにメモリパラメータを設定する必要があります。

まず、Tomcatにデプロイするwarファイル作成しましょう。

次に、単純な 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行目にいくつかのデフォルトを指定しました。 X184X]。

適用されたランタイムパラメータをチェックし、オプション-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 2.3以降、Spring Boot MavenおよびGradleプラグインは、Dockerfileなしで効率的なコンテナーを構築できます。

Mavenを使用して、それらを<に追加します。 構成> spring-boot-maven-plugin内のブロック:

<?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

これにより、次の名前の画像が作成されます 。 この例では demo-app:0.0.1-SNAPSHOT 。 内部的には、SpringBootは基盤となるコンテナ化テクノロジーとしてCloud NativeBuildpacksを使用しています。

プラグインは、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. GoogleJIBの使用

Spring Boot mavenプラグインと同様に、 Google JIB は、Dockerfileなしで効率的なDockerイメージを作成します。 MavenプラグインとGradleプラグインは同様の方法で構成されます。 Google JIBは、環境変数JAVA_TOOL_OPTIONSをJVMパラメーターのオーバーライドメカニズムとしても使用します。

Google JIB Mavenプラグインは、実行可能なjarファイルを生成できる任意のJavaフレームワークで使用できます。 たとえば、 spring-boot-maven プラグインの代わりにSpring Bootアプリケーションで使用して、コンテナーイメージを生成することができます。

<?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アプリケーションのコンテナ化を管理する方法を確認しました。

いつものように、例のソースコードはGitHubから入手できます。