JVM 如何獲取當前容器的資源限制?

阿里云云原生發表於2023-01-11

作者:卜比

本文是《容器中的 Java》系列文章之 1/n,歡迎關注後續連載 :) 。

最近同事說到Java的 ParallelGCThreads [ 1]  引數,我翻了下 jdk8 的程式碼,發現 ParallelGCThreads 的引數預設值如下:

  • 如果 CPU 核心數目少於等於 8,則 GC 執行緒數量和CPU數一致
  • 如果 CPU 核心數大於 8,則前 8 個核,每個核心對應一個 GC 線;其他核,每 8 個核對應 5 個 GC 執行緒

但是被提醒,發現即使在分配 4 核的容器上,GC 執行緒數也為 38。然後就想到應該和容器的資源限制有關——jvm 可能無法覺察到當前容器的資源限制。

翻了下程式碼,發現最新版本的 Java 是能感知容器的資源限制的,就按照 jdk 版本再翻了下程式碼:

線上的 jdk(jdk8u144)

寫一個 sleep 1000s 的程式,用於檢視 JVM 的執行緒數量:

./jdk1.8.0_144/bin/java -XX:+UseG1GC -XX:+ParallelRefProcEnabled Main

然後檢視 GC 執行緒數目:

$ jstack $pid | grep 'Parallel GC Threads' | wc -l38

一算就知道物理機器有 56 個核心(8+(56-8)*5/8=38)
然後使用 +PrintFlagsFinal 看下引數:

$ ./jdk1.8.0_144/bin/java -XX:+UnlockDiagnosticVMOptions -XX:+PrintFlagsFinal  -version | grep ParallelGCThreads
uintx ParallelGCThreads          =38                     {product}

看來 jdk8u144 並無法讀取容器配額。

jdk 8u191

然後發現,從 jdk 8u191 [2 ] 版本開始,Java 就可以讀取容器配額了:

執行同樣的程式:

./jre1.8.0_191/bin/java -XX:+UseG1GC -XX:+ParallelRefProcEnabled Main

$ jstack $pid | grep 'Parallel GC Threads' | wc -l
4

檢視實際引數:

$ ./jre1.8.0_191/bin/java -XX:+UnlockDiagnosticVMOptions -XX:+PrintFlagsFinal  -version | grep ParallelGCThreads
uintx ParallelGCThreads         =4                   {product}

另外,jdk 8u191 引入了 PrintContainerInfo 引數:

$ ./jre1.8.0_191/bin/java -XX:+UnlockDiagnosticVMOptions -XX:+PrintContainerInfo -version
OSContainer::init: Initializing Container Support
Path to /memory.limit_in_bytes is /sys/fs/cgroup/memory/memory.limit_in_bytes
Memory Limit is: 10737418240
Path to /cpu.cfs_quota_us is /sys/fs/cgroup/cpu,cpuacct/cpu.cfs_quota_us
CPU Quota is: 400000
Path to /cpu.cfs_period_us is /sys/fs/cgroup/cpu,cpuacct/cpu.cfs_period_us
CPU Period is: 100000
Path to /cpu.shares is /sys/fs/cgroup/cpu,cpuacct/cpu.shares
CPU Shares is: 681
CPU Quota count based on quota/period: 4
CPU Share count based on shares: 1
OSContainer::active_processor_count: 4
Path to /cpu.cfs_quota_us is /sys/fs/cgroup/cpu,cpuacct/cpu.cfs_quota_us
CPU Quota is: 400000
Path to /cpu.cfs_period_us is /sys/fs/cgroup/cpu,cpuacct/cpu.cfs_period_us
CPU Period is: 100000
Path to /cpu.shares is /sys/fs/cgroup/cpu,cpuacct/cpu.shares
CPU Shares is: 681
CPU Quota count based on quota/period: 4
CPU Share count based on shares: 1
OSContainer::active_processor_count: 4
……
java version "1.8.0_191"
Java(TM) SE Runtime Environment (build 1.8.0_191-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.191-b12, mixed mode)

可以看到,獲取的記憶體限制、可用 CPU 數目都是對的了。

如何獲取容器資源配額呢?

結合這個日誌和程式碼,我們也可以看到如何獲取容器配額:

首先從 /proc/self/mounts 中讀取對應的資源的 mount 位置,比如 CPU 就是在 /sys/fs/cgroup/cpu,cpuacct :

$ cat /proc/mounts  | grep -E -w '(cpu|memory)'
cgroup /sys/fs/cgroup/memory cgroup ro,nosuid,nodev,noexec,relatime,memory 0 0
cgroup /sys/fs/cgroup/cpu,cpuacct cgroup ro,nosuid,nodev,noexec,relatime,cpu,cpuacct 0 0

對於記憶體:

$ cat /sys/fs/cgroup/memory/memory.limit_in_bytes
10737418240

對於 CPU 資源:

其一,可以透過 quota/period 來算:

$ cat /sys/fs/cgroup/cpu,cpuacct/cpu.cfs_period_us #單CPU總時間片配額,微秒 
100000
$ cat /sys/fs/cgroup/cpu,cpuacct/cpu.cfs_quota_us #時間片內,容器可佔用的CPU時間
400000

比如上面就表示分配了 4 核。

也可以透過 cpu.shares 來獲取:

$ cat /sys/fs/cgroup/cpu,cpuacct/cpu.shares
681

不過這個值是 CPU 佔用份額,無法根據這個算出來可用 CPU 數量,所以基本沒用…

總結

使用 jdk 8u191 或更新版本就完事了。

相關連結

[1] ParallelGCThreads:

https://docs.oracle.com/javas...

[2] jdk 8u191:

https://www.oracle.com/java/t...

相關文章