使用 -XX:MaxRAMPercentage
能够根据物理内存计算出最大堆大小,在容器服务等场景下相比 -Xmx
更易用。在 JDK 8 中计算相关源码位于:jdk8u/arguments.cpp at jdk8u352-b05 · openjdk/jdk8u · GitHub:
1 | void Arguments::set_heap_size() { |
需要注意的是 JDK 8 中的实现是基于 cgroup v1 的,如果内核使用的是 cgroup v2,则容器内存探测不会生效,当前使用的 cgroup 版本可以参考 Runtime metrics | Docker Documentation。关于 JDK 8 中基于 cgroup v2 的容器感知,可以参考 JDK-8230305 Cgroups v2: Container awareness - Java Bug System,可知,该 bug 在 JDK 15 中得到修复,并反向移植至了 JDK 11,但是 JDK 8 中并未进行反向移植,所以当使用 JDK 8 且使用了 -XX:MaxRAMPercentage
选项时,需要保证操作系统使用的 cgroup 版本为 v1,否则计算出的最大堆内存是基于物理机的内存,而不是对容器限制的内存。
我们可以用一台 Ubuntu 22.04 LTS 的机器来进行验证,首先确认物理机操作系统,执行 lsb_release -a
命令输出如下:
1 | No LSB modules are available. |
执行 uname -a
确认内核版本如下:
1 | Linux VM-0-16-ubuntu 5.15.0-40-generic #43-Ubuntu SMP Wed Jun 15 12:54:21 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux |
执行 free -h
确认物理机内存如下:
1 | total used free shared buff/cache available |
可知物理机内存为 3.3G,我们再执行 ll /sys/fs/cgroup/cgroup.controllers
可确认该文件存在,表明当前操作系统使用的 cgroup 版本为 v2。
我们首先执行 sudo docker run -m 1GB openjdk:11 java -XX:MaxRAMPercentage=80.0 -XshowSettings:vm -version
验证 JDK 11 中对 cgroup v2 的支持,输出如下:
1 | VM settings: |
可知在容器中使用 JDK 11 时 JVM 实例的最大内存为我们设置的 1GB 的 80% 左右,说明探测生效。
我们再执行 sudo docker run -m 1GB openjdk:8 java -XX:MaxRAMPercentage=80.0 -XshowSettings:vm -version
验证 JDK 8 中对 cgroup v2 的支持,输出如下:
1 | VM settings: |
可知在容器中使用 JDK 8 时 JVM 实例的最大内存为 2.58G,并非我们指定的容器内存上限 1G 的 80% 左右,而是物理内存 3.3G 的 80% 左右,说明探测并未生效,此时读取到了物理机的内存。所以,在 JDK 8 下使用 -XX:MaxRAMPercentage
选项时需要注意操作系统使用的 cgroup 版本一定为 v1,否则最大堆内存限制不能按预期工作。
Reference
JVM Parameters InitialRAMPercentage, MinRAMPercentage, and MaxRAMPercentage | Baeldung
JDK-8146115 Improve docker container detection and resource configuration usage - Java Bug System
8146115: Improve docker container detection and resource configuratio… · openjdk/jdk8u@392e56e · GitHub
/sys/fs/cgroup/memory/memory.limit_in_bytes is missing in the container in version > 4.2.0 · Issue #6118 · docker/for-mac · GitHub