本來部署在物理機上服務遷移至 docker 容器以後,發現 「Parallel GC Threads」 和 「C* CompilerThread」 的數量不正常。html
因爲這些線程的數量與 CPU 的核心數是正相關的,因此在 docker 容器設置了 CPU 限制以後,應該比在物理機上少一些纔對。java
以一個 CPU 設置爲 4 的 docker 容器爲例:linux
「Parallel GC Threads」 線程數的計算公式在 vm_version.cpp
中:git
若是 os::active_processor_count()
返回 4,那麼線程數應該是 4;可是實際的線程數爲 33,能夠反推 JVM 獲取到的 CPU 核心數爲 48,與物理機的核心數一致。github
如今的問題是:JVM 沒法感知 docker 容器設置的 CPU 限制,至少在 Java HotSpot(TM) 64-Bit Server VM (build 25.102-b14, mixed mode) 版本是這樣。docker
咱們先看一下 jdk8u102-b14 中 active_processor_count()
方法的實現,在 os_linux.cpp
中:oracle
經過 sysconf 獲取當前在線的 CPU 核心數,這樣獲取的固然是物理機的 CPU 核心數。app
搜索了一下發現新發布的 Java SE 對 docker 進行了支持 —— 《Java SE support for Docker CPU and memory limits》測試
As of Java SE 8u131, and in JDK 9, the JVM is Docker-aware with respect to Docker CPU limits transparently. That means if -XX:ParalllelGCThreads, or -XX:CICompilerCount are not specified as command line options, the JVM will apply the Docker CPU limit as the number of CPUs the JVM sees on the system. The JVM will then adjust the number of GC threads and JIT compiler threads just like it would as if it were running on a bare metal system with number of CPUs set as the Docker CPU limit.ui
使用 JDK 9 還遙遙無期,萬幸 Java SE 8u131 也進行了兼容,在驗證測試以前咱們先看一下 jdk8u131-b11 中的代碼實現:
這個實現看起來只支持 --cpuset-cpus
這種指定固定 CPU 的方式。
順着 JDK 8 Update Release Notes 查看,發如今 1.8.0_191-b12 提供了更完善的 docker 容器支持。
再來看一下 jdk8u191-b12 中的代碼實現:
ActiveProcessorCount
,那麼使用配置的 CPU 核心數;OSContainer
中;is_containerized()
的實如今 osContainer_linux.hpp
中:
_is_containerized
的初始化在 init()
方法中,因爲代碼比較多這裏就不復制了,總結一下處理邏輯:
/proc/self/mountinfo
,也是 false;/proc/self/cgroup
,也是 false;接着看一下 OSContainer::active_processor_count()
的實現:
處理邏輯在註釋中寫得很清楚:
若是你對 docker 不太熟悉,能夠經過官方文檔理解 cpu_quota、cpu_period 和 cpu_shares 這三個配置項。
須要說明一下 cpu_shares 只是一個軟限制,只有在機器 CPU 資源飽和時纔有效,因此 PreferContainerQuotaForCPUCount 默認也是 true。
設置 --cpu-quota=800000
、--cpu-period=100000
、--cpu-shares=4096
,也就是預期的 CPU 核心數是 8。
「Parallel GC Threads」 線程數符合預期。
升級 JDK 版本不太容易,新 feature 每每伴隨着新 bug,因此也有一些外部連接預加載庫的方案。