前言

对于那些在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=2ubuntu /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默认开启

参考资料:

java jvm限制cpu_Docker——JVM 感知容器的 CPU 和 Memory 资源限制相关推荐

  1. Docker容器指定使用那几个CPU、memory资源限制

    https://www.cnblogs.com/zhuochong/p/9728383.html

  2. Java高级深入与JVM

    1.关于HashCode       不能根据hashCode值判断两个对象是否相等,但可以直接根据hashCode值判断两个对象不相等.       如果两个对象的hashCode值不等,一定是不同 ...

  3. JAVA基础+集合+多线程+JVM

    1. Java 基础 1.1. 面向对象和面向过程的区别 面向过程性能比面向对象高. 因为类调用时需要实例化,开销比较大,比较消耗资源,所以当性能是最重要的考量因素的时候 等一般采用面向过程开发.但是 ...

  4. java 启动多个jvm实例_当多个Java程序在同一台计算机上运行时

    小编典典 1)如果我有一个用Java编写的Web服务,它将需要一个JVM实例来运行.那么可以将JVM设为守护进程吗? 是的,它可以.如何完成取决于O / S和Web服务器容器本身. 2)如果是,当我们 ...

  5. 好程序员Java教程分享之jvm篇

    好程序员java教程分享之jvm篇,在前面的文章中,介绍了JVM内存模型分为:堆区.虚拟机栈.方法区.本地方法区和程序计数器,其中堆区是JVM中最大的一块内存区域,在Java中的所有对象实例都保存在此 ...

  6. Java 内存模型和 JVM 内存结构真不是一回事

    这两个概念估计有不少人会混淆,它们都可以说是 JVM 规范的一部分,但真不是一回事!它们描述和解决的是不同问题,简单来说, Java 内存模型,描述的是多线程允许的行为 JVM 内存结构,描述的是线程 ...

  7. java 利用ManagementFactory获取jvm,os的一些信息--转

    原文地址:http://blog.csdn.net/dream_broken/article/details/49759043 想了解下某个Java项目的运行时jvm的情况,可以使用一些监控工具,比如 ...

  8. jvm系列(八):jvm知识点总览-高级Java工程师面试必备

    在江湖中要练就绝世武功必须内外兼备,精妙的招式和深厚的内功,武功的基础是内功.对于武功低(就像江南七怪)的人,招式更重要,因为他们不能靠内功直接去伤人,只能靠招式,利刃上优势来取胜了,但是练到高手之后 ...

  9. java解析nes_【JVM系列】一步步解析java执行内幕

    对于任何一门语言,要想达到精通的水平,研究它的执行原理(或者叫底层机制)不失为一种良好的方式.在本篇文章中,将重点研究java源代码的执行原理,即从程 序员编写JAVA源代码,到最终形成产品,在整个过 ...

最新文章

  1. 网站权重增加需要做的
  2. 如何设置MySQL的时区?
  3. python【蓝桥杯vip练习题库】ADV-73数组输出
  4. jw player相关JS插件
  5. 糖果传递 (数学题)
  6. 23行代码_动图展示——快排详解(排序最快的经典算法)
  7. 苹果一半员工未接受4年制大学教育 库克却对此感到骄傲
  8. Spring配置实现AOP
  9. jquery监听html状态,jquery监听页面刷新
  10. 还在使用OpenGL ES做渲染,你Out了,赶紧来拥抱Vulkan吧~
  11. Atitit 图像清晰度 模糊度 检测 识别 评价算法 源码实现attilax总结
  12. ThinkPHP表单信息收集系统
  13. 打开secpol.msc、gpedit.msc显示“试图引用不存在的令牌”,复制到其他目录可正常打开
  14. jsp页面获取到后台的cookie(记住我)
  15. css3 呼吸的莲花_Css实例制作“荷花盛开”代码演示
  16. Android so文件函数加密
  17. 常见的HTTP网络状态码汇总+HttpServletResponse源码
  18. vue实现倒计时定时器
  19. McObject新版eXtremeDB v.8.0在E8存储上运行STAC基准测试创造了多个记录
  20. < 每日算法 - Javascript解析:经典弹珠游戏 >

热门文章

  1. ANN神经网络车牌识别
  2. IPX:互联网分组交换协议--网络大典
  3. 西门子300硬件升级包下载_用博途建立西门子触摸屏和S7300PLC的通讯
  4. android assets目录下资源,Android之assets资源目录的各种操作
  5. qp系统激活服务器异常,电子口岸系统QP服务器异常早上开始,电子口 – 手机爱问...
  6. 论文文字公式怎么降重
  7. 用python画玫瑰花的简笔画-只用C++和Python,让你的简笔画实时动起来!
  8. 王小二切饼(递推)SDUT
  9. 分享一次光电可控硅的误用
  10. 数据分析——Tableau数据可视化与仪表盘搭建