前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java容器化参数配置最佳实践

Java容器化参数配置最佳实践

作者头像
用户5166556
发布2023-03-18 14:28:02
2K0
发布2023-03-18 14:28:02
举报
文章被收录于专栏:让技术和时代并行

Java是以VM为基础的,而云原生讲究的就是Native,天然的矛盾,虽然Quarkus是为GraalVM和HotSpot量身定制的K8s Native Java框架,生态原因切换成本太高,这种矛盾体现在很多方面,比如:当你在物理机或者虚拟机上配置 JVM 参数时,你可以选择使用-Xmx/-Xms 来指定 Java 堆大小,但这样指定的话,就固定了 JVM 堆占用大小,如果将 Java 应用程序移植到容器或者说 K8s Pod 中,K8S 本身有垂直扩容的能力,如果我把内存从 8G 增长到 16G,JVM 如何感知到呢?我们又该如何配置 Java 堆大小呢?本文我们讨论下如何在 Java 容器中参数配置的最佳实践。

在 K8S Pod 中,我们是否有必要指定 Java 堆大小配置

K8s 编排文件中有两个比较重要的资源限制参数 request / limit, 如下所示通过这两个参数我们可以限制内部容器占用的 CPU 和内存。

代码语言:javascript
复制
...
     containers:
      - name: tomcat-test
        image: harbor/tomcat-test:v2021
        env:
        - name: JVM_OPTS
          value: "-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -XX:MaxRAMFraction=1 -Xms256M"
        resources:
          requests:
            cpu: 2
        memory: 4Gi
          limits:
            cpu: 2
            memory: 4Gi
...

设置这两个参数的目的是什么呢?

就是告诉 K8s 资源调度器,你的服务总共需要这么多的资源配额,如果要超过了 limit,k8s 会毫不客气的把服务 kill 掉。

我们知道对于 JVM 来说,默认情况下占用物理机内存的 1/4,那对于容器来说,JVM 如何感知内存占用呢?

为了解决这个问题,Java 10 引入了 UseContainerSupport 允许 JVM 从主机读取 cgroup 限制,例如可用的 CPU 和 RAM,并进行相应的配置。这样当容器超过内存限制时,会抛出 OOM 异常,而不是杀死容器。该特性在 Java 8u191 +,10 及更高版本上可用。具体可以参考文档[1]

当然你也可以通过如下命令进行简单的验证 JVM 是否能够感知容器内存的限制以及默认情况下占用内存的大小。

代码语言:javascript
复制
[root@x-test ~]# docker run -m 4GB --rm  openjdk:12 java -XshowSettings:vm  -version
VM settings:
    Max. Heap Size (Estimated): 1.00G
    Using VM: OpenJDK 64-Bit Server VM

openjdk version "12-ea" 2019-03-19
OpenJDK Runtime Environment (build 12-ea+23)
OpenJDK 64-Bit Server VM (build 12-ea+23, mixed mode, sharing)

在如上这个验证过程中,你会发现对于低版本 JDK,比如 1.7 及以下版本,仍然使用的是物理机内存,如果恰巧使用了这种低版本,一定要想办法添加-Xmx/-Xms 资源使用限制,否则就会出现服务无缘无故被 kill 的问题。

代码语言:javascript
复制
[root@x-test ~]# docker run -m 4GB --rm  openjdk:9-jre-slim java  -XshowSettings:vm  -version
VM settings:
    Max. Heap Size (Estimated): 29.97G
    Using VM: OpenJDK 64-Bit Server VM

openjdk version "9.0.4"
OpenJDK Runtime Environment (build 9.0.4+12-Debian-4)
OpenJDK 64-Bit Server VM (build 9.0.4+12-Debian-4, mixed mode)

如此 JVM 自动识别到容器限制后,默认把最大堆设置为了容器内存的 1/4,从某种程度上来说,对内存的使用产生了浪费。所以很有必要在 JVM 层面进行参数设置,而不仅仅设置 K8s 编排文件。

如何进行参数配置

Java 提供了如下三组参数用于限制容器中 Java 堆内存占用大小

代码语言:javascript
复制
1. -XX:MaxRAMFraction, -XX:MinRAMFraction

2. -XX:MaxRAMPercentage, -XX:MinRAMPercentage

3. -Xmx, -Xms

下面进行一一介绍

MaxRAMFraction/MinRAMFraction

版本支持:'-XX:MaxRAMFraction', '-XX:MinRAMFraction' JVM 参数仅支持从 Java 8 更新 131 到 Java 8 更新 190。因此,如果您使用任何其他版本的 JDK,则不能使用此选项。

如何配置:如果您打算使用“-XX:MaxRAMFraction” JVM 参数,请确保同时传递这两个额外的 JVM 参数“-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap”。只有当您传递这两个 JVM 参数时,JVM 才会从容器的内存大小派生堆大小值,否则,它将从底层主机的内存大小派生堆大小值。具体参考[2]

配置测试

这种配置方法有一定缺点,如果要配置 docker 内存大小的 40%,那么我们必须设置'-XX:MaxRAMFraction=2.5'。当您传递 2.5 作为值时,JVM 将不会启动。这是因为 '-XX:MaxRAMFraction' 只能取整数值,请参阅下面的示例,其中 JVM 无法启动。

代码语言:javascript
复制
# docker run -m 1GB openjdk:8u131 java -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -XX:MaxRAMFraction=2.5 -XshowSettings:vm -version VM
Improperly specified VM option 'MaxRAMFraction=2.5'
Error: Could not create the Java Virtual Machine.
Error: A fatal exception has occurred. Program will exit.

这大概就是这一对参数被弃用的原因吧,具体参考[3]

MaxRAMPercentage/MinRAMPercentage

Java 8 update 191 及更高版本支持“-XX:MaxRAMPercentage”、“-XX: MinRAMPercentage” JVM 参数。因此,如果您在较旧的 JDK 版本上运行,则不能使用此 JVM 参数。

代码语言:javascript
复制
# docker run -m 1GB openjdk:10 java -XX:MaxRAMPercentage=50 -XshowSettings:vm -version
VM settings:
    Max. Heap Size (Estimated): 494.94M
    Using VM: OpenJDK 64-Bit Server VM

在这里您可以看到 docker 容器的内存设置为 1GB 和“-XX:MaxRAMPercentage=50”。基于此设置,JVM 将最大堆大小分配为 494.9MB(大约 1GB 的一半)。

具体配置可以参考[4]

注意:在网上很多文章中提到在传递“-XX:MaxRAMPercentage”、“-XX:InitialRAMPercentage”、“-XX:MinRAMPercentage”时需要传递-XX:+UseContainerSupport JVM 参数。事实并非如此。-XX:+UseContainerSupport是 JVM 中的默认参数。因此无需显式配置。

-Xmx/-Xms

这一对参数配置最大优点就是所有 JDK 版本都支持 -Xmx

在这里您可以看到非容器(传统物理服务器)支持的 -Xmx,如下所示可以看到容器中的 java 8 update 131 版本支持 -Xmx:

代码语言:javascript
复制
# docker run -m 1GB openjdk:8u131 java -Xmx512m -XshowSettings:vm -version VM
VM settings:
    Max. Heap Size: 512.00M
    Ergonomics Machine Class: client
    Using VM: OpenJDK 64-Bit Server VM

但是它的最大缺点就是如果当你为 Pod 动态分配(-Xmx)容器的内存大小 JVM 无法感知到,因此应用程序可能遇到内存溢出的问题。

实践总结

  1. 你可以根据场景选择使用配置项(即 -XX:MaxRAMFraction、-XX:MaxRAMPercentage、-Xmx),但是请始终确保为容器分配的内存至少比您的容器多 25% 堆大小值。假设您已将 -Xmx 值配置为 2GB,然后将容器的内存限制至少为 2.5GB。即使您的 Java 应用程序是将在容器上运行的唯一进程,也要这样做。因为除了堆空间,您的应用程序还需要用于 Java 线程、垃圾收集、元空间、本机内存、套接字缓冲区的空间。所有这些组件都需要分配的堆大小之外的额外内存。除此之外,其他小进程(如 网络代理、日志收集脚本等)也需要内存,参考[7]。

配置建议:

  • 容器内存 Request >= 1.25 * JVM 最大堆内存 ;
  • 容器内存 Limit >= 2 * JVM 最大堆内存;
  1. 镜像中尽可能包含日常需要的工具,比如常见的 mysql-client、redis-client、网络工具等,当出现问题时,这些工具可能可以帮助你第一时间定位和发现问题。
  2. 如果您在容器内仅运行 Java 应用程序,则将初始堆大小与最大堆大小最好相等。如此设置会产生较低的垃圾收集暂停时间。因为每当堆大小从初始分配的大小增长时,会发生 STW。当您将初始和最大堆大小设置为相同时,它可以在一定程度上被规避。
  3. 配置 JVM 启动的垃圾收集日志打印并分析是否因容器中的新设置而受到影响。给出一个常用 GC 日志输出配置:
代码语言:javascript
复制
-XX:+UseG1GC -XX:InitialRAMPercentage=75.0 -XX:MaxRAMPercentage=75.0 -XX:MinRAMPercentage=75.0 -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintTenuringDistribution -XX:+PrintHeapAtGC -XX:+PrintReferenceGC -XX:+PrintGCApplicationStoppedTime -Xloggc:gc-%t.log -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=15 -XX:GCLogFileSize=50M
  1. 添加监控, 你可以可以选择 Prometheus,具体不在赘述具体实践参考[5] 或者其它 java 体系自带的监控,参考[6]
  2. 添加健康检查探针,帮助服务重启和启动过程的预热,但要根据实际情况配置探针的探测频率和超时时间,防止反复重启。Kubernetes生产环境最佳实践
  3. 服务添加优雅关闭,防止不必要的流量损失。K8s Pod优雅关闭,没你想象的那么简单!

参考

  1. https://www.oracle.com/java/technologies/javase/jdk-relnotes-index.html
  2. https://medium.com/marionete/managing-java-heap-size-in-kubernetes-3807159e2438
  3. https://www.oracle.com/java/technologies/javase/8u191-relnotes.html
  4. https://medium.com/@marketing_864/difference-between-initialrampercentage-minrampercentage-maxrampercentage-8c9c0706ec95
  5. https://marknienaber.medium.com/how-to-monitor-your-java-applications-jvm-70d7bddea668
  6. https://segmentfault.com/a/1190000037451226
  7. https://dzone.com/articles/best-practices-java-memory-arguments-for-container
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-11-04,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 云原生技术爱好者社区 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 在 K8S Pod 中,我们是否有必要指定 Java 堆大小配置
  • 如何进行参数配置
    • MaxRAMFraction/MinRAMFraction
      • MaxRAMPercentage/MinRAMPercentage
        • -Xmx/-Xms
        • 实践总结
        • 参考
        相关产品与服务
        容器服务
        腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档