前言

对于那些在Java应用程序中使用Docker的CPU和内存限制的人来说,可能会遇到一些挑战。特别是CPU限制,因为JVM在内部透明地设置GC线程和JIT编译器线程的数量。

这些可以通过命令行选项 -XX:ParallelGCThreads 和 -XX:CICompilerCount 显式设置。对于内存限制,也可以通过JVM命令行选项 -Xmx 显式设置最大Java堆大小。

但是,在没有指定上述JVM命令行选项的情况下,当使用Java SE 8u121和更早版本的Java应用程序在Docker容器中运行时,可能会出现以下问题:

  • 老的 JVM 版本并不能自动的发现Docker设置的内存限制,CPU限制。这将导致JVM不能稳定服务业务,容器会杀死你JVM进程,而健康检查又将拉起你的JVM进程,进而导致一天重启次数甚至能达到几百次

首先Docker容器本质是是宿主机上的一个进程,它与宿主机共享一个/proc目录,也就是说我们在容器内看到的/proc/meminfo/proc/cpuinfo

与直接在宿主机上看到的一致,如下:

Host:

1
2
3
4
cat /proc/meminfo 
MemTotal: 197869260 kB
MemFree: 3698100 kB
MemAvailable: 62230260 kB

容器:

1
2
3
4
docker run -it --rm alpine cat /proc/meminfo
MemTotal: 197869260 kB
MemFree: 3677800 kB
MemAvailable: 62210088 kB

那么Java是如何获取到Host的内存信息的呢?没错就是通过/proc/meminfo来获取到的。

默认情况下,JVM的Max Heap Size是系统内存的1/4,假如我们系统是8G,那么JVM将的默认Heap≈2G。

Docker通过CGroups完成的是对内存的限制,而/proc目录是已只读形式挂载到容器中的,由于默认情况下Java压根就看不见CGroups的限制的内存大小,而默认使用/proc/meminfo中的信息作为内存信息进行启动,

这种不兼容情况会导致,如果容器分配的内存小于JVM的内存,JVM进程会被理解杀死。

  • 发现 “Parallel GC Threads” 和 “C* CompilerThread” 的线程数量不正常

以一个 CPU 设置为 4 的 docker 容器为例,“Parallel GC Threads” 线程数的计算公式在 vm_version.cpp 中:

  1)如果cpu核心数目少于等于8,则GC线程数量和CPU数一致

  2)如果cpu核心数大于8,则前8个核,每个核心对应一个GC线;其他核,每8个核对应5个GC线程

如果 os::active_processor_count() 返回 4,那么线程数应该是 4;但是实际的线程数为 33,可以反推 JVM 获取到的 CPU 核心数为 48,与物理机的核心数一致。

  • 使用Runtime.getRuntime().availableProcessors() ,会拿到宿主机CPU个数,而不是容器申请时的CPU个数

JDK 版本差异

  • 老的 JVM 版本(JDK 8u131以前)是无法感知容器的资源限制的。
  • 从JDK 8u131开始,在JDK 9中,JVM可以透明地了解Docker的CPU限制。

CPU 限制

  • Java SE 8u131 和 JDK9

如果没有将 -XX:paralllelgthreads 或 -XX:CICompilerCount 指定为命令行选项,JVM将应用Docker CPU限制作为JVM在系统上看到的CPU数量。

然后,JVM将调整GC线程和JIT编译器线程的数量,就像它在裸机系统上运行一样,CPU数量设置为Docker CPU限制。

如果 -XX:ParallelGCThreads 或 -XX:CICompilerCount 被指定为JVM命令行选项,并且指定了Docker CPU限制,JVM将使用 -XX:ParallelGCThreads 和 -XX:CICompilerCount 值。

只支持 --cpuset-cpus 这种指定固定 CPU 的方式:

docker run -it --cpuset-cpus="0" ubuntu /bin/bash
  • Java SE 8u191 和 JDK10

JVM知道在Docker容器中运行,并将提取特定于容器的配置信息,而不是从宿主机提取。正在提取的信息是已分配给容器的CPU数量和总内存。

Java进程可用的cpu总数是根据任何指定的cpu集、cpu共享或cpu配额计算的。此支持仅在基于Linux的平台上可用。默认情况下,此新支持是启用的,可以在命令行中使用JVM选项禁用:

-XX:-UseContainerSupport

此外,此更改还添加了一个JVM选项,该选项提供指定JVM将使用的cpu数量的能力:

-XX:ActiveProcessorCount=count

完整示例:

docker run -it --cpus=2 ubuntu /bin/bash

docker run -it --cpu-period=800000 --cpu-quota=100000 ubuntu /bin/bash

如果你对 docker 不太熟悉,可以通过官方文档理解cpus、cpu_quota、cpu_period 这三个配置项

Memory 限制

  • Java SE 8u131 和 JDK9

对于Docker内存限制,最大Java堆的透明设置还有一些工作要做。要告诉JVM在没有通过 -Xmx 设置最大Java堆的情况下注意Docker内存限制,需要两个JVM命令行选项:

-XX:+UnlockExperimentalVMOptions 和 -XX:+UseCGroupMemoryLimitForHeap

-XX:+UnlockExperimentalVMOptions 是必需的,因为在将来的版本中,目标是透明地标识Docker内存限制。

当使用这两个JVM命令行选项并且未指定 -Xmx 时,JVM将查看Linux cgroup配置,这是Docker容器用于设置内存限制的配置,以便透明地调整最大Java堆大小。

仅供参考,Docker容器也使用cGroup配置来限制CPU。

  • Java SE 8u191 和 JDK10

添加了三个新的JVM选项,以允许Docker容器用户更细粒度地控制将用于Java堆的系统内存量:

-XX:InitialRAMPercentage    #初始百分比
-XX:MaxRAMPercentage       #最大百分比
-XX:MinRAMPercentage     #最小百分比

这些选项替换已弃用的分数形式(-XX:InitialRAMFraction、-XX:maxmRamFraction和-XX:MinRAMFraction)。

总结

CPU

  • java5/6/7/8u131以前:手动设置jvm相关的选项,如:

    • ParallelGCThreads
    • ConcGCThreads
    • G1ConcRefinementThreads
    • CICompilerCount / CICompilerCountPerCPU
  • java8u131+ 和 java9+

    • java 8u131+ 和 java 8u191以前:--cpuset-cpus
    • java 8u191+: UseContainerSupport默认开启
  • java 10+:

    • 使用最新版就好了,UseContainerSupport默认开启

Memory

  • java5/6/7/8u131以前:务必设置内存选项
  • java8u131+ 和 java9+

    • java 8u131+ 和 java 8u191以前:-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap
    • java 8u191+: UseContainerSupport默认开启
  • java10+

    • 使用最新版就好了,UseContainerSupport默认开启

参考资料:

Docker——JVM 感知容器的 CPU 和 Memory 资源限制的更多相关文章

  1. [转帖]Docker容器CPU、memory资源限制

    Docker容器CPU.memory资源限制 https://www.cnblogs.com/zhuochong/p/9728383.html 处理事项内容等 这一块内容感觉 不清楚.. 背景 在使用 ...

  2. Docker(二十)-Docker容器CPU、memory资源限制

    背景 在使用 docker 运行容器时,默认的情况下,docker没有对容器进行硬件资源的限制,当一台主机上运行几百个容器,这些容器虽然互相隔离,但是底层却使用着相同的 CPU.内存和磁盘资源.如果不 ...

  3. Docker容器CPU、memory资源限制

    背景 在使用 docker 运行容器时,默认的情况下,docker没有对容器进行硬件资源的限制,当一台主机上运行几百个容器,这些容器虽然互相隔离,但是底层却使用着相同的 CPU.内存和磁盘资源.如果不 ...

  4. docker高级应用之cpu与内存资源限制(转)

    时间:2015-06-09 14:01:52      阅读:1581      评论:0      收藏:0      [点我收藏+] 标签:docker资源限制   docker cpu限制    ...

  5. jvm感知docker容器参数

    docker中的jvm检测到的是宿主机的内存信息,它无法感知容器的资源上限,这样可能会导致意外的情况. -m参数用于限制容器使用内存的大小,超过大小时会被OOMKilled. -Xmx:  默认为物理 ...

  6. 限制容器对CPU的使用 - 每天5分钟玩转 Docker 容器技术(28)

    上节学习了如何限制容器对内存的使用,本节我们来看CPU. 默认设置下,所有容器可以平等地使用 host CPU 资源并且没有限制. Docker 可以通过 -c 或 --cpu-shares 设置容器 ...

  7. 容器中JVM获取真实的CPU核数

    容器中JVM获取真实的CPU核数 基于 libsysconfcpus的方案,可以为各个版本的JDK提供一个通用的解决方案. libsysconfcpus.so的原理是截获JVM获取CPU核数所用的系统 ...

  8. Docker深入浅出系列 | 容器初体验

    目录 Docker深入浅出系列 | 容器初体验 教程目标 预备工作 容器与虚拟化技术 什么是Docker 为什么要用Docker 事例 什么是容器镜像和容器 容器与虚拟机的区别 Vagrant与Doc ...

  9. Docker背后的容器管理——Libcontainer深度解析

    Libcontainer 是Docker中用于容器管理的包,它基于Go语言实现,通过管理namespaces.cgroups.capabilities以及文件系统来进行容器控制.你可以使用Libcon ...

随机推荐

  1. java面试-内存分配与回收策略

    1.对象优先在Eden分配 -Xms20M -Xmx20M java堆的大小20M -Xmn10M 新生代10M 老年代10M -XX:SurvivorRatio=8 新生代Eden与一个Surviv ...

  2. 如何在 NET 程序万种死法中有效的生成 Dump (上)

    一:背景 相信很多人都知道通过 任务管理器 抓取dump,虽然简单粗暴,但无法满足程序的无数种死法,比如: 内存膨胀,程序爆炸 CPU爆高,程序累死 应用无响应,用户气死 意外退出,和人生一样 既然手 ...

  3. JWT 介绍 - Step by Step

    翻译自 Mohamad Lawand 2021年3月11日的文章 <Intro to JWT - Step by Step> [1] 在本文中,我将向您介绍 JWT[2]. 我们今天要讲的 ...

  4. Object.assign()和解构赋值:给对象赋值的两种方法

    一.Object.assign()方法给对象赋值 Object.assign() 方法用于将所有可枚举属性的值从一个或多个源对象分配到目标对象.它将返回目标对象. 拷贝的是属性值 如果目标对象中的属性 ...

  5. JAVAEE_Servlet_09_Adapter适配器GenericServlet

    适配器 GenericServlet * 适配器 (Adapter) - 适配器的作用? 1.我们目前所有的Servlet类都直接实现了javax.servlet.Servlet接口,但是该接口中有些 ...

  6. 另类的曲线方式定时Start up/Shut down VM 的解决方案

    一,引言 最近看到一位小兄弟在为了做 Azure 云虚拟机的自动关机开启 在群里求助,最后也不知道结果咋样了. 至于他提到的利用 Automation Account 我是没有接触过,并且也没有看资料 ...

  7. OAuth 2.0 了解了,OAuth 2.1 呢?

    OAuth 2.0 OAuth 2.0 是工业级标准授权协议. OAuth 2.0 聚焦于客户端开发者便利性,为网页应用程序.桌面客户端.手机.客厅设备提供特定的授权流程. RFC6749 OAuth ...

  8. 【Java】String、StringBuilder和StringBuffer

    [String] 首先,从String类的定义入手,可以看到String类是由final修饰,即不可变的,一旦创建出来就不可修改,因此首先明确,字符串的拼接.截取等操作都会产生新的字符串对象. 观察以 ...

  9. 探索使用 Golang 和 Webassembly 构建一个多人游戏服务器

    什么是 WebAssembly?由 Google.Microsoft.Mozilla.Apple 等发起的 WebAssembly 是一种新的字节码格式,主流浏览器都已经支持 WebAssembly. ...

  10. 官宣 MAUI 在.NET Preview 3的最新进展

    我们在.NET 6 Preview 3中交付了.NET多平台应用UI的移动和桌面开发的最新进展.此版本添加了Windows平台和WinUI 3,改进了基本应用程序和启动构建器,添加了原生生命周期事件, ...