zoukankan      html  css  js  c++  java
  • 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
    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默认开启

    参考资料:

  • 相关阅读:
    改变文件属性和权限
    Linux文件属性
    Linux 学习笔记
    一些常见的第三方UI库
    list转化为json数组
    在js文件里调用另一个js文件里的函数
    Intellij idea创建maven项目并配置tomcat
    使用JS语句,利用for循环的方法创建表格的两种方法
    intellij idea 破解教程
    Jquery中的DOM操作:
  • 原文地址:https://www.cnblogs.com/caoweixiong/p/12427202.html
Copyright © 2011-2022 走看看