给出一个通过mmap'd文件创建大型Linux内核页面缓存的进程,该进程在具有内存限制的docker容器(cgroup)中运行会导致内核slab分配错误:
Jul 18 21:29:01 ip-10-10-17-135 kernel: [186998.252395] SLUB: Unable to allocate memory on node -1 (gfp=0x2080020)
Jul 18 21:29:01 ip-10-10-17-135 kernel: [186998.252402] cache: kmalloc-2048(2412:6c2c4ef2026a77599d279450517cb061545fa963ff9faab731daab2a1f672915), object size: 2048, buffer size: 2048, default order: 3, min order: 0
Jul 18 21:29:01 ip-10-10-17-135 kernel: [186998.252407] node 0: slabs: 135, objs: 1950, free: 64
Jul 18 21:29:01 ip-10-10-17-135 kernel: [186998.252409] node 1: slabs: 130, objs: 1716, free: 0
观看slabtop
可以看到在以内存限制开始的容器中,buffer_head,radix_tree_node和kmalloc *对象的数量受到严格限制。这似乎会对应用程序中的IO吞吐量产生病理影响,并且可以通过iostat
进行观察。即使页面缓存消耗了运行在容器外部或没有内存限制的容器之外的主机OS上的所有可用内存,也不会发生这种情况。
这似乎是内核内存记帐中的一个问题,其中内核页面高速缓存不计入容器内存中,但支持它的SLAB对象却计入容器内存中。该行为似乎是异常的,因为在预先分配了一个大的平板对象池时运行,内存受限的容器运行良好,可以自由地重用现有的平板空间。只有在容器中分配的平板才计入容器。内存和内核内存的容器选项的组合似乎无法解决此问题(除非根本不设置内存限制,或者设置的限制太大以至于不能限制平板,但这会限制可寻址空间)。我试图通过在启动时通过cgroup.memory=nokmem
来完全禁用kmem计费,但没有成功。
系统信息:
要重现该问题,可以使用我的PageCache Java代码。这是embedded database library的基本实例,它极大地利用了要在非常快速的文件系统上部署的内存映射文件。该应用程序通过ECS部署在AWS i3.baremetal实例上。我正在将主机上的大容量映射到存储内存映射文件的Docker容器。 AWS ECS代理要求为所有容器设置非零内存限制。内存限制会导致病理性平板行为,并且由此产生的应用程序IO吞吐量完全不可接受。
使用drop_caches
在两次运行之间进行echo 3 > /proc/sys/vm/drop_caches
很有帮助。这将清除页面缓存和关联的平板对象池。
建议如何解决,解决此问题或什至在哪里报告此问题,这些建议。
答案 0 :(得分:3)
java 10.0.1 2018-04-17
您应该尝试使用Java 10(或11或...)的最新版本
我在去年5月(2019年)的“ Docker support in Java 8 — finally!”中提到,从Java 10的新版本(移植到Java 8)意味着Docker将更准确地报告所使用的内存。
成功!没有提供任何标志,Java 10(10u46-Nightly)可以正确检测到Dockers的内存限制。
OP David在the comments中确认:
docker-jvm集成是Java 10的一项重大改进。
确实与设置合理的XMS和处理器数量有关。现在,它们遵循docker容器限制,而不是选择主机实例值(您可以根据用例使用-XX:-UseContainerSupport
来关闭此功能)。尽管如此,我仍发现它对处理页面缓存没有帮助。
我发现最好的解决方案是在创建容器后禁用docker内存限制。
这绝对是黑客-用户要当心。