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
여기서도 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. 톰캣 9
Tomcat 9 컨테이너에는 자체 시작 스크립트가 있으므로 JVM 매개 변수를 설정하려면 해당 스크립트로 작업해야합니다.
빈 / catalina.sh의 스크립트는 우리를 필요로 환경 변수에 메모리 매개 변수를 설정 CATALINA_OPTS .
먼저 Tomcat에 배포 할 war 파일 을 만들어 보겠습니다 .
그런 다음 CATALINA_OPTS 환경 변수를 선언 하는 간단한 Dockerfile을 사용하여 컨테이너화 합니다.
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 Buildpack 을 사용합니다.
플러그인은 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-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 애플리케이션의 컨테이너화를 관리하기 위해 빌드 도구를 활용하는 방법을 살펴 보았습니다.