• 关于Docker中设置Java应用的JVM


    背景

    运行在 Docker 容器中的 Java 应用经常会被操作系统 kill,但 JVM 没有 OOM 日志,下面是一个 Java 应用的容器因为超过了 cgroup 的限制被 kill:

    # dmesg -T

    [Sun Mar 22 10:26:23 2020] Memory cgroup out of memory: Kill process 25086 (java) score 1838 or sacrifice child

    [Sun Mar 22 10:26:23 2020] Killed process 25086 (java) total-vm:12040204kB, anon-rss:3705828kB, file-rss:23472kB

    为什么设置了 -Xmx 还是被 kill

    根据 JDK1.8 的特点,JVM 运行时的内存 = 非heap(元空间 + Thread Stack * num of thread + …) + heap + JVM进程运行所需内存 + 其他数据,我们设置的 -Xmx 等参数只是限制了 JVM 堆内存(heap) 的大小,当 -Xmx 设置的值接近与容器限制的值,堆内存 + 非堆内存的使用总和超出了 cgroup 的限制就会被操作系统 kill 掉。

    让 JVM 动态感知 cgroup

    如果没有设置堆内存的大小,默认情况下,JVM 的 Max Heap Size 是操作系统的 1/4,我们知道 Docker 是通过 CGroups 来实现内存的限制,而 /proc 目录只是以只读的形式挂载到容器中,默认情况下 Java 是看不到 CGroups 限制的内存大小,而是通过 /proc/meminfo 中的信息作为内存信息启动,这种不兼容的情况就会导致,容器分配的内存小于JVM Max Heap Size 的情况。

    操作系统的内存信息:

    / $ [ec2-user@ip-172-29-165-49 ~]$ cat /proc/meminfo

    MemTotal: 31960240 kB

    MemFree: 233200 kB

    MemAvailable: 4412724 kB

    Docker 容器读取到的内存信息:

    [ec2-user@ip-172-29-165-49 ~]$ docker run -it --rm alpine cat /proc/meminfo

    Unable to find image ‘alpine:latest’ locally

    latest: Pulling from library/alpine

    89d9c30c1d48: Pull complete

    Digest: sha256:c19173c5ada610a5989151111163d28a67368362762534d8a8121ce95cf2bd5a

    Status: Downloaded newer image for alpine:latest

    MemTotal: 31960240 kB

    MemFree: 278460 kB

    MemAvailable: 4351520 kB

    Java 8u131及以上版本开始支持了Docker的cpu和memory限制。 在 java8u131+ 及 java9,需要加上 -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap 就能使 JVM 感知到对容器的内存限制了。

    不过在 Java11 中移除了 UseCGroupMemoryLimitForHeap,而增加了一个 bool UseContainerSupport = true 的参数。

    版本

    -XX:+UseCGroupMemoryLimitForHeap

    -XX:ActiveProcessorCount

    -XX:+UseContainerSupport

    java9

    experimental,默认false

    java10

    experimental,默认false

    -1

    java11

    移除

    -1

    product,默认true

    如何优化

    虽然 JVM 能够动态感知 CGroups 对容器的内存限制了,但是 JVM 默认的堆大小是限制值的 1/4,这会导致内存的利用率太低了;如此看来,要想充分利用服务器的资源,还要手动调整好 -Xmx 参数,下面是来自网络的一组经验值:

    以下参数配置适用于非计算密集型的大部分应用

    分配内存

    堆配置推荐

    1.5G

    -Xmx1008M -Xms1008M -Xmn336M -XX:MaxMetaspaceSize=128M -XX:MetaspaceSize=128M

    2G

    -Xmx1344M -Xms1344M -Xmn448M -XX:MaxMetaspaceSize=192M -XX:MetaspaceSize=192M

    3G

    -Xmx2048M -Xms2048M -Xmn768M -XX:MaxMetaspaceSize=256M -XX:MetaspaceSize=256M

    4G

    -Xmx2688M -Xms2688M -Xmn960M -XX:MaxMetaspaceSize=256M -XX:MetaspaceSize=256M

    5G

    -Xmx3392M -Xms3392M -Xmn1216M -XX:MaxMetaspaceSize=512M -XX:MetaspaceSize=512M

    6G

    -Xmx4096M -Xms4096M -Xmn1536M -XX:MaxMetaspaceSize=512M -XX:MetaspaceSize=512M

    7G

    -Xmx4736M -Xms4736M -Xmn1728M -XX:MaxMetaspaceSize=512M -XX:MetaspaceSize=512M

    8G

    -Xmx5440M -Xms5440M -XX:MaxMetaspaceSize=512M -XX:MetaspaceSize=512M

    内存>=8G 基础配置

    -server

    -XX:+DisableExplicitGC

    -XX:+UseG1GC

    -XX:MaxGCPauseMillis=100

    -XX:+ParallelRefProcEnabled

    -XX:+HeapDumpOnOutOfMemoryError

    -XX:+PrintGCDetails

    -XX:+PrintGCTimeStamps

    -XX:+PrintHeapAtGC

    -XX:+PrintGCApplicationStoppedTime

    -XX:+PrintGCDateStamps

    -XX:ErrorFile=/var/app/gc/hs_err_pid%p.log

    -XX:HeapDumpPath=/var/app/gc

    -Xloggc:/var/app/gc/gc%t.log

    内存<8G 基础配置

    -server

    -XX:+DisableExplicitGC

    -XX:+UseParNewGC

    -XX:+UseConcMarkSweepGC

    -XX:+UseCMSInitiatingOccupancyOnly

    -XX:CMSInitiatingOccupancyFraction=70

    -XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses

    -XX:+CMSClassUnloadingEnabled

    -XX:+ParallelRefProcEnabled

    -XX:+CMSScavengeBeforeRemark

    -XX:+HeapDumpOnOutOfMemoryError

    -XX:+PrintGCDetails

    -XX:+PrintGCTimeStamps

    -XX:+PrintHeapAtGC

    -XX:+PrintGCApplicationStoppedTime

    -XX:+PrintGCDateStamps

    -XX:ErrorFile=/var/app/gc/hs_err_pid%p.log

    -XX:HeapDumpPath=/var/app/gc

    -Xloggc:/var/app/gc/gc%t.log

    参考

  • 相关阅读:
    电脑死机重启后服务出现:Failed to start RocketMQ push consumer
    51.基于SpringBoot + Vue实现的前后端分离-校园志愿者管理系统(项目 + 论文)
    0605-JavaSE-单例模式-饿懒汉模式
    从 0 到 1 ,手把手教你编写《消息队列》项目(Java实现) —— 核心类内存存储
    常用负载均衡详解
    10 分钟教会你如何看懂 MySQL 执行计划
    蓝桥杯1039
    深入理解 C 语言的内存管理
    <el-drawer>中在添加弹窗,遮罩层被覆盖
    库表字段加密存储,查询解密
  • 原文地址:https://blog.csdn.net/m0_67391121/article/details/126565049