容器启动时–memory未生效,因docker默认cgroup v1而新系统多启用v2,需配置daemon.json启用cgroup v2支持;java应用oom因jvm不自动感知容器内存限制,须显式设置-xmx并开启-xx:+usecontainersupport。

容器启动时加 --memory 为什么没生效?
Docker 默认使用 cgroup v1,但很多新系统(如 ubuntu 22.04+、centos 8+)默认启用了 cgroup v2。如果内核启用了 cgroup v2,而 Docker daemon 没显式配置支持,--memory、--cpus 这类资源限制会被静默忽略——容器跑起来完全不受控,docker stats 显示的内存值也可能是宿主机总内存。
- 确认当前 cgroup 版本:
cat /proc/1/cgroup,如果路径含unified或第一行是0::/,说明是 v2 - 检查 Docker 是否启用 cgroup v2 支持:
docker info | grep "Cgroup Driver",输出应为systemd或cgroupfs,且版本需 ≥ 20.10 - 若不支持,需在
/etc/docker/daemon.json中添加:{"exec-opts": ["native.cgroupdriver=systemd"]},然后重启
dockerd
docker run --memory=512m 后,Java 应用还是 OOM
JVM 不会自动感知容器内存限制,它读的是宿主机的 /proc/meminfo。即使你设了 --memory=512m,JVM 默认可能按宿主机内存的 1/4 设堆(比如宿主机 64G → 堆 16G),远超容器限额,触发内核 OOM killer 杀进程。
- Java 8u191+ / Java 10+ 开始支持容器感知,但需显式开启:
-XX:+UseContainerSupport(默认已开,但别假设) - 强制指定堆上限,而不是靠比例:
-Xmx384m,且建议 ≤ 容器限制的 75%,留空间给元空间、直接内存等 - 验证 JVM 实际识别的内存:在容器内运行
java -XX:+PrintFlagsFinal -version | grep MaxHeapSize,看输出是否接近你设的-Xmx
限制 CPU 用 --cpus=1.5 和 --cpu-period/--cpu-quota 有什么区别?
--cpus=1.5 是 Docker 封装后的语义糖,底层仍转成 --cpu-period 和 --cpu-quota,但它的行为在不同内核版本下有差异:
-
内核 ≥ 4.13:
--cpus=1.5等价于--cpu-period=100000 --cpu-quota=150000,能真正实现“平均 1.5 核” -
内核 –cpu-shares(权重机制),无法硬限,高负载时可能吃到 2 核甚至更多
-
如果你在 kubernetes 里用
resources.limits.cpu: "1500m",底层也是走 period/quota,但 kubelet 会做归一化,和裸 Docker 表现未必一致 -
生产环境建议统一用
--cpu-period+--cpu-quota显式控制,避免内核版本导致行为漂移 -
注意:
--cpu-quota必须配合--cpu-period使用,单独设 quota 无效
容器内 top 看到的 CPU% 超过 100%,但 docker stats 显示正常
top 默认以单核为 100% 计算(即 4 核机器满载是 400%),而 docker stats 显示的是“实际占用的 CPU 时间占所有可用 CPU 时间的比例”,单位是百分比(100% = 一个完整 CPU 核的全部时间)。两者参照系不同。
-
docker stats的 CPU% 是 cgroup 的cpu.stat中usage_usec / total_usec计算而来,受--cpus限制直接影响 -
top的 %CPU 是采样周期内该进程在所有 CPU 上运行时间的总和占比,不感知容器限制 - 更可靠的监控方式是看
docker stats --no-stream <container></container>输出的CPU %列,或直接读容器 cgroup 路径:cat /sys/fs/cgroup/cpu,cpuacct/docker/<id>/cpu.stat</id>
cgroup 对资源的约束不是“闸门”,而是“配额调度器”,它不阻止进程申请资源,只决定是否给它执行机会。这点在调试延迟敏感型服务时最容易被忽略。