Docker——JVM 感知容器的 CPU 和 Memory 资源限制
前言
对于那些在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 |
cat /proc/meminfo |
容器:
1 |
docker run -it --rm alpine cat /proc/meminfo |
那么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限制。
- 顺着 JDK 8 Update Release Notes 查看,JDK 8u191 提供了更完善的 docker 容器支持。
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默认开启
- java 8u131+ 和 java 8u191以前:
- java10+
- 使用最新版就好了,UseContainerSupport默认开启
参考资料:
- https://www.liangzl.com/get-article-detail-153252.html
- jdk8u131:https://blogs.oracle.com/java-platform-group/java-se-support-for-docker-cpu-and-memory-limits
- jdk8u191:https://www.oracle.com/technetwork/java/javase/8u191-relnotes-5032181.html 、https://cloud.tencent.com/developer/article/1438099
- jdk 10:https://yq.aliyun.com/articles/576200
- docker:https://docs.docker.com/config/containers/resource_constraints/#cpu
- https://www.cnblogs.com/cheyunhua/p/10208059.html
- https://www.cnblogs.com/duanxz/p/10248762.html
- 详细:容器(docker)中运行java需关注的几个小问题
- 生产:https://juejin.im/post/5cd96e0be51d453b5854b8b9
Docker——JVM 感知容器的 CPU 和 Memory 资源限制的更多相关文章
- [转帖]Docker容器CPU、memory资源限制
Docker容器CPU.memory资源限制 https://www.cnblogs.com/zhuochong/p/9728383.html 处理事项内容等 这一块内容感觉 不清楚.. 背景 在使用 ...
- Docker(二十)-Docker容器CPU、memory资源限制
背景 在使用 docker 运行容器时,默认的情况下,docker没有对容器进行硬件资源的限制,当一台主机上运行几百个容器,这些容器虽然互相隔离,但是底层却使用着相同的 CPU.内存和磁盘资源.如果不 ...
- Docker容器CPU、memory资源限制
背景 在使用 docker 运行容器时,默认的情况下,docker没有对容器进行硬件资源的限制,当一台主机上运行几百个容器,这些容器虽然互相隔离,但是底层却使用着相同的 CPU.内存和磁盘资源.如果不 ...
- docker高级应用之cpu与内存资源限制(转)
时间:2015-06-09 14:01:52 阅读:1581 评论:0 收藏:0 [点我收藏+] 标签:docker资源限制 docker cpu限制 ...
- jvm感知docker容器参数
docker中的jvm检测到的是宿主机的内存信息,它无法感知容器的资源上限,这样可能会导致意外的情况. -m参数用于限制容器使用内存的大小,超过大小时会被OOMKilled. -Xmx: 默认为物理 ...
- 限制容器对CPU的使用 - 每天5分钟玩转 Docker 容器技术(28)
上节学习了如何限制容器对内存的使用,本节我们来看CPU. 默认设置下,所有容器可以平等地使用 host CPU 资源并且没有限制. Docker 可以通过 -c 或 --cpu-shares 设置容器 ...
- 容器中JVM获取真实的CPU核数
容器中JVM获取真实的CPU核数 基于 libsysconfcpus的方案,可以为各个版本的JDK提供一个通用的解决方案. libsysconfcpus.so的原理是截获JVM获取CPU核数所用的系统 ...
- Docker深入浅出系列 | 容器初体验
目录 Docker深入浅出系列 | 容器初体验 教程目标 预备工作 容器与虚拟化技术 什么是Docker 为什么要用Docker 事例 什么是容器镜像和容器 容器与虚拟机的区别 Vagrant与Doc ...
- Docker背后的容器管理——Libcontainer深度解析
Libcontainer 是Docker中用于容器管理的包,它基于Go语言实现,通过管理namespaces.cgroups.capabilities以及文件系统来进行容器控制.你可以使用Libcon ...
随机推荐
- JMeter元件作用域实践指南
从一个问题说起 对于以下测试脚本: 为了能调用进入房间接口,需要从考场接口获取考场token.为了调用考场接口,需要从登陆接口获取登陆token.元件说明如下: 学生登录,提取登录${token}传入 ...
- 面试关于Spring循环依赖问题,我建议你这么答!
写在前面 在关于Spring的面试中,我们经常会被问到一个问题:Spring是如何解决循环依赖的问题的. 这个问题算是关于Spring的一个高频面试题,因为如果不刻意研读,相信即使读过源码,面试者也不 ...
- [Azure DevOps] 如何安装并配置 Build Agent
1. 编译服务器 在 Azure Pipelines 中至少需要一个编译服务器的 Agent 才能编译代码或发布软件.Azure DevOps 本身已经提供了一个 Agent,但出于各种理由(需要特殊 ...
- 2020 OO 第三单元总结 JML语言
title: 2020 OO 第三单元总结 date: 2020-05-21 10:10:06 tags: OO categories: 学习 第三单元终于结束了,这是我目前为止最惨的一单元,第十次作 ...
- 如何使用yolov3训练自己的数据集
博客主要结构 1. 如何在ubuntu18.04上安装yolo 2 .如何配置yolov3 3 .如何制作自己的训练集测试集 4 .如何在自己的数据集上运行yolov3 1. 在ubuntu18.04 ...
- (Collection, List, 泛型)JAVA集合框架一
Java集合框架部分细节总结一 Collection List 有序,有下标,元素可重复 Set 无序,无下标,元素不可重复 以上为Collection接口 以ArrayList为实现类实现遍历:增强 ...
- Chrome插件开发入门
最近学习了Chrome插件的开发,总体来说上手还是很容易的,因为浏览器插件本质上依旧是网页,写几个demo基本就了解了他的开发过程. 完整项目:xmy6364/chrome-extension-get ...
- GO-01-GoLang的快捷键
- Day12_62_线程的生命周期
线程的生命周期 要实现多线程,必须在主线程中创建新的线程对象. 任何线程一般都具有五种状态,即创建,就绪,运行,阻塞,终止(消亡) 新建状态:在程序中创建了一个新的线程对象后,新的线程对象便处于新建状 ...
- @valid和自定义异常
@valid和自定义异常 问题的产生: 当有很多参数需要校验时,比如name,age,email等很多参数都需要判空,或者有长度限制时,如果后端写很多if-else就有很多代码,不美观,不优雅.前端每 ...