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 和内存。

...
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 及更高版本上可用。

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

# 主机内存4G
[root@localhost ~]# free -h
total used free shared buff/cache available
Mem: 3.8G 361M 2.3G 11M 1.2G 3.2G
Swap: 3.9G 0B 3.9G # 容器内存设置4G,JVM内存占用1G,正好是容器内存的 1/4
[root@localhost ~]# 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.0.2" 2019-07-16
OpenJDK Runtime Environment (build 12.0.2+10)
OpenJDK 64-Bit Server VM (build 12.0.2+10, mixed mode, sharing) # 容器内存设置8G,,JVM内存占用2G,正好是容器内存的 1/4
[root@localhost ~]# docker run -m 8GB --rm openjdk:12 java -XshowSettings:vm -version
VM settings:
Max. Heap Size (Estimated): 2.00G
Using VM: OpenJDK 64-Bit Server VM openjdk version "12.0.2" 2019-07-16
OpenJDK Runtime Environment (build 12.0.2+10)
OpenJDK 64-Bit Server VM (build 12.0.2+10, mixed mode, sharing)

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

# 主机内存4G
[root@localhost ~]# free -h
total used free shared buff/cache available
Mem: 3.8G 361M 2.3G 11M 1.2G 3.2G
Swap: 3.9G 0B 3.9G # 容器内存设置4G,JVM内存占用875.00M,占用主机内存的1/4, 不是设置的容器内存的1/4
[root@localhost ~]# docker run -m 4GB --rm openjdk:7u111-alpine java -XshowSettings:vm -version
VM settings:
Max. Heap Size (Estimated): 875.00M
Ergonomics Machine Class: server
Using VM: OpenJDK 64-Bit Server VM java version "1.7.0_111"
OpenJDK Runtime Environment (IcedTea 2.6.7) (Alpine 7.111.2.6.7-r2)
OpenJDK 64-Bit Server VM (build 24.111-b01, mixed mode) # 容器内存设置8G,JVM内存占用875.00M,占用主机内存的1/4, 不是设置的容器内存的1/4
[root@localhost ~]# docker run -m 8GB --rm openjdk:7u111-alpine java -XshowSettings:vm -version
VM settings:
Max. Heap Size (Estimated): 875.00M
Ergonomics Machine Class: server
Using VM: OpenJDK 64-Bit Server VM java version "1.7.0_111"
OpenJDK Runtime Environment (IcedTea 2.6.7) (Alpine 7.111.2.6.7-r2)
OpenJDK 64-Bit Server VM (build 24.111-b01, mixed mode)

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

以上可以总结如下

  1. Java 8u191 +,10 及更高版本引入了 UseContainerSupport 允许 JVM 从主机读取 cgroup 限制,例如可用的 CPU 和 RAM,并进行相应的配置。也就是说这些版本的jvm使用的内存默认是设置的容器内存的1/4.
  2. Java 1.7 及以下版本,仍然使用的是物理机内存.也就是说这些版本的jvm使用的内存默认是主机内存的1/4,不管容器内存限制怎么设置.

如何进行参数配置

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

1. -XX:MaxRAMFraction, -XX:MinRAMFraction (弃用)
2. -XX:MaxRAMPercentage, -XX:MinRAMPercentage
3. -Xmx, -Xms

MaxRAMPercentage/MinRAMPercentage

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

[root@localhost ~]# docker run -m 1GB openjdk:12 java -XX:MaxRAMPercentage=50 -XshowSettings:vm -version
VM settings:
Max. Heap Size (Estimated): 494.94M
Using VM: OpenJDK 64-Bit Server VM openjdk version "12.0.2" 2019-07-16
OpenJDK Runtime Environment (build 12.0.2+10)
OpenJDK 64-Bit Server VM (build 12.0.2+10, mixed mode, sharing) [root@localhost ~]# docker run -m 2GB openjdk:12 java -XX:MaxRAMPercentage=50 -XshowSettings:vm -version
VM settings:
Max. Heap Size (Estimated): 1.00G
Using VM: OpenJDK 64-Bit Server VM openjdk version "12.0.2" 2019-07-16
OpenJDK Runtime Environment (build 12.0.2+10)
OpenJDK 64-Bit Server VM (build 12.0.2+10, mixed mode, sharing)

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

docker 容器的内存设置为 2GB 和“-XX:MaxRAMPercentage=50”。基于此设置,JVM 将最大堆大小分配为 1GB(2GB 的一半)。

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

-Xmx/-Xms

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

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

[root@localhost ~]# docker run -m 1GB openjdk:8u131 java -Xmx512m -XshowSettings:vm -version VM
VM settings:
Max. Heap Size: 512.00M
Ergonomics Machine Class: server
Using VM: OpenJDK 64-Bit Server VM openjdk version "1.8.0_131"
OpenJDK Runtime Environment (build 1.8.0_131-8u131-b11-2-b11)
OpenJDK 64-Bit Server VM (build 25.131-b11, mixed mode)

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

实践总结

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

配置建议:

  • 容器内存 Request >= 1.25 * JVM 最大堆内存 ;
  • 容器内存 Limit >= 2 * JVM 最大堆内存;

2.镜像中尽可能包含日常需要的工具,比如常见的 mysql-client、redis-client、网络工具等,当出现问题时,这些工具可能可以帮助你第一时间定位和发现问题。

3.如果您在容器内仅运行 Java 应用程序,则将初始堆大小与最大堆大小最好相等。如此设置会产生较低的垃圾收集暂停时间。因为每当堆大小从初始分配的大小增长时,会发生 STW。当您将初始和最大堆大小设置为相同时,它可以在一定程度上被规避。

4.配置 JVM 启动的垃圾收集日志打印并分析是否因容器中的新设置而受到影响。给出一个常用 GC 日志输出配置:

-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

5.添加监控, 你可以可以选择 Prometheus

6.添加健康检查探针,帮助服务重启和启动过程的预热,但要根据实际情况配置探针的探测频率和超时时间,防止反复重启。

7.服务添加优雅关闭,防止不必要的流量损失。

Java容器化参数配置最佳实践的更多相关文章

  1. paip.log4j 日志系统 参数以及最佳实践

    paip.log4j 日志系统 参数以及最佳实践   %d{yyyy-MM-dd HH:mm:ss} [thrd:%t] %5p   loger:%c   (%C.%M.%L)  - %m%n 201 ...

  2. 如何让HTTPS站点评级达到A+? 还得看这篇HTTPS安全优化配置最佳实践指南

    0x00 前言简述 SSL/TLS 简单说明 描述: 当下越来越多的网站管理员为企业站点或自己的站点进行了SSL/TLS配置, SSL/TLS 是一种简单易懂的技术,它很容易部署及运行,但要对其进行安 ...

  3. 《转载》Java异常处理的10个最佳实践

    本文转载自 ImportNew - 挖坑的张师傅 异常处理在编写健壮的 Java 应用中扮演着非常重要的角色.异常处理并不是功能性需求,它需要优雅地处理任何错误情况,比如资源不可用.非法的输入.nul ...

  4. 利用Google开源Java容器化工具Jib构建镜像

    转载:https://blog.csdn.net/u012562943/article/details/80995373 一.前言 容器的出现让Java开发人员比以往任何时候都更接近“编写一次,到处运 ...

  5. Java异常处理的10个最佳实践

    本文作者: ImportNew - 挖坑的张师傅 未经许可,禁止转载! 异常处理在编写健壮的 Java 应用中扮演着非常重要的角色.异常处理并不是功能性需求,它需要优雅地处理任何错误情况,比如资源不可 ...

  6. Knative 应用在阿里云容器服务上的最佳实践

    作者|元毅 阿里云智能事业群高级开发工程师 相信通过前面几个章节的内容,大家对 Knative 有了初步的体感,那么在云原生时代如何在云上玩转 Knative?本篇内容就给你带来了 Knative 应 ...

  7. (转)Amazon Aurora MySQL 数据库配置最佳实践

    转自:https://zhuanlan.zhihu.com/p/165047153 Amazon Aurora MySQL 数据库配置最佳实践 AWS云计算 ​ 已认证的官方帐号 1 人赞同了该文章 ...

  8. atitit.spring3 mvc url配置最佳实践

    atitit.spring3 mvc url配置最佳实践 1. Url-pattern  bp 1 2. 通用星号url pattern的问题 1 3. Other code 1 4. 参考 2 1. ...

  9. vagrant 虚拟机配置最佳实践

    Mac VirtualBox Vagrant 管理虚拟机 这篇文章定位是在理解了 vagrant 相关概念之后,教你如何灵活玩转自己的虚拟机配置 本文为 @favoorr 常用的 Mac Virtua ...

随机推荐

  1. Pytorch从0开始实现YOLO V3指南 part3——实现网络前向传播

    本节翻译自:https://blog.paperspace.com/how-to-implement-a-yolo-v3-object-detector-from-scratch-in-pytorch ...

  2. DNS 系列(一):为什么更新了 DNS 记录不生效?

    我们在上网时如果想要访问到另一台机器上的内容,通常只需要直接输入一串地址,例如:www.upyun.com,就能够准确访问到自己想要访问的网站.但是实际上这只是方便我们记忆的字符形式网络标识,真正让我 ...

  3. Docker安装canal、mysql进行简单测试与实现redis和mysql缓存一致性

    一.简介 canal [kə'næl],译意为水道/管道/沟渠,主要用途是基于 MySQL 数据库增量日志解析,提供增量数据订阅和消费. 早期阿里巴巴因为杭州和美国双机房部署,存在跨机房同步的业务需求 ...

  4. 使用docker buildx打包发布多平台镜像

    2022-07-07 个人比较喜欢影视作品,下载了大量的剧集视频,有些文件的命名不规范,就需要重新命名,之前是用的一款 renamer 客户端软件,用起来不太爽就自己做了个 bs 架构的重命名软件并开 ...

  5. squareline搭档OneOS图形组件之可视化GUI开发

    LVGL+OneOS! LVGL,一款很火的GUI开发库,一个高度可裁剪.低资源占用.界面美观且易用的嵌入式系统图形库.本身并不依赖特定的硬件平台,任何满足LVGL硬件配置要求的微控制器均可运行LVG ...

  6. Active Backup for Business激活

    依次执行 https://IP地址:5001/webapi/auth.cgi?api=SYNO.API.Auth&method=Login&version=1&account= ...

  7. jdbc 04: 配置连接信息

    jdbc连接mysql,将需要的信息配置到文件中 package com.examples.jdbc.o4_配置连接信息; import java.sql.Connection; import jav ...

  8. SpringCloud gateway自定义请求的 httpClient

    本文为博主原创,转载请注明出处: 引用 的 spring cloud gateway 的版本为 2.2.5 : SpringCloud gateway 在实现服务路由并请求的具体过程是在 org.sp ...

  9. 6.17 NOI 模拟

    \(T1\ crime\) 计算几何\(+\)最短路,我的写法很麻烦 比较无脑,直接扫一遍判断能否连接即可,需要特别判断对角线的情况 #include<bits/stdc++.h> #de ...

  10. APICloud 可视化编程 - 拖拉拽实现专业级源码

    低代码开发平台是无需编码 (0 代码或⽆代码) 或通过少量代码就可以快速生成应用程序的开发平台.它的强⼤之处在于,允许终端⽤户使⽤易于理解的可视化⼯具开发自己的应用程序,而不是传统的编写代码⽅式.当遇 ...