diff --git a/src/hotspot/os/linux/cgroupSubsystem_linux.cpp b/src/hotspot/os/linux/cgroupSubsystem_linux.cpp index fe1363b1cff..2a2de1e5a34 100644 --- a/src/hotspot/os/linux/cgroupSubsystem_linux.cpp +++ b/src/hotspot/os/linux/cgroupSubsystem_linux.cpp @@ -555,7 +555,30 @@ jlong CgroupSubsystem::memory_limit_in_bytes() { if (!memory_limit->should_check_metric()) { return memory_limit->value(); } + jlong phys_mem = os::Linux::physical_memory(); + log_trace(os, container)("total physical memory: " JLONG_FORMAT, phys_mem); jlong mem_limit = read_memory_limit_in_bytes(); + + if (mem_limit <= 0 || mem_limit >= phys_mem) { + jlong read_mem_limit = mem_limit; + const char *reason; + if (mem_limit >= phys_mem) { + // Exceeding physical memory is treated as unlimited. Cg v1's implementation + // of read_memory_limit_in_bytes() caps this at phys_mem since Cg v1 has no + // value to represent 'max'. Cg v2 may return a value >= phys_mem if e.g. the + // container engine was started with a memory flag exceeding it. + reason = "ignored"; + mem_limit = -1; + } else if (OSCONTAINER_ERROR == mem_limit) { + reason = "failed"; + } else { + assert(mem_limit == -1, "Expected unlimited"); + reason = "unlimited"; + } + log_debug(os, container)("container memory limit %s: " JLONG_FORMAT ", using host value " JLONG_FORMAT, + reason, read_mem_limit, phys_mem); + } + // Update cached metric to avoid re-reading container settings too often memory_limit->set_value(mem_limit, OSCONTAINER_CACHE_TIMEOUT); return mem_limit; diff --git a/src/hotspot/os/linux/cgroupV1Subsystem_linux.cpp b/src/hotspot/os/linux/cgroupV1Subsystem_linux.cpp index 7fad485dc48..17f54d7d0db 100644 --- a/src/hotspot/os/linux/cgroupV1Subsystem_linux.cpp +++ b/src/hotspot/os/linux/cgroupV1Subsystem_linux.cpp @@ -31,6 +31,7 @@ #include "runtime/globals.hpp" #include "runtime/os.hpp" #include "utilities/globalDefinitions.hpp" +#include "os_linux.hpp" /* * Set directory to subsystem specific files based @@ -105,7 +106,7 @@ jlong CgroupV1Subsystem::read_memory_limit_in_bytes() { GET_CONTAINER_INFO(julong, _memory->controller(), "/memory.limit_in_bytes", "Memory Limit is: " JULONG_FORMAT, JULONG_FORMAT, memlimit); - if (memlimit >= _unlimited_memory) { + if (memlimit >= os::Linux::physical_memory()) { log_trace(os, container)("Non-Hierarchical Memory Limit is: Unlimited"); CgroupV1MemoryController* mem_controller = reinterpret_cast(_memory->controller()); if (mem_controller->is_hierarchical()) { @@ -113,7 +114,7 @@ jlong CgroupV1Subsystem::read_memory_limit_in_bytes() { const char* format = "%s " JULONG_FORMAT; GET_CONTAINER_INFO_LINE(julong, _memory->controller(), "/memory.stat", matchline, "Hierarchical Memory Limit is: " JULONG_FORMAT, format, hier_memlimit) - if (hier_memlimit >= _unlimited_memory) { + if (hier_memlimit >= os::Linux::physical_memory()) { log_trace(os, container)("Hierarchical Memory Limit is: Unlimited"); } else { return (jlong)hier_memlimit; @@ -127,9 +128,11 @@ jlong CgroupV1Subsystem::read_memory_limit_in_bytes() { } jlong CgroupV1Subsystem::memory_and_swap_limit_in_bytes() { + julong host_total_memsw; GET_CONTAINER_INFO(julong, _memory->controller(), "/memory.memsw.limit_in_bytes", "Memory and Swap Limit is: " JULONG_FORMAT, JULONG_FORMAT, memswlimit); - if (memswlimit >= _unlimited_memory) { + host_total_memsw = os::Linux::host_swap() + os::Linux::physical_memory(); + if (memswlimit >= host_total_memsw) { log_trace(os, container)("Non-Hierarchical Memory and Swap Limit is: Unlimited"); CgroupV1MemoryController* mem_controller = reinterpret_cast(_memory->controller()); if (mem_controller->is_hierarchical()) { @@ -137,7 +140,7 @@ jlong CgroupV1Subsystem::memory_and_swap_limit_in_bytes() { const char* format = "%s " JULONG_FORMAT; GET_CONTAINER_INFO_LINE(julong, _memory->controller(), "/memory.stat", matchline, "Hierarchical Memory and Swap Limit is : " JULONG_FORMAT, format, hier_memswlimit) - if (hier_memswlimit >= _unlimited_memory) { + if (hier_memswlimit >= host_total_memsw) { log_trace(os, container)("Hierarchical Memory and Swap Limit is: Unlimited"); } else { jlong swappiness = read_mem_swappiness(); @@ -172,7 +175,7 @@ jlong CgroupV1Subsystem::read_mem_swappiness() { jlong CgroupV1Subsystem::memory_soft_limit_in_bytes() { GET_CONTAINER_INFO(julong, _memory->controller(), "/memory.soft_limit_in_bytes", "Memory Soft Limit is: " JULONG_FORMAT, JULONG_FORMAT, memsoftlimit); - if (memsoftlimit >= _unlimited_memory) { + if (memsoftlimit >= os::Linux::physical_memory()) { log_trace(os, container)("Memory Soft Limit is: Unlimited"); return (jlong)-1; } else { diff --git a/src/hotspot/os/linux/cgroupV1Subsystem_linux.hpp b/src/hotspot/os/linux/cgroupV1Subsystem_linux.hpp index a949f6c4ea1..ce92deb0084 100644 --- a/src/hotspot/os/linux/cgroupV1Subsystem_linux.hpp +++ b/src/hotspot/os/linux/cgroupV1Subsystem_linux.hpp @@ -97,8 +97,6 @@ class CgroupV1Subsystem: public CgroupSubsystem { CachingCgroupController * cpu_controller() { return _cpu; } private: - julong _unlimited_memory; - /* controllers */ CachingCgroupController* _memory = NULL; CgroupV1Controller* _cpuset = NULL; @@ -121,7 +119,6 @@ class CgroupV1Subsystem: public CgroupSubsystem { _cpuacct = cpuacct; _pids = pids; _memory = new CachingCgroupController(memory); - _unlimited_memory = (LONG_MAX / os::vm_page_size()) * os::vm_page_size(); } }; diff --git a/src/hotspot/os/linux/osContainer_linux.cpp b/src/hotspot/os/linux/osContainer_linux.cpp index 6d887c00e21..46fc76c27b1 100644 --- a/src/hotspot/os/linux/osContainer_linux.cpp +++ b/src/hotspot/os/linux/osContainer_linux.cpp @@ -42,8 +42,6 @@ CgroupSubsystem* cgroup_subsystem; * we are running under cgroup control. */ void OSContainer::init() { - jlong mem_limit; - assert(!_is_initialized, "Initializing OSContainer more than once"); _is_initialized = true; @@ -59,15 +57,8 @@ void OSContainer::init() { if (cgroup_subsystem == NULL) { return; // Required subsystem files not found or other error } - // We need to update the amount of physical memory now that - // cgroup subsystem files have been processed. - if ((mem_limit = cgroup_subsystem->memory_limit_in_bytes()) > 0) { - os::Linux::set_physical_memory(mem_limit); - log_info(os, container)("Memory Limit is: " JLONG_FORMAT, mem_limit); - } _is_containerized = true; - } const char * OSContainer::container_type() { diff --git a/src/hotspot/os/linux/os_linux.cpp b/src/hotspot/os/linux/os_linux.cpp index f073aa63a05..8a53e16f348 100644 --- a/src/hotspot/os/linux/os_linux.cpp +++ b/src/hotspot/os/linux/os_linux.cpp @@ -209,15 +209,12 @@ julong os::Linux::available_memory() { julong avail_mem; if (OSContainer::is_containerized()) { - jlong mem_limit, mem_usage; - if ((mem_limit = OSContainer::memory_limit_in_bytes()) < 1) { - log_debug(os, container)("container memory limit %s: " JLONG_FORMAT ", using host value", - mem_limit == OSCONTAINER_ERROR ? "failed" : "unlimited", mem_limit); - } + jlong mem_limit = OSContainer::memory_limit_in_bytes(); + jlong mem_usage; if (mem_limit > 0 && (mem_usage = OSContainer::memory_usage_in_bytes()) < 1) { log_debug(os, container)("container memory usage failed: " JLONG_FORMAT ", using host value", mem_usage); } - if (mem_limit > 0 && mem_usage > 0 ) { + if (mem_limit > 0 && mem_usage > 0) { avail_mem = mem_limit > mem_usage ? (julong)mem_limit - (julong)mem_usage : 0; log_trace(os)("available container memory: " JULONG_FORMAT, avail_mem); return avail_mem; @@ -238,8 +235,6 @@ julong os::physical_memory() { log_trace(os)("total container memory: " JLONG_FORMAT, mem_limit); return mem_limit; } - log_debug(os, container)("container memory limit %s: " JLONG_FORMAT ", using host value", - mem_limit == OSCONTAINER_ERROR ? "failed" : "unlimited", mem_limit); } phys_mem = Linux::physical_memory(); @@ -368,6 +363,14 @@ pid_t os::Linux::gettid() { return (pid_t)rslt; } +// Returns the amount of swap currently configured, in bytes. +// This can change at any time. +julong os::Linux::host_swap() { + struct sysinfo si; + sysinfo(&si); + return (julong)si.totalswap; +} + // Most versions of linux have a bug where the number of processors are // determined by looking at the /proc file system. In a chroot environment, // the system call returns 1. This causes the VM to act as if it is diff --git a/src/hotspot/os/linux/os_linux.hpp b/src/hotspot/os/linux/os_linux.hpp index 62150144f2c..e9e7eaea4e1 100644 --- a/src/hotspot/os/linux/os_linux.hpp +++ b/src/hotspot/os/linux/os_linux.hpp @@ -74,8 +74,6 @@ class Linux { static int _page_size; static julong available_memory(); - static julong physical_memory() { return _physical_memory; } - static void set_physical_memory(julong phys_mem) { _physical_memory = phys_mem; } static int active_processor_count(); static void initialize_system_info(); @@ -152,6 +150,10 @@ class Linux { static address ucontext_get_pc(const ucontext_t* uc); static void ucontext_set_pc(ucontext_t* uc, address pc); + + static julong physical_memory() { return _physical_memory; } + static julong host_swap(); + static intptr_t* ucontext_get_sp(const ucontext_t* uc); static intptr_t* ucontext_get_fp(const ucontext_t* uc); diff --git a/test/hotspot/jtreg/containers/docker/TestMemoryAwareness.java b/test/hotspot/jtreg/containers/docker/TestMemoryAwareness.java index 8c70c9608d6..0243c56a0ab 100644 --- a/test/hotspot/jtreg/containers/docker/TestMemoryAwareness.java +++ b/test/hotspot/jtreg/containers/docker/TestMemoryAwareness.java @@ -24,6 +24,7 @@ /* * @test + * @bug 8146115 8292083 * @key cgroups * @summary Test JVM's memory resource awareness when running inside docker container * @requires docker.support @@ -41,6 +42,8 @@ import jdk.test.lib.containers.docker.DockerTestUtils; import jdk.test.lib.process.OutputAnalyzer; +import static jdk.test.lib.Asserts.assertNotNull; + public class TestMemoryAwareness { private static final String imageName = Common.imageName("memory"); @@ -76,6 +79,7 @@ public static void main(String[] args) throws Exception { "1G", Integer.toString(((int) Math.pow(2, 20)) * 1024), "1500M", Integer.toString(((int) Math.pow(2, 20)) * (1500 - 1024)) ); + testContainerMemExceedsPhysical(); } finally { if (!DockerTestUtils.RETAIN_IMAGE_AFTER_TEST) { DockerTestUtils.removeDockerImage(imageName); @@ -96,6 +100,28 @@ private static void testMemoryLimit(String valueToSet, String expectedTraceValue .shouldMatch("Memory Limit is:.*" + expectedTraceValue); } + // JDK-8292083 + // Ensure that Java ignores container memory limit values above the host's physical memory. + private static void testContainerMemExceedsPhysical() + throws Exception { + + Common.logNewTestCase("container memory limit exceeds physical memory"); + + DockerRunOptions opts = Common.newOpts(imageName); + + // first run: establish physical memory in test environment and derive + // a bad value one power of ten larger + String goodMem = Common.run(opts).firstMatch("total physical memory: (\\d+)", 1); + assertNotNull(goodMem, "no match for 'total physical memory' in trace output"); + String badMem = goodMem + "0"; + + // second run: set a container memory limit to the bad value + opts = Common.newOpts(imageName) + .addDockerOpts("--memory", badMem); + Common.run(opts) + .shouldMatch("container memory limit (ignored: " + badMem + "|unlimited: -1), using host value " + goodMem); + } + private static void testMemorySoftLimit(String valueToSet, String expectedTraceValue) throws Exception {