Java 服务 Docker 容器化最佳实践
wptr33 2024-12-19 16:51 28 浏览
一、概述
当我们在容器中运行 Java 应用程序时,可能希望对其进行调整参数以充分利用资源。
在本教程中,我们将了解如何在运行 Java 进程的容器中设置 JVM 参数。本文将重点关注常见的 -Xmx 和-Xms 标志。
另外,我们还将研究使用某些 Java 版本运行的程序容器化的常见问题,以及如何在常见的容器化 Java 应用程序时设置自定义标志。
二、Java 容器中的默认堆设置
过去,JVM 不知道分配给容器的内存和 CPU。
Java 10 引入了一个新设置:+UseContainerSupport(默认启用)来修复 这个问题[3],并在 8u191[4] 中将修复反向移植到 Java 8 。
现在 JVM 可以根据分配给容器的内存计算其内存。
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。
出于测试目的,创建一个文件,名为PrintXmxXms.java,内容是以 MB 为单位打印堆大小,代码内容如下:
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
public class PrintXmxXms {
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;
System.out.println("Initial Memory (xms) : " + xms + "mb");
System.out.println("Max Memory (xmx) : " + xmx + "mb");
}
}
假设已经安装了 JDK,可以编译程序并运行:
$ javac ./PrintXmxXms.java
$ java -cp . PrintXmxXms
在 16Gb RAM 的主机上,输出结果为:
INFO: Initial Memory (xms) : 254mb
INFO: Max Memory (xmx) : 4056mb
下面,在容器中尝试一下。
- JDK 8u191 之前的版本
在包含 PrintXmxXms.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,它早于更新版本中可用的容器支持。构建镜像:
$ sudo docker build -t oldjava .
Dockerfile? 中的 CMD? 行是运行容器时默认执行的进程。由于没有提供-Xmx或-XmsJVM 标志,内存设置将是默认设置。
运行容器:
$ sudo 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
现在使用--memory=1g命令行标志将容器内存限制为 1GB:
$ sudo 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 没有遵守容器内存限制。
- JDK 8u130 之后的版本
使用相同的测试程序,更改 Dockerfile 的第一行来使用 JVM 8 的新版本:
FROM openjdk:8-jdk-alpine
然后再次做测试:
$ sudo docker build -t newjava .
$ sudo 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:
$ sudo 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 来获得正确的默认值。
三、常用的基础镜像中内存设置
- OpenJDK
与其直接在容器命令上硬编码 JVM 标志,不如使用环境变量。例如在Dockerfile? 中使用 JAVA_OPTS 变量,可以在启动容器时对其进行修改:
FROM openjdk:8u92-jdk-alpine
COPY *.java /src/
RUN mkdir /app \
&& ls /src \
&& javac /src/PrintXmxXms.java -d /app
ENV JAVA_OPTS=""
CMD ["sh", "-c", \
"java -version \
&& java $JAVA_OPTS -cp /app PrintXmxXms"]
构建镜像:
$ sudo docker build -t openjdk-java .
通过指定JAVA_OPTS环境变量在运行时选择内存设置:
$ sudo docker run --rm -ti -e JAVA_OPTS="-Xms50M -Xmx50M" openjdk-java
openjdk version "1.8.0_92-internal"
OpenJDK Runtime Environment (build 1.8.0_92-internal-alpine-r1-b14)
OpenJDK 64-Bit Server VM (build 25.92-b14, mixed mode)
Initial Memory (xms) : 50mb
Max Memory (xmx) : 48mb
注意:-Xmx? 参数和 JVM? 报告的 Max memory? 之间存在细微差别。这是因为 Xmx 设置了内存分配池的最大大小,其中包括堆、垃圾收集器的幸存者空间和其他池。
2. Tomcat 9
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"
然后构建容器镜像并运行它:
$ sudo docker build -t tomcat .
$ sudo docker run --name tomcat -d -p 8080:8080 \
-e CATALINA_OPTS="-Xms512M -Xmx512M" tomcat
注意:运行时,将新值传递给 CATALINA_OPTS?。如果不提供这个值,会使用 Dockerfile 的第 3 行给出的默认值。
可以检查应用的运行时参数并验证选项-Xmx和-Xms是否存在:
$ sudo docker exec -ti tomcat jps -lv
1 org.apache.catalina.startup.Bootstrap <other options...> -Xms512M -Xmx512M
四、使用构建插件
Maven 和 Gradle 提供的插件允许我们在没有Dockerfile的情况下创建容器镜像。生成的镜像通常可以在运行时通过环境变量进行参数化。
下面看几个例子。
- 使用 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 来覆盖:
$ sudo 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
[...]
- 使用谷歌 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>
镜像是使用 mvn jib:dockerBuild 命令构建的:
$ mvn clean install && mvn jib:dockerBuild
尝试运行:
$ sudo 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
五、结论
在本文中,我们介绍了需要使用最新的 JVM 来获取在容器中默认内存设置。
然后,研究了在自定义容器映像中设置 -Xms? 和 -Xmx 的最佳实践, 以及如何使用现有 Java 应用程序容器在其中设置 JVM 选项。
最后,我们看到了如何利用构建工具来管理 Java 应用程序的容器
相关推荐
- 什么是Java中的继承?如何实现继承?
-
什么是继承?...
- Java 继承与多态:从基础到实战的深度解析
-
在面向对象编程(OOP)的三大支柱中,继承与多态是构建灵活、可复用代码的核心。无论是日常开发还是框架设计,这两个概念都扮演着至关重要的角色。本文将从基础概念出发,结合实例与图解,带你彻底搞懂Java...
- Java基础教程:Java继承概述_java的继承
-
继承概述假如我们要定义如下类:学生类,老师类和工人类,分析如下。学生类属性:姓名,年龄行为:吃饭,睡觉老师类属性:姓名,年龄,薪水行为:吃饭,睡觉,教书班主任属性:姓名,年龄,薪水行为:吃饭,睡觉,管...
- java4个技巧:从继承和覆盖,到最终的类和方法
-
日复一日,我们编写的大多数Java只使用了该语言全套功能的一小部分。我们实例化的每个流以及我们在实例变量前面加上的每个@Autowired注解都足以完成我们的大部分目标。然而,有些时候,我们必须求助于...
- java:举例说明继承的概念_java继承的理解
-
在现实生活中,继承一般指的是子女继承父辈的财产。在程序中,继承描述的是事物之间的所属关系,通过继承可以使多种事物之间形成一种关系体系。例如猫和狗都属于动物,程序中便可以描述为猫和狗继承自动物,同理,...
- 从零开始构建一款开源的 Vibe Coding 产品 Week1Day4:业界调研之 Agent 横向对比
-
前情回顾前面两天我们重点调研了了一下Cursor的原理和Cursor中一个关键的工具edit_file的实现,但是其他CodingAgent也需要稍微摸一下底,看看有没有优秀之处,下...
- 学会这几个插件,让你的Notepad++使用起来更丝滑
-
搞程序开发的小伙伴相信对Notepad++都不会陌生,是一个占用空间少、打开启动快的文件编辑器,很多程序员喜欢使用Notepad++进行纯文本编辑或者脚本开发,但是Notepad++的功能绝不止于此,...
- 将 node_modules 目录放入 Git 仓库的优点
-
推荐一篇文章Whyyoushouldcheck-inyournodedependencies[1]...
- 再度加码AI编程,腾讯发布AI CLI并宣布CodeBuddy IDE开启公测
-
“再熬一年,90%的程序员可能再也用不着写for循环。”凌晨两点半,王工还在公司敲键盘。他手里那份需求文档写了足足六页,产品经理反复改了三次。放在过去,光数据库建表、接口对接、单元测试就得写两三天。现...
- git 如何查看stash的内容_git查看ssh key
-
1.查看Stash列表首先,使用gitstashlist查看所有已保存的stash:...
- 6万星+ Git命令懒人必备!lazygit 终端UI神器,效率翻倍超顺手!
-
项目概览lazygit是一个基于终端的Git命令可视化工具,通过简易的TUI(文本用户界面)提升Git操作效率。开发者无需记忆复杂命令,即可完成分支管理、提交、合并等操作。...
- 《Gemini CLI 实战系列》(一)Gemini CLI 入门:AI 上命令行的第一步
-
谷歌的Gemini模型最近热度很高,而它的...
- deepin IDE新版发布:支持玲珑构建、增强AI智能化
-
IT之家8月7日消息,深度操作系统官方公众号昨日(8月6日)发布博文,更新推出新版deepin集成开发环境(IDE),重点支持玲珑构建。支持玲珑构建deepinIDE在本次重磅更...
- 狂揽82.7k的star,这款开源可视化神器,轻松创建流程图和图表
-
再不用Mermaid,你的技术文档可能已经在悄悄“腐烂”——图表版本对不上、同事改完没同步、评审会上被一句“这图哪来的”问得哑口无言。这不是危言耸听。GitHub2025年开发者报告显示,63%的新仓...
- 《Gemini CLI 实战系列》(五)打造专属命令行工具箱
-
在前几篇文章中,我们介绍了GeminiCLI的基础用法、效率提升、文件处理和与外部工具结合。今天我们进入第五篇...
- 一周热门
-
-
C# 13 和 .NET 9 全知道 :13 使用 ASP.NET Core 构建网站 (1)
-
程序员的开源月刊《HelloGitHub》第 71 期
-
详细介绍一下Redis的Watch机制,可以利用Watch机制来做什么?
-
如何将AI助手接入微信(打开ai手机助手)
-
假如有100W个用户抢一张票,除了负载均衡办法,怎么支持高并发?
-
SparkSQL——DataFrame的创建与使用
-
Java面试必考问题:什么是乐观锁与悲观锁
-
redission YYDS spring boot redission 使用
-
一文带你了解Redis与Memcached? redis与memcached的区别
-
如何利用Redis进行事务处理呢? 如何利用redis进行事务处理呢英文
-
- 最近发表
-
- 什么是Java中的继承?如何实现继承?
- Java 继承与多态:从基础到实战的深度解析
- Java基础教程:Java继承概述_java的继承
- java4个技巧:从继承和覆盖,到最终的类和方法
- java:举例说明继承的概念_java继承的理解
- 从零开始构建一款开源的 Vibe Coding 产品 Week1Day4:业界调研之 Agent 横向对比
- 学会这几个插件,让你的Notepad++使用起来更丝滑
- 将 node_modules 目录放入 Git 仓库的优点
- 再度加码AI编程,腾讯发布AI CLI并宣布CodeBuddy IDE开启公测
- git 如何查看stash的内容_git查看ssh key
- 标签列表
-
- git pull (33)
- git fetch (35)
- mysql insert (35)
- mysql distinct (37)
- concat_ws (36)
- java continue (36)
- jenkins官网 (37)
- mysql 子查询 (37)
- python元组 (33)
- mybatis 分页 (35)
- vba split (37)
- redis watch (34)
- python list sort (37)
- nvarchar2 (34)
- mysql not null (36)
- hmset (35)
- python telnet (35)
- python readlines() 方法 (36)
- munmap (35)
- docker network create (35)
- redis 集合 (37)
- python sftp (37)
- setpriority (34)
- c语言 switch (34)
- git commit (34)