Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
8292083: Detected container memory limit may exceed physical machine …
…memory

Reviewed-by: sgehwolf
Backport-of: f694f8a7671002559e7d23fdb65d5e9c768f9c03
  • Loading branch information
Jonathan Dowland committed Dec 19, 2022
1 parent 78cdc41 commit b4ef3d3
Show file tree
Hide file tree
Showing 7 changed files with 74 additions and 36 deletions.
27 changes: 27 additions & 0 deletions hotspot/src/os/linux/vm/cgroupSubsystem_linux.cpp
Expand Up @@ -543,7 +543,34 @@ jlong CgroupSubsystem::memory_limit_in_bytes() {
if (!memory_limit->should_check_metric()) {
return memory_limit->value();
}
jlong phys_mem = os::Linux::physical_memory();
if (PrintContainerInfo) {
tty->print_cr("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";
}
if (PrintContainerInfo) {
tty->print_cr("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;
Expand Down
13 changes: 8 additions & 5 deletions hotspot/src/os/linux/vm/cgroupV1Subsystem_linux.cpp
Expand Up @@ -30,6 +30,7 @@
#include "runtime/globals.hpp"
#include "runtime/os.hpp"
#include "utilities/globalDefinitions.hpp"
#include "os_linux.hpp"

/*
* Set directory to subsystem specific files based
Expand Down Expand Up @@ -104,7 +105,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()) {
if (PrintContainerInfo) {
tty->print_cr("Non-Hierarchical Memory Limit is: Unlimited");
}
Expand All @@ -114,7 +115,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()) {
if (PrintContainerInfo) {
tty->print_cr("Hierarchical Memory Limit is: Unlimited");
}
Expand All @@ -130,9 +131,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) {
if (PrintContainerInfo) {
tty->print_cr("Non-Hierarchical Memory and Swap Limit is: Unlimited");
}
Expand All @@ -142,7 +145,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_memlimit)
if (hier_memlimit >= _unlimited_memory) {
if (hier_memlimit >= host_total_memsw) {
if (PrintContainerInfo) {
tty->print_cr("Hierarchical Memory and Swap Limit is: Unlimited");
}
Expand All @@ -159,7 +162,7 @@ jlong CgroupV1Subsystem::memory_and_swap_limit_in_bytes() {
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()) {
if (PrintContainerInfo) {
tty->print_cr("Memory Soft Limit is: Unlimited");
}
Expand Down
3 changes: 0 additions & 3 deletions hotspot/src/os/linux/vm/cgroupV1Subsystem_linux.hpp
Expand Up @@ -94,8 +94,6 @@ class CgroupV1Subsystem: public CgroupSubsystem {
CachingCgroupController * cpu_controller() { return _cpu; }

private:
julong _unlimited_memory;

/* controllers */
CachingCgroupController* _memory;
CgroupV1Controller* _cpuset;
Expand All @@ -111,7 +109,6 @@ class CgroupV1Subsystem: public CgroupSubsystem {
_cpu = new CachingCgroupController(cpu);
_cpuacct = cpuacct;
_memory = new CachingCgroupController(memory);
_unlimited_memory = (LONG_MAX / os::vm_page_size()) * os::vm_page_size();
}
};

Expand Down
11 changes: 0 additions & 11 deletions hotspot/src/os/linux/vm/osContainer_linux.cpp
Expand Up @@ -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;
Expand All @@ -63,17 +61,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);
if (PrintContainerInfo) {
tty->print_cr("Memory Limit is: " JLONG_FORMAT, mem_limit);
}
}

_is_containerized = true;

}

const char * OSContainer::container_type() {
Expand Down
26 changes: 11 additions & 15 deletions hotspot/src/os/linux/vm/os_linux.cpp
Expand Up @@ -184,21 +184,14 @@ 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) {
if (PrintContainerInfo) {
tty->print_cr("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) {
if (PrintContainerInfo) {
tty->print_cr("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;
if (PrintContainerInfo) {
tty->print_cr("available container memory: " JULONG_FORMAT, avail_mem);
Expand All @@ -225,11 +218,6 @@ julong os::physical_memory() {
}
return mem_limit;
}

if (PrintContainerInfo) {
tty->print_cr("container memory limit %s: " JLONG_FORMAT ", using host value",
mem_limit == OSCONTAINER_ERROR ? "failed" : "unlimited", mem_limit);
}
}

phys_mem = Linux::physical_memory();
Expand Down Expand Up @@ -308,6 +296,14 @@ pid_t os::Linux::gettid() {
}
}

// 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
Expand Down
5 changes: 3 additions & 2 deletions hotspot/src/os/linux/vm/os_linux.hpp
Expand Up @@ -81,8 +81,6 @@ class Linux {
static const int _vm_default_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();
Expand Down Expand Up @@ -154,6 +152,9 @@ class Linux {
static intptr_t* ucontext_get_sp(ucontext_t* uc);
static intptr_t* ucontext_get_fp(ucontext_t* uc);

static julong physical_memory() { return _physical_memory; }
static julong host_swap();

// For Analyzer Forte AsyncGetCallTrace profiling support:
//
// This interface should be declared in os_linux_i486.hpp, but
Expand Down
25 changes: 25 additions & 0 deletions hotspot/test/runtime/containers/docker/TestMemoryAwareness.java
Expand Up @@ -24,6 +24,7 @@

/*
* @test
* @bug 8146115 8292083
* @summary Test JVM's memory resource awareness when running inside docker container
* @library /testlibrary /testlibrary/whitebox
* @build AttemptOOM sun.hotspot.WhiteBox PrintContainerInfo CheckOperatingSystemMXBean
Expand All @@ -36,6 +37,7 @@
import com.oracle.java.testlibrary.DockerTestUtils;
import com.oracle.java.testlibrary.OutputAnalyzer;

import com.oracle.java.testlibrary.Asserts;

public class TestMemoryAwareness {
private static final String imageName = Common.imageName("memory");
Expand Down Expand Up @@ -72,6 +74,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 {
DockerTestUtils.removeDockerImage(imageName);
}
Expand All @@ -90,6 +93,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);
Asserts.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 {
Expand Down

1 comment on commit b4ef3d3

@openjdk-notifier
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.