From 9ac0d0589bc374723683060fa1ea2646c1d53b19 Mon Sep 17 00:00:00 2001
Date: Thu, 27 Nov 2025 20:16:06 +0800
Subject: [PATCH 2/6] Add NUMARandom feature
make/data/hotspot-symbols/symbols-shared | 1 +
src/hotspot/cpu/aarch64/globals_aarch64.hpp | 12 +
src/hotspot/os/linux/os_linux.cpp | 80 +++-
src/hotspot/os/linux/os_linux.hpp | 130 ++++++-
.../os_cpu/linux_aarch64/os_linux_aarch64.cpp | 368 ++++++++++++++++++
src/hotspot/share/prims/jni.cpp | 6 +
src/java.base/share/native/include/jni.h | 3 +
src/java.base/share/native/libjli/java.c | 2 +
src/java.base/share/native/libjli/java.h | 3 +
src/java.base/unix/native/libjli/java_md.c | 6 +
.../docker/TestNUMAErrorHandling.java | 113 ++++++
.../containers/docker/TestNUMANodes.java | 177 +++++++++
.../docker/TestNUMANodesRandom.java | 203 ++++++++++
13 files changed, 1090 insertions(+), 14 deletions(-)
create mode 100644 test/hotspot/jtreg/containers/docker/TestNUMAErrorHandling.java
create mode 100644 test/hotspot/jtreg/containers/docker/TestNUMANodes.java
create mode 100644 test/hotspot/jtreg/containers/docker/TestNUMANodesRandom.java
@@ -29,6 +29,7 @@ jio_vfprintf
jio_vsnprintf
JNI_CreateJavaVM
JNI_GetCreatedJavaVMs
+JNI_SetCParam
JNI_GetDefaultJavaVMInitArgs
JVM_IsForeignLinkerSupported
JVM_FindClassFromBootLoader
@@ -148,6 +148,18 @@ define_pd_global(intx, InlineSmallCode, 1000);
product(uintx, ElasticMaxHeapShrinkMinFreeRatio, 40, \
"minimal ratio of free bytes after elastic max heap shirnk") \
\
+ product(bool, LogNUMANodes, false, \
+ "Print NUMANodes") \
+ \
+ product(ccstr, NUMANodes, NULL, \
+ "This parameter provides the same functionality as" \
+ "'numactl --all -N <nodes> -m <nodes>'." \
+ "<nodes> can be '0-2', '0,1,2', 'all' and so on.") \
+ \
+ product(uintx, NUMANodesRandom, 0, \
+ "Number of continuous nodes to bind" \
+ "with the first node randomly chosen." \
+ "NUMANodesRandom has higher priority than NUMANodes") \
// end of ARCH_FLAGS
@@ -3212,6 +3212,10 @@ bool os::Linux::libnuma_init() {
libnuma_dlsym(handle, "numa_node_to_cpus")));
set_numa_node_to_cpus_v2(CAST_TO_FN_PTR(numa_node_to_cpus_v2_func_t,
libnuma_v2_dlsym(handle, "numa_node_to_cpus")));
+ set_numa_node_of_cpu(CAST_TO_FN_PTR(numa_node_of_cpu_func_t,
+ libnuma_dlsym(handle, "numa_node_of_cpu")));
+ set_numa_num_configured_cpus(CAST_TO_FN_PTR(numa_num_configured_cpus_func_t,
+ libnuma_dlsym(handle, "numa_num_configured_cpus")));
set_numa_max_node(CAST_TO_FN_PTR(numa_max_node_func_t,
libnuma_dlsym(handle, "numa_max_node")));
set_numa_num_configured_nodes(CAST_TO_FN_PTR(numa_num_configured_nodes_func_t,
@@ -3223,7 +3227,7 @@ bool os::Linux::libnuma_init() {
set_numa_interleave_memory(CAST_TO_FN_PTR(numa_interleave_memory_func_t,
libnuma_dlsym(handle, "numa_interleave_memory")));
set_numa_interleave_memory_v2(CAST_TO_FN_PTR(numa_interleave_memory_v2_func_t,
- libnuma_v2_dlsym(handle, "numa_interleave_memory")));
+ libnuma_v2_dlsym(handle, "numa_interleave_memory")));
set_numa_set_bind_policy(CAST_TO_FN_PTR(numa_set_bind_policy_func_t,
libnuma_dlsym(handle, "numa_set_bind_policy")));
set_numa_bitmask_isbitset(CAST_TO_FN_PTR(numa_bitmask_isbitset_func_t,
@@ -3232,12 +3236,38 @@ bool os::Linux::libnuma_init() {
libnuma_dlsym(handle, "numa_distance")));
set_numa_get_membind(CAST_TO_FN_PTR(numa_get_membind_func_t,
libnuma_v2_dlsym(handle, "numa_get_membind")));
+ set_numa_get_mems_allowed(CAST_TO_FN_PTR(numa_get_mems_allowed_func_t,
+ libnuma_dlsym(handle, "numa_get_mems_allowed")));
+ set_numa_allocate_cpumask(CAST_TO_FN_PTR(numa_allocate_cpumask_func_t,
+ libnuma_dlsym(handle, "numa_allocate_cpumask")));
+ set_numa_allocate_nodemask(CAST_TO_FN_PTR(numa_allocate_nodemask_func_t,
+ libnuma_dlsym(handle, "numa_allocate_nodemask")));
+ set_numa_sched_setaffinity(CAST_TO_FN_PTR(numa_sched_setaffinity_func_t,
+ libnuma_dlsym(handle, "numa_sched_setaffinity")));
+ set_numa_bitmask_nbytes(CAST_TO_FN_PTR(numa_bitmask_nbytes_func_t,
+ libnuma_dlsym(handle, "numa_bitmask_nbytes")));
+ set_numa_bitmask_setbit(CAST_TO_FN_PTR(numa_bitmask_setbit_func_t,
+ libnuma_dlsym(handle, "numa_bitmask_setbit")));
+ set_numa_bitmask_clearall(CAST_TO_FN_PTR(numa_bitmask_clearall_func_t,
+ libnuma_dlsym(handle, "numa_bitmask_clearall")));
set_numa_get_interleave_mask(CAST_TO_FN_PTR(numa_get_interleave_mask_func_t,
libnuma_v2_dlsym(handle, "numa_get_interleave_mask")));
set_numa_move_pages(CAST_TO_FN_PTR(numa_move_pages_func_t,
libnuma_dlsym(handle, "numa_move_pages")));
set_numa_set_preferred(CAST_TO_FN_PTR(numa_set_preferred_func_t,
libnuma_dlsym(handle, "numa_set_preferred")));
+ set_numa_parse_nodestring_all(CAST_TO_FN_PTR(numa_parse_nodestring_all_func_t,
+ libnuma_dlsym(handle, "numa_parse_nodestring_all")));
+ set_numa_run_on_node_mask(CAST_TO_FN_PTR(numa_run_on_node_mask_func_t,
+ libnuma_v2_dlsym(handle, "numa_run_on_node_mask")));
+ set_numa_bitmask_equal(CAST_TO_FN_PTR(numa_bitmask_equal_func_t,
+ libnuma_v2_dlsym(handle, "numa_bitmask_equal")));
+ set_numa_set_membind(CAST_TO_FN_PTR(numa_set_membind_func_t,
+ libnuma_v2_dlsym(handle, "numa_set_membind")));
+ set_numa_bitmask_free(CAST_TO_FN_PTR(numa_bitmask_free_func_t,
+ libnuma_dlsym(handle, "numa_bitmask_free")));
+ set_numa_get_run_node_mask(CAST_TO_FN_PTR(numa_get_run_node_mask_func_t,
+ libnuma_v2_dlsym(handle, "numa_get_run_node_mask")));
if (numa_available() != -1) {
set_numa_all_nodes((unsigned long*)libnuma_dlsym(handle, "numa_all_nodes"));
@@ -3245,6 +3275,8 @@ bool os::Linux::libnuma_init() {
set_numa_nodes_ptr((struct bitmask **)libnuma_dlsym(handle, "numa_nodes_ptr"));
set_numa_interleave_bitmask(_numa_get_interleave_mask());
set_numa_membind_bitmask(_numa_get_membind());
+ set_numa_cpunodebind_bitmask(_numa_get_run_node_mask());
+ AARCH64_ONLY(chose_numa_nodes();)
// Create an index -> node mapping, since nodes are not always consecutive
_nindex_to_node = new (mtInternal) GrowableArray<int>(0, mtInternal);
rebuild_nindex_to_node_map();
@@ -3413,6 +3445,8 @@ GrowableArray<int>* os::Linux::_nindex_to_node;
os::Linux::sched_getcpu_func_t os::Linux::_sched_getcpu;
os::Linux::numa_node_to_cpus_func_t os::Linux::_numa_node_to_cpus;
os::Linux::numa_node_to_cpus_v2_func_t os::Linux::_numa_node_to_cpus_v2;
+os::Linux::numa_node_of_cpu_func_t os::Linux::_numa_node_of_cpu;
+os::Linux::numa_num_configured_cpus_func_t os::Linux::_numa_num_configured_cpus;
os::Linux::numa_max_node_func_t os::Linux::_numa_max_node;
os::Linux::numa_num_configured_nodes_func_t os::Linux::_numa_num_configured_nodes;
os::Linux::numa_available_func_t os::Linux::_numa_available;
@@ -3423,15 +3457,29 @@ os::Linux::numa_set_bind_policy_func_t os::Linux::_numa_set_bind_policy;
os::Linux::numa_bitmask_isbitset_func_t os::Linux::_numa_bitmask_isbitset;
os::Linux::numa_distance_func_t os::Linux::_numa_distance;
os::Linux::numa_get_membind_func_t os::Linux::_numa_get_membind;
+os::Linux::numa_get_mems_allowed_func_t os::Linux::_numa_get_mems_allowed;
+os::Linux::numa_allocate_cpumask_func_t os::Linux::_numa_allocate_cpumask;
+os::Linux::numa_allocate_nodemask_func_t os::Linux::_numa_allocate_nodemask;
+os::Linux::numa_sched_setaffinity_func_t os::Linux::_numa_sched_setaffinity;
+os::Linux::numa_bitmask_nbytes_func_t os::Linux::_numa_bitmask_nbytes;
+os::Linux::numa_bitmask_setbit_func_t os::Linux::_numa_bitmask_setbit;
+os::Linux::numa_bitmask_clearall_func_t os::Linux::_numa_bitmask_clearall;
os::Linux::numa_get_interleave_mask_func_t os::Linux::_numa_get_interleave_mask;
os::Linux::numa_move_pages_func_t os::Linux::_numa_move_pages;
os::Linux::numa_set_preferred_func_t os::Linux::_numa_set_preferred;
os::Linux::NumaAllocationPolicy os::Linux::_current_numa_policy;
+os::Linux::numa_parse_nodestring_all_func_t os::Linux::_numa_parse_nodestring_all;
+os::Linux::numa_run_on_node_mask_func_t os::Linux::_numa_run_on_node_mask;
+os::Linux::numa_bitmask_equal_func_t os::Linux::_numa_bitmask_equal;
+os::Linux::numa_set_membind_func_t os::Linux::_numa_set_membind;
+os::Linux::numa_bitmask_free_func_t os::Linux::_numa_bitmask_free;
+os::Linux::numa_get_run_node_mask_func_t os::Linux::_numa_get_run_node_mask;
unsigned long* os::Linux::_numa_all_nodes;
struct bitmask* os::Linux::_numa_all_nodes_ptr;
struct bitmask* os::Linux::_numa_nodes_ptr;
struct bitmask* os::Linux::_numa_interleave_bitmask;
struct bitmask* os::Linux::_numa_membind_bitmask;
+struct bitmask* os::Linux::_numa_cpunodebind_bitmask;
bool os::pd_uncommit_memory(char* addr, size_t size, bool exec) {
uintptr_t res = (uintptr_t) ::mmap(addr, size, PROT_NONE,
@@ -4648,19 +4696,19 @@ void os::Linux::numa_init() {
// bitmask when externally configured to run on all or fewer nodes.
if (!Linux::libnuma_init()) {
- FLAG_SET_ERGO(UseNUMA, false);
- FLAG_SET_ERGO(UseNUMAInterleaving, false); // Also depends on libnuma.
+ disable_numa("Failed to initialize libnuma", true);
} else {
- if ((Linux::numa_max_node() < 1) || Linux::is_bound_to_single_node()) {
- // If there's only one node (they start from 0) or if the process
- // is bound explicitly to a single node using membind, disable NUMA
- UseNUMA = false;
+ Linux::set_configured_numa_policy(Linux::identify_numa_policy());
+ if (Linux::numa_max_node() < 1) {
+ disable_numa("Only a single NUMA node is available", false);
+ } else if (Linux::is_bound_to_single_mem_node()) {
+ disable_numa("The process is bound to a single NUMA node", true);
+ } else if (Linux::mem_and_cpu_node_mismatch()) {
+ disable_numa("The process memory and cpu node configuration does not match", true);
} else {
LogTarget(Info,os) log;
LogStream ls(log);
- Linux::set_configured_numa_policy(Linux::identify_numa_policy());
-
struct bitmask* bmp = Linux::_numa_membind_bitmask;
const char* numa_mode = "membind";
@@ -4770,6 +4818,20 @@ void os::Linux::load_ACC_library() {
BOOL_TO_STR(_jboltMerge_judge != nullptr));)
}
+void os::Linux::disable_numa(const char* reason, bool warning) {
+ if ((UseNUMA && FLAG_IS_CMDLINE(UseNUMA)) ||
+ (UseNUMAInterleaving && FLAG_IS_CMDLINE(UseNUMAInterleaving))) {
+ // Only issue a message if the user explicitly asked for NUMA support
+ if (warning) {
+ log_warning(os)("NUMA support disabled: %s", reason);
+ } else {
+ log_info(os)("NUMA support disabled: %s", reason);
+ }
+ }
+ FLAG_SET_ERGO(UseNUMA, false);
+ FLAG_SET_ERGO(UseNUMAInterleaving, false);
+}
+
#if defined(IA32) && !defined(ZERO)
/*
* Work-around (execute code at a high address) for broken NX emulation using CS limit,
@@ -140,6 +140,7 @@ class os::Linux {
static void load_ACC_library();
static void libpthread_init();
static void sched_getcpu_init();
+ static void chose_numa_nodes();
static bool libnuma_init();
static void* libnuma_dlsym(void* handle, const char* name);
// libnuma v2 (libnuma_1.2) symbols
@@ -197,9 +198,12 @@ class os::Linux {
private:
static void numa_init();
+ static void disable_numa(const char* reason, bool warning);
typedef int (*sched_getcpu_func_t)(void);
typedef int (*numa_node_to_cpus_func_t)(int node, unsigned long *buffer, int bufferlen);
typedef int (*numa_node_to_cpus_v2_func_t)(int node, void *mask);
+ typedef int (*numa_node_of_cpu_func_t)(int cpu);
+ typedef int (*numa_num_configured_cpus_func_t)(void);
typedef int (*numa_max_node_func_t)(void);
typedef int (*numa_num_configured_nodes_func_t)(void);
typedef int (*numa_available_func_t)(void);
@@ -207,9 +211,22 @@ class os::Linux {
typedef void (*numa_interleave_memory_func_t)(void *start, size_t size, unsigned long *nodemask);
typedef void (*numa_interleave_memory_v2_func_t)(void *start, size_t size, struct bitmask* mask);
typedef struct bitmask* (*numa_get_membind_func_t)(void);
+ typedef struct bitmask* (*numa_get_mems_allowed_func_t)(void);
+ typedef struct bitmask* (*numa_allocate_cpumask_func_t)(void);
+ typedef struct bitmask* (*numa_allocate_nodemask_func_t)(void);
+ typedef int (*numa_sched_setaffinity_func_t)(int pid, struct bitmask* mask);
+ typedef int (*numa_bitmask_nbytes_func_t)(struct bitmask* mask);
+ typedef struct bitmask* (*numa_bitmask_setbit_func_t)(struct bitmask* mask, int len);
+ typedef struct bitmask* (*numa_bitmask_clearall_func_t)(struct bitmask* mask);
typedef struct bitmask* (*numa_get_interleave_mask_func_t)(void);
+ typedef struct bitmask* (*numa_get_run_node_mask_func_t)(void);
typedef long (*numa_move_pages_func_t)(int pid, unsigned long count, void **pages, const int *nodes, int *status, int flags);
typedef void (*numa_set_preferred_func_t)(int node);
+ typedef struct bitmask* (*numa_parse_nodestring_all_func_t)(const char*);
+ typedef int (*numa_run_on_node_mask_func_t)(struct bitmask* mask);
+ typedef void (*numa_set_membind_func_t)(struct bitmask* mask);
+ typedef int (*numa_bitmask_equal_func_t)(struct bitmask* mask, struct bitmask* mask1);
+ typedef void (*numa_bitmask_free_func_t)(struct bitmask* mask);
typedef void (*numa_set_bind_policy_func_t)(int policy);
typedef int (*numa_bitmask_isbitset_func_t)(struct bitmask *bmp, unsigned int n);
typedef int (*numa_distance_func_t)(int node1, int node2);
@@ -238,6 +255,8 @@ class os::Linux {
static sched_getcpu_func_t _sched_getcpu;
static numa_node_to_cpus_func_t _numa_node_to_cpus;
static numa_node_to_cpus_v2_func_t _numa_node_to_cpus_v2;
+ static numa_node_of_cpu_func_t _numa_node_of_cpu;
+ static numa_num_configured_cpus_func_t _numa_num_configured_cpus;
static numa_max_node_func_t _numa_max_node;
static numa_num_configured_nodes_func_t _numa_num_configured_nodes;
static numa_available_func_t _numa_available;
@@ -248,18 +267,34 @@ class os::Linux {
static numa_bitmask_isbitset_func_t _numa_bitmask_isbitset;
static numa_distance_func_t _numa_distance;
static numa_get_membind_func_t _numa_get_membind;
+ static numa_get_run_node_mask_func_t _numa_get_run_node_mask;
+ static numa_get_mems_allowed_func_t _numa_get_mems_allowed;
+ static numa_allocate_cpumask_func_t _numa_allocate_cpumask;
+ static numa_allocate_nodemask_func_t _numa_allocate_nodemask;
+ static numa_sched_setaffinity_func_t _numa_sched_setaffinity;
+ static numa_bitmask_nbytes_func_t _numa_bitmask_nbytes;
+ static numa_bitmask_setbit_func_t _numa_bitmask_setbit;
+ static numa_bitmask_clearall_func_t _numa_bitmask_clearall;
static numa_get_interleave_mask_func_t _numa_get_interleave_mask;
static numa_move_pages_func_t _numa_move_pages;
static numa_set_preferred_func_t _numa_set_preferred;
+ static numa_parse_nodestring_all_func_t _numa_parse_nodestring_all;
+ static numa_run_on_node_mask_func_t _numa_run_on_node_mask;
+ static numa_bitmask_equal_func_t _numa_bitmask_equal;
+ static numa_set_membind_func_t _numa_set_membind;
+ static numa_bitmask_free_func_t _numa_bitmask_free;
static unsigned long* _numa_all_nodes;
static struct bitmask* _numa_all_nodes_ptr;
static struct bitmask* _numa_nodes_ptr;
static struct bitmask* _numa_interleave_bitmask;
static struct bitmask* _numa_membind_bitmask;
+ static struct bitmask* _numa_cpunodebind_bitmask;
static void set_sched_getcpu(sched_getcpu_func_t func) { _sched_getcpu = func; }
static void set_numa_node_to_cpus(numa_node_to_cpus_func_t func) { _numa_node_to_cpus = func; }
static void set_numa_node_to_cpus_v2(numa_node_to_cpus_v2_func_t func) { _numa_node_to_cpus_v2 = func; }
+ static void set_numa_node_of_cpu(numa_node_of_cpu_func_t func) { _numa_node_of_cpu = func; }
+ static void set_numa_num_configured_cpus(numa_num_configured_cpus_func_t func) { _numa_num_configured_cpus = func; }
static void set_numa_max_node(numa_max_node_func_t func) { _numa_max_node = func; }
static void set_numa_num_configured_nodes(numa_num_configured_nodes_func_t func) { _numa_num_configured_nodes = func; }
static void set_numa_available(numa_available_func_t func) { _numa_available = func; }
@@ -270,14 +305,28 @@ class os::Linux {
static void set_numa_bitmask_isbitset(numa_bitmask_isbitset_func_t func) { _numa_bitmask_isbitset = func; }
static void set_numa_distance(numa_distance_func_t func) { _numa_distance = func; }
static void set_numa_get_membind(numa_get_membind_func_t func) { _numa_get_membind = func; }
+ static void set_numa_get_run_node_mask(numa_get_run_node_mask_func_t func) { _numa_get_run_node_mask = func; }
+ static void set_numa_get_mems_allowed(numa_get_mems_allowed_func_t func) { _numa_get_mems_allowed = func; }
+ static void set_numa_allocate_cpumask(numa_allocate_cpumask_func_t func) { _numa_allocate_cpumask = func; }
+ static void set_numa_allocate_nodemask(numa_allocate_nodemask_func_t func) { _numa_allocate_nodemask = func; }
+ static void set_numa_sched_setaffinity(numa_sched_setaffinity_func_t func) { _numa_sched_setaffinity = func; }
+ static void set_numa_bitmask_nbytes(numa_bitmask_nbytes_func_t func) { _numa_bitmask_nbytes = func; }
+ static void set_numa_bitmask_setbit(numa_bitmask_setbit_func_t func) { _numa_bitmask_setbit = func; }
+ static void set_numa_bitmask_clearall(numa_bitmask_clearall_func_t func) { _numa_bitmask_clearall = func; }
static void set_numa_get_interleave_mask(numa_get_interleave_mask_func_t func) { _numa_get_interleave_mask = func; }
static void set_numa_move_pages(numa_move_pages_func_t func) { _numa_move_pages = func; }
static void set_numa_set_preferred(numa_set_preferred_func_t func) { _numa_set_preferred = func; }
+ static void set_numa_parse_nodestring_all(numa_parse_nodestring_all_func_t func) { _numa_parse_nodestring_all = func; }
+ static void set_numa_run_on_node_mask(numa_run_on_node_mask_func_t func) { _numa_run_on_node_mask = func; }
+ static void set_numa_bitmask_equal(numa_bitmask_equal_func_t func) { _numa_bitmask_equal = func; }
+ static void set_numa_set_membind(numa_set_membind_func_t func) { _numa_set_membind = func; }
+ static void set_numa_bitmask_free(numa_bitmask_free_func_t func) { _numa_bitmask_free = func; }
static void set_numa_all_nodes(unsigned long* ptr) { _numa_all_nodes = ptr; }
static void set_numa_all_nodes_ptr(struct bitmask **ptr) { _numa_all_nodes_ptr = (ptr == nullptr ? nullptr : *ptr); }
static void set_numa_nodes_ptr(struct bitmask **ptr) { _numa_nodes_ptr = (ptr == nullptr ? nullptr : *ptr); }
static void set_numa_interleave_bitmask(struct bitmask* ptr) { _numa_interleave_bitmask = ptr ; }
static void set_numa_membind_bitmask(struct bitmask* ptr) { _numa_membind_bitmask = ptr ; }
+ static void set_numa_cpunodebind_bitmask(struct bitmask* ptr) { _numa_cpunodebind_bitmask = ptr ; }
static int sched_getcpu_syscall(void);
enum NumaAllocationPolicy{
@@ -383,21 +432,26 @@ class os::Linux {
}
return false;
}
- // Check if bound to only one numa node.
- // Returns true if bound to a single numa node, otherwise returns false.
- static bool is_bound_to_single_node() {
+ // Check if memory is bound to only one numa node.
+ // Returns true if memory is bound to a single numa node, otherwise returns false.
+ static bool is_bound_to_single_mem_node() {
int nodes = 0;
unsigned int node = 0;
unsigned int highest_node_number = 0;
- if (_numa_membind_bitmask != nullptr && _numa_max_node != nullptr && _numa_bitmask_isbitset != nullptr) {
+ struct bitmask* mem_nodes_bitmask = Linux::_numa_membind_bitmask;
+ if (Linux::is_running_in_interleave_mode()) {
+ mem_nodes_bitmask = Linux::_numa_interleave_bitmask;
+ }
+
+ if (mem_nodes_bitmask != nullptr && _numa_max_node != nullptr && _numa_bitmask_isbitset != nullptr) {
highest_node_number = _numa_max_node();
} else {
return false;
}
for (node = 0; node <= highest_node_number; node++) {
- if (_numa_bitmask_isbitset(_numa_membind_bitmask, node)) {
+ if (_numa_bitmask_isbitset(mem_nodes_bitmask, node)) {
nodes++;
}
}
@@ -408,6 +462,20 @@ class os::Linux {
return false;
}
}
+ // Check if cpu and memory nodes are aligned, returns true if nodes misalign
+ static bool mem_and_cpu_node_mismatch() {
+ NOT_AARCH64(return false;)
+ struct bitmask* mem_nodes_bitmask = Linux::_numa_membind_bitmask;
+ if (Linux::is_running_in_interleave_mode()) {
+ mem_nodes_bitmask = Linux::_numa_interleave_bitmask;
+ }
+
+ if (mem_nodes_bitmask == nullptr || Linux::_numa_cpunodebind_bitmask == nullptr) {
+ return false;
+ }
+
+ return !_numa_bitmask_equal(mem_nodes_bitmask, Linux::_numa_cpunodebind_bitmask);
+ }
static const GrowableArray<int>* numa_nindex_to_node() {
return _nindex_to_node;
@@ -511,6 +579,58 @@ class os::Linux {
_heap_vector_free(heap_vector);
}
}
+
+ static bitmask* numa_parse_nodestring_all(const char* s) {
+ return _numa_parse_nodestring_all != NULL ? _numa_parse_nodestring_all(s) : NULL;
+ }
+
+ static int numa_num_configured_cpus() {
+ return _numa_num_configured_cpus != NULL ? _numa_num_configured_cpus() : 0;
+ }
+
+ static bitmask* numa_allocate_cpumask() {
+ return _numa_allocate_cpumask != NULL ? _numa_allocate_cpumask() : NULL;
+ }
+
+ static bitmask* numa_allocate_nodemask() {
+ return _numa_allocate_nodemask != NULL ? _numa_allocate_nodemask() : NULL;
+ }
+
+ static int numa_sched_setaffinity(int pid, struct bitmask* mask) {
+ return _numa_sched_setaffinity != NULL ? _numa_sched_setaffinity(pid, mask) : -1;
+ }
+
+ static int numa_bitmask_nbytes(struct bitmask* mask) {
+ return _numa_bitmask_nbytes != NULL ? _numa_bitmask_nbytes(mask) : 0;
+ }
+
+ static bitmask* numa_bitmask_setbit(struct bitmask* mask, int len) {
+ return _numa_bitmask_setbit != NULL ? _numa_bitmask_setbit(mask, len) : NULL;
+ }
+
+ static bitmask* numa_bitmask_clearall(struct bitmask* mask) {
+ return _numa_bitmask_clearall != NULL ? _numa_bitmask_clearall(mask) : NULL;
+ }
+
+ static int numa_run_on_node_mask(bitmask* bitmask) {
+ return _numa_run_on_node_mask != NULL ? _numa_run_on_node_mask(bitmask) : -1;
+ }
+
+ static int numa_bitmask_equal(bitmask* bitmask, struct bitmask* bitmask1) {
+ return _numa_bitmask_equal != NULL ? _numa_bitmask_equal(bitmask, bitmask1) : 1;
+ }
+
+ static void numa_set_membind(bitmask* bitmask) {
+ if (_numa_set_membind != NULL) {
+ _numa_set_membind(bitmask);
+ }
+ }
+
+ static void numa_bitmask_free(bitmask* bitmask) {
+ if (_numa_bitmask_free != NULL) {
+ _numa_bitmask_free(bitmask);
+ }
+ }
};
#endif // OS_LINUX_OS_LINUX_HPP
@@ -390,6 +390,374 @@ int os::extra_bang_size_in_bytes() {
static inline void atomic_copy64(const volatile void *src, volatile void *dst) {
*(jlong *) dst = *(const jlong *) src;
}
+extern char** argv_for_execvp;
+
+void os::Linux::chose_numa_nodes() {
+ const char* numa_chosen_env = getenv("_JVM_NUMA_BINDING_DONE");
+ if (numa_chosen_env != NULL && strcmp(numa_chosen_env, "1") == 0) {
+ if (LogNUMANodes) {
+ warning("NUMA binding already done (detected via environment variable), skipping");
+ }
+ return;
+ }
+
+ if (NUMANodes == NULL && NUMANodesRandom == 0) {
+ return;
+ }
+
+ const int MAX_DISTANCE = 999999;
+
+ int nodes_num = Linux::numa_max_node() + 1;
+ const int MAXNODE = 100;
+ if (nodes_num <= 0 || nodes_num >= MAXNODE) {
+ warning("Invalid NUMA nodes number: %d", nodes_num);
+ return;
+ }
+
+ // Parse the NUMANodes
+ bool user_specified_nodes[MAXNODE] = {false};
+ bool has_user_constraint = false;
+
+ if (NUMANodes != NULL) {
+ if (LogNUMANodes) {
+ warning("NUMANodes parameter specified: %s", NUMANodes);
+ }
+
+ // Parse the nodestring
+ bitmask* user_nodes_mask = os::Linux::numa_parse_nodestring_all(NUMANodes);
+ if (user_nodes_mask != NULL) {
+ has_user_constraint = true;
+ for (int i = 0; i < nodes_num; i++) {
+ if (_numa_bitmask_isbitset(user_nodes_mask, i)) {
+ user_specified_nodes[i] = true;
+ if (LogNUMANodes) {
+ warning("User specified node %d is allowed", i);
+ }
+ }
+ }
+ os::Linux::numa_bitmask_free(user_nodes_mask);
+ } else {
+ if (LogNUMANodes) {
+ warning("Failed to parse NUMANodes: %s", NUMANodes);
+ warning("Skip NUMANodes parameter.");
+ }
+ }
+ }
+
+ // Save the original CPU mask specified by system(numactl)
+ cpu_set_t original_cpu_mask;
+ if (sched_getaffinity(0, sizeof(cpu_set_t), &original_cpu_mask) == -1) {
+ perror("sched_getaffinity");
+ return;
+ }
+
+ // Within the limit, count available cpus each NUMA node has
+ int cpu_count_per_node[MAXNODE] = {0};
+ bool node_has_cpu[MAXNODE] = {false};
+
+ int cpus_num = os::Linux::numa_num_configured_cpus();
+
+ // Traverse all cpus and count which cpus each Node has
+ for (int i = 0; i < cpus_num; i++) {
+ if (CPU_ISSET(i, &original_cpu_mask)) {
+ int node_id = _numa_node_of_cpu(i);
+ if (node_id == -1) {
+ if (LogNUMANodes) {
+ warning("Failed to get NUMA node for CPU %d", i);
+ }
+ } else if (node_id >= 0 && node_id < MAXNODE) {
+ node_has_cpu[node_id] = true;
+ cpu_count_per_node[node_id]++;
+ if (LogNUMANodes) {
+ warning("CPU %d belongs to Node %d", i, node_id);
+ }
+ }
+ }
+ }
+
+ // Build a list of available nodes
+ int available_nodes[MAXNODE];
+ int available_nodes_count = 0;
+ for (int i = 0; i < nodes_num; i++) {
+ if (node_has_cpu[i]) {
+ // Set the NUMANodes
+ if (has_user_constraint && !user_specified_nodes[i]) {
+ if (LogNUMANodes) {
+ warning("Node %d: has CPUs but excluded by NUMANodes parameter", i);
+ }
+ continue;
+ }
+
+ available_nodes[available_nodes_count++] = i;
+ if (LogNUMANodes) {
+ warning("Node %d: available, CPU count = %d", i, cpu_count_per_node[i]);
+ }
+ } else if (has_user_constraint && user_specified_nodes[i]) {
+ // Specified this node, but no available CPU under the system(numactl) limit
+ if (LogNUMANodes) {
+ warning("Node %d: specified in NUMANodes but has no available CPUs in numactl range", i);
+ }
+ }
+ }
+
+ if (available_nodes_count == 0) {
+ if (LogNUMANodes) {
+ if (has_user_constraint) {
+ warning("No available NUMA nodes found in NUMANodes range: %s", NUMANodes);
+ } else {
+ warning("No available NUMA nodes found");
+ }
+ }
+ return;
+ }
+
+ if (LogNUMANodes) {
+ warning("Total available nodes (after NUMANodes filter): %d", available_nodes_count);
+ }
+
+ // Check the memory binding permissions
+ bitmask * mem_allowed = _numa_get_mems_allowed();
+ if (!mem_allowed) {
+ if (LogNUMANodes) {
+ warning("Failed to get mems allowed");
+ }
+ return;
+ }
+
+ // Determine the number of nodes to be selected
+ int nodes_to_select = NUMANodesRandom;
+
+ // If NUMANodesRandom is not set or the value is invalid, use all available nodes
+ if (nodes_to_select <= 0 || nodes_to_select > available_nodes_count) {
+ nodes_to_select = available_nodes_count;
+ }
+
+ if (LogNUMANodes) {
+ if (has_user_constraint) {
+ warning("NUMANodes filter applied: %s, available nodes after filter: %d",
+ NUMANodes, available_nodes_count);
+ }
+ warning("NUMANodesRandom=%lu, will select %d nodes from %d available nodes",
+ NUMANodesRandom, nodes_to_select, available_nodes_count);
+ }
+
+ // Use PID as the random seed
+ int pid = getpid();
+ int start_index = pid % available_nodes_count;
+ int start_node = available_nodes[start_index];
+
+ // Check whether the starting node can be bound to memory
+ if (!(_numa_bitmask_isbitset(mem_allowed, start_node) &&
+ _numa_bitmask_isbitset(_numa_membind_bitmask, start_node))) {
+ // If the starting node is unavailable, find another one
+ start_node = -1;
+ for (int i = 0; i < available_nodes_count; i++) {
+ int node_id = available_nodes[i];
+ if (_numa_bitmask_isbitset(mem_allowed, node_id) &&
+ _numa_bitmask_isbitset(_numa_membind_bitmask, node_id)) {
+ start_node = node_id;
+ break;
+ }
+ }
+ if (start_node == -1) {
+ os::Linux::numa_bitmask_free(mem_allowed);
+ if (LogNUMANodes) {
+ warning("No bindable nodes found!");
+ }
+ return;
+ }
+ }
+
+ if (LogNUMANodes) {
+ warning("Start node: %d", start_node);
+ }
+
+ // Select nodes: Choose the nearest nodes_to_select node based on distance
+ int selected_nodes[MAXNODE];
+ int selected_count = 0;
+
+ // First node is the starting node
+ selected_nodes[selected_count++] = start_node;
+
+ if (nodes_to_select == 1) {
+ if (LogNUMANodes) {
+ warning("Selected node %d", start_node);
+ }
+ } else {
+ // Select by distance
+ bool node_selected[MAXNODE] = {false};
+ node_selected[start_node] = true;
+
+ // Select the nearest nodes
+ while (selected_count < nodes_to_select) {
+ int nearest_node = -1;
+ int min_total_distance = MAX_DISTANCE;
+
+ // Calculate the total distance from it to all selected nodes
+ for (int i = 0; i < available_nodes_count; i++) {
+ int candidate = available_nodes[i];
+
+ // Skip the selected nodes and the unbound nodes
+ if (node_selected[candidate]) continue;
+ if (!(_numa_bitmask_isbitset(mem_allowed, candidate) &&
+ _numa_bitmask_isbitset(_numa_membind_bitmask, candidate))) {
+ continue;
+ }
+
+ // Calculate the total distance from the candidate to all selected nodes
+ int total_distance = 0;
+ for (int j = 0; j < selected_count; j++) {
+ int selected = selected_nodes[j];
+ int dist = _numa_distance(candidate, selected);
+ total_distance += dist;
+ if (LogNUMANodes) {
+ warning("Distance from node %d to node %d: %d", candidate, selected, dist);
+ }
+ }
+
+ // Find the node with the smallest distance
+ if (total_distance < min_total_distance) {
+ min_total_distance = total_distance;
+ nearest_node = candidate;
+ }
+ }
+
+ // No more available nodes are found, exit
+ if (nearest_node == -1) {
+ if (LogNUMANodes) {
+ warning("No more bindable nodes available, selected %d nodes", selected_count);
+ }
+ break;
+ }
+
+ // Select the nearest node
+ selected_nodes[selected_count++] = nearest_node;
+ node_selected[nearest_node] = true;
+
+ if (LogNUMANodes) {
+ warning("Selected node %d (total distance: %d)", nearest_node, min_total_distance);
+ }
+ }
+ }
+
+ os::Linux::numa_bitmask_free(mem_allowed);
+
+ if (selected_count == 0) {
+ if (LogNUMANodes) {
+ warning("Cannot find proper nodes to bind!");
+ }
+ return;
+ }
+
+ if (LogNUMANodes) {
+ warning("Final selected nodes count: %d", selected_count);
+ }
+
+ // New CPU mask: only include the CPU on the selected node
+ cpu_set_t new_cpu_mask;
+ CPU_ZERO(&new_cpu_mask);
+
+ bitmask * node_cpumask = os::Linux::numa_allocate_cpumask();
+ if (!node_cpumask) {
+ if (LogNUMANodes) {
+ warning("Cannot allocate bitmask for cpus!");
+ }
+ return;
+ }
+
+ // Only retain the CPU on the selected node
+ for (int i = 0; i < selected_count; i++) {
+ int node_id = selected_nodes[i];
+
+ if (_numa_node_to_cpus_v2(node_id, node_cpumask) != 0) {
+ if (LogNUMANodes) {
+ warning("Failed to get CPUs for node %d", node_id);
+ }
+ continue;
+ }
+
+ // Key: Only add cpus that meet both of the following conditions:
+ // 1. Belongs to the selected Node
+ // 2. Within the original limits of system(numactl)
+ for (int cpu = 0; cpu < cpus_num; cpu++) {
+ if (_numa_bitmask_isbitset(node_cpumask, cpu) &&
+ CPU_ISSET(cpu, &original_cpu_mask)) {
+ CPU_SET(cpu, &new_cpu_mask);
+ if (LogNUMANodes) {
+ warning("Keeping CPU %d from node %d", cpu, node_id);
+ }
+ }
+ }
+ }
+
+ os::Linux::numa_bitmask_free(node_cpumask);
+
+ // Set the bit mask
+ char buf[256] = {0};
+ int buf_pos = 0;
+
+ for (int i = 0; i < selected_count; i++) {
+ if (i > 0) {
+ buf_pos += snprintf(buf + buf_pos, sizeof(buf) - buf_pos, ",");
+ }
+ buf_pos += snprintf(buf + buf_pos, sizeof(buf) - buf_pos, "%d", selected_nodes[i]);
+ }
+
+ bitmask* mask = numa_allocate_nodemask();
+ numa_bitmask_clearall(mask);
+ for (int i = 0; i < selected_count; i++) {
+ numa_bitmask_setbit(mask, selected_nodes[i]); // Set the bit directly
+ }
+
+ if (os::Linux::numa_bitmask_equal(mask, os::Linux::_numa_membind_bitmask)) {
+ os::Linux::numa_bitmask_free(mask);
+ if (LogNUMANodes) {
+ warning("Mempolicy is not changed, param: %s", buf);
+ }
+ return;
+ }
+
+ // Set the NUMA memory binding
+ errno = 0;
+ os::Linux::numa_run_on_node_mask(mask);
+ if (errno) {
+ perror("numa_run_on_node_mask");
+ }
+
+ errno = 0;
+ os::Linux::numa_set_membind(mask);
+ int errtmp = errno;
+ os::Linux::numa_bitmask_free(mask);
+ if (errtmp) {
+ perror("numa_set_membind");
+ }
+
+ // New CPU affinity
+ if (sched_setaffinity(0, sizeof(cpu_set_t), &new_cpu_mask) == -1) {
+ perror("sched_setaffinity");
+ if (LogNUMANodes) {
+ warning("Failed to set CPU affinity");
+ }
+ return;
+ }
+
+ if (LogNUMANodes) {
+ warning("Successfully bound to %d node(s): %s", selected_count, buf);
+ warning("Final available CPUs:");
+ for (int cpu = 0; cpu < cpus_num; cpu++) {
+ if (CPU_ISSET(cpu, &new_cpu_mask)) {
+ warning(" CPU %d", cpu);
+ }
+ }
+ }
+
+ setenv("_JVM_NUMA_BINDING_DONE", "1", 1);
+
+ execvp(*argv_for_execvp, argv_for_execvp);
+
+ // If execvp fails, perror will be executed
+ perror("execvp failed");
+}
extern "C" {
int SpinPause() {
@@ -3523,6 +3523,12 @@ _JNI_IMPORT_OR_EXPORT_ jint JNICALL JNI_GetDefaultJavaVMInitArgs(void *args_) {
DT_RETURN_MARK_DECL(CreateJavaVM, jint
, HOTSPOT_JNI_CREATEJAVAVM_RETURN(_ret_ref));
+const char** argv_for_execvp;
+
+_JNI_IMPORT_OR_EXPORT_ void JNICALL JNI_SetCParam(char** raw_argv) {
+ argv_for_execvp = (const char**)raw_argv;
+}
+
static jint JNI_CreateJavaVM_inner(JavaVM **vm, void **penv, void *args) {
HOTSPOT_JNI_CREATEJAVAVM_ENTRY((void **) vm, penv, args);
@@ -1973,6 +1973,9 @@ JNI_GetDefaultJavaVMInitArgs(void *args);
_JNI_IMPORT_OR_EXPORT_ jint JNICALL
JNI_CreateJavaVM(JavaVM **pvm, void **penv, void *args);
+_JNI_IMPORT_OR_EXPORT_ void JNICALL
+JNI_SetCParam(char **raw_argv);
+
_JNI_IMPORT_OR_EXPORT_ jint JNICALL
JNI_GetCreatedJavaVMs(JavaVM **, jsize, jsize *);
@@ -287,6 +287,7 @@ JLI_Launch(int argc, char ** argv, /* main argc, argv */
ifn.CreateJavaVM = 0;
ifn.GetDefaultJavaVMInitArgs = 0;
+ ifn.raw_argv = argv;
if (JLI_IsTraceLauncher()) {
start = CurrentTimeMicros();
@@ -1510,6 +1510,7 @@
i, args.options[i].optionString);
}
+ ifn->SetCParam(ifn->raw_argv);
r = ifn->CreateJavaVM(pvm, (void **)penv, &args);
JLI_MemFree(options);
options = NULL;
@@ -77,13 +77,16 @@
* Pointers to the needed JNI invocation API, initialized by LoadJavaVM.
*/
typedef jint (JNICALL *CreateJavaVM_t)(JavaVM **pvm, void **env, void *args);
+typedef void (JNICALL *SetCParam_t)(char** raw_argv);
typedef jint (JNICALL *GetDefaultJavaVMInitArgs_t)(void *args);
typedef jint (JNICALL *GetCreatedJavaVMs_t)(JavaVM **vmBuf, jsize bufLen, jsize *nVMs);
typedef struct {
CreateJavaVM_t CreateJavaVM;
+ SetCParam_t SetCParam;
GetDefaultJavaVMInitArgs_t GetDefaultJavaVMInitArgs;
GetCreatedJavaVMs_t GetCreatedJavaVMs;
+ char** raw_argv;
} InvocationFunctions;
JNIEXPORT int JNICALL
@@ -542,6 +542,12 @@ LoadJavaVM(const char *jvmpath, InvocationFunctions *ifn)
return JNI_FALSE;
}
+ ifn->SetCParam = (SetCParam_t)
+ dlsym(libjvm, "JNI_SetCParam");
+ if (ifn->SetCParam == NULL) {
+ JLI_ReportErrorMessage(DLL_ERROR2, jvmpath, dlerror());
+ return JNI_FALSE;
+ }
ifn->GetDefaultJavaVMInitArgs = (GetDefaultJavaVMInitArgs_t)
dlsym(libjvm, "JNI_GetDefaultJavaVMInitArgs");
if (ifn->GetDefaultJavaVMInitArgs == NULL) {
new file mode 100644
@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) 2025, Huawei Technologies Co., Ltd. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/**
+ * @test TestNUMAErrorHandling
+ * @summary Test error handling and edge cases
+ * @library /test/lib
+ * @requires os.family == "linux"
+ * @run driver TestNUMAErrorHandling
+ */
+import jdk.test.lib.process.OutputAnalyzer;
+import jdk.test.lib.process.ProcessTools;
+
+public class TestNUMAErrorHandling {
+
+ public static void main(String[] args) throws Exception {
+ // Test Case 1: Without UseNUMA
+ testWithoutUseNUMA();
+
+ // Test Case 2: Negative parameter
+ testNegativeParameter();
+
+ // Test Case 3: Without NUMANodes & NUMANodesRandom
+ testNoParametersSet();
+
+ // Test Case 4: Invalid NUMANodes
+ testInvalidNodeString();
+
+ System.out.println("All error handling tests passed!");
+ }
+
+ private static void testWithoutUseNUMA() throws Exception {
+ System.out.println("\n=== Test Case 1: Without UseNUMA ===");
+
+ OutputAnalyzer output = ProcessTools.executeTestJava(
+ "-XX:NUMANodesRandom=1",
+ "-XX:+LogNUMANodes",
+ "-version"
+ );
+
+ output.shouldHaveExitValue(0);
+ output.shouldNotContain("Successfully bound to");
+
+ System.out.println("PASS: No binding without UseNUMA flag");
+ }
+
+ private static void testNegativeParameter() throws Exception {
+ System.out.println("\n=== Test Case 2: Negative parameter ===");
+
+ OutputAnalyzer output = ProcessTools.executeTestJava(
+ "-XX:+UseNUMA",
+ "-XX:NUMANodesRandom=-1",
+ "-XX:+LogNUMANodes",
+ "-version"
+ );
+
+ output.shouldNotHaveExitValue(0);
+ output.shouldContain("Improperly specified VM option");
+
+ System.out.println("PASS: Negative parameter handled");
+ }
+
+ private static void testNoParametersSet() throws Exception {
+ System.out.println("\n=== Test Case 3: No parameters set ===");
+
+ OutputAnalyzer output = ProcessTools.executeTestJava(
+ "-XX:+UseNUMA",
+ "-XX:+LogNUMANodes",
+ "-version"
+ );
+
+ output.shouldHaveExitValue(0);
+ output.shouldNotContain("will select");
+
+ System.out.println("PASS: No binding without parameters");
+ }
+
+ private static void testInvalidNodeString() throws Exception {
+ System.out.println("\n=== Test Case 4: Invalid NUMANodes format ===");
+
+ OutputAnalyzer output = ProcessTools.executeTestJava(
+ "-XX:+UseNUMA",
+ "-XX:NUMANodes=invalid",
+ "-XX:+LogNUMANodes",
+ "-version"
+ );
+
+ output.shouldHaveExitValue(0);
+ output.shouldMatch("Failed to parse NUMANodes|<invalid> is invalid");
+
+ System.out.println("PASS: Invalid format handled gracefully");
+ }
+}
new file mode 100644
@@ -0,0 +1,177 @@
+/*
+ * Copyright (c) 2025, Huawei Technologies Co., Ltd. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/**
+ * @test TestNUMANodes
+ * @summary Test NUMANodes parameter with different node specifications
+ * @library /test/lib
+ * @requires os.family == "linux"
+ * @run driver TestNUMANodes
+ */
+import jdk.test.lib.process.OutputAnalyzer;
+import jdk.test.lib.process.ProcessTools;
+import java.io.File;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class TestNUMANodes {
+
+ private static int getNumaNodeCount() throws Exception {
+ // Read /sys/devices/system/node/possible for node number
+ File nodeFile = new File("/sys/devices/system/node/possible");
+ if (!nodeFile.exists()) {
+ return -1; // NUMA is unavailable
+ }
+
+ ProcessBuilder pb = new ProcessBuilder("numactl", "-H");
+ Process p = pb.start();
+ p.waitFor();
+
+ // Simply Check if numactl is available
+ if (p.exitValue() != 0) {
+ return -1;
+ }
+
+ // Determine the number of nodes through the output of numactl-H
+ String output = new String(p.getInputStream().readAllBytes());
+ Pattern pattern = Pattern.compile("available: (\\d+) nodes");
+ Matcher matcher = pattern.matcher(output);
+ if (matcher.find()) {
+ return Integer.parseInt(matcher.group(1));
+ }
+ return -1;
+ }
+
+ public static void main(String[] args) throws Exception {
+ int nodeCount = getNumaNodeCount();
+ if (nodeCount <= 1) {
+ System.out.println("SKIP: Need multi-node NUMA system");
+ return;
+ }
+
+ // Test Case 1: Single-node "0"
+ testSingleNode();
+
+ // Test Case 2: Node range "0-1"
+ testNodeRange();
+
+ // Test Case 3: Node enumeration "0,2"
+ if (nodeCount >= 3) {
+ testNodeList();
+ }
+
+ // Test Case 4: Mixed format "0-1,3"
+ if (nodeCount >= 4) {
+ testMixedFormat();
+ }
+
+ // Test Case 5: Invalid node range
+ testInvalidNodeRange();
+
+ System.out.println("All NUMANodes tests passed!");
+ }
+
+ private static void testSingleNode() throws Exception {
+ System.out.println("\n=== Test Case 1: NUMANodes=0 ===");
+
+ OutputAnalyzer output = ProcessTools.executeTestJava(
+ "-XX:+UseNUMA",
+ "-XX:NUMANodes=0",
+ "-XX:+LogNUMANodes",
+ "-version"
+ );
+
+ output.shouldHaveExitValue(0);
+ output.shouldContain("NUMANodes parameter specified: 0");
+ output.shouldContain("User specified node 0 is allowed");
+
+ System.out.println("PASS: Single node specification works");
+ }
+
+ private static void testNodeRange() throws Exception {
+ System.out.println("\n=== Test Case 2: NUMANodes=0-1 ===");
+
+ OutputAnalyzer output = ProcessTools.executeTestJava(
+ "-XX:+UseNUMA",
+ "-XX:NUMANodes=0-1",
+ "-XX:+LogNUMANodes",
+ "-version"
+ );
+
+ output.shouldHaveExitValue(0);
+ output.shouldContain("NUMANodes parameter specified: 0-1");
+ output.shouldContain("User specified node 0 is allowed");
+ output.shouldContain("User specified node 1 is allowed");
+
+ System.out.println("PASS: Node range specification works");
+ }
+
+ private static void testNodeList() throws Exception {
+ System.out.println("\n=== Test Case 3: NUMANodes=0,2 ===");
+
+ OutputAnalyzer output = ProcessTools.executeTestJava(
+ "-XX:+UseNUMA",
+ "-XX:NUMANodes=0,2",
+ "-XX:+LogNUMANodes",
+ "-version"
+ );
+
+ output.shouldHaveExitValue(0);
+ output.shouldContain("NUMANodes parameter specified: 0,2");
+
+ System.out.println("PASS: Node list specification works");
+ }
+
+ private static void testMixedFormat() throws Exception {
+ System.out.println("\n=== Test Case 4: NUMANodes=0-1,3 ===");
+
+ OutputAnalyzer output = ProcessTools.executeTestJava(
+ "-XX:+UseNUMA",
+ "-XX:NUMANodes=0-1,3",
+ "-XX:+LogNUMANodes",
+ "-version"
+ );
+
+ output.shouldHaveExitValue(0);
+ output.shouldContain("NUMANodes parameter specified: 0-1,3");
+
+ System.out.println("PASS: Mixed format specification works");
+ }
+
+ private static void testInvalidNodeRange() throws Exception {
+ System.out.println("\n=== Test Case 5: Invalid NUMANodes=99 ===");
+
+ OutputAnalyzer output = ProcessTools.executeTestJava(
+ "-XX:+UseNUMA",
+ "-XX:NUMANodes=999",
+ "-XX:+LogNUMANodes",
+ "-version"
+ );
+
+ output.shouldHaveExitValue(0);
+ // There should be warnings but no core dump
+ output.shouldMatch("Failed to parse NUMANodes|No available NUMA nodes");
+
+ System.out.println("PASS: Invalid node range handled gracefully");
+ }
+}
new file mode 100644
@@ -0,0 +1,203 @@
+/*
+ * Copyright (c) 2025, Huawei Technologies Co., Ltd. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/**
+ * @test TestNUMANodesRandom
+ * @summary Test NUMANodesRandom parameter with different values
+ * @library /test/lib
+ * @requires os.family == "linux"
+ * @run driver TestNUMANodesRandom
+ */
+import jdk.test.lib.process.OutputAnalyzer;
+import jdk.test.lib.process.ProcessTools;
+import jdk.test.lib.Platform;
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class TestNUMANodesRandom {
+
+ private static int getNumaNodeCount() throws Exception {
+ // Read /sys/devices/system/node/possible for node number
+ File nodeFile = new File("/sys/devices/system/node/possible");
+ if (!nodeFile.exists()) {
+ return -1; // NUMA is unavailable
+ }
+
+ ProcessBuilder pb = new ProcessBuilder("numactl", "-H");
+ Process p = pb.start();
+ p.waitFor();
+
+ // Simply Check if numactl is available
+ if (p.exitValue() != 0) {
+ return -1;
+ }
+
+ // Determine the number of nodes through the output of numactl-H
+ String output = new String(p.getInputStream().readAllBytes());
+ Pattern pattern = Pattern.compile("available: (\\d+) nodes");
+ Matcher matcher = pattern.matcher(output);
+ if (matcher.find()) {
+ return Integer.parseInt(matcher.group(1));
+ }
+ return -1;
+ }
+
+ private static boolean hasLibnuma() {
+ try {
+ ProcessBuilder pb = new ProcessBuilder("ldconfig", "-p");
+ Process p = pb.start();
+ String output = new String(p.getInputStream().readAllBytes());
+ return output.contains("libnuma.so");
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+ public static void main(String[] args) throws Exception {
+ // Check the preconditions
+ if (!hasLibnuma()) {
+ System.out.println("SKIP: libnuma not available");
+ return;
+ }
+
+ int nodeCount = getNumaNodeCount();
+ if (nodeCount <= 0) {
+ System.out.println("SKIP: NUMA not available or single node system");
+ return;
+ }
+
+ System.out.println("Detected " + nodeCount + " NUMA nodes");
+
+ // Test Case 1: Select one random node
+ testSingleNodeRandom();
+
+ // Test Case 2: Select multiple random nodes
+ if (nodeCount >= 2) {
+ testMultipleNodesRandom(2);
+ }
+
+ // Test Case 3: NUMANodesRandom exceeds the number of nodes
+ testRandomExceedingNodeCount(nodeCount);
+
+ // Test Case 4: NUMANodesRandom=0 (Not enabled)
+ testRandomZero();
+
+ // Test Case 5: Verify the environment variable tags
+ testEnvVarPreventsReexecution();
+
+ System.out.println("All tests passed!");
+ }
+
+ private static void testSingleNodeRandom() throws Exception {
+ System.out.println("\n=== Test Case 1: NUMANodesRandom=1 ===");
+
+ OutputAnalyzer output = ProcessTools.executeTestJava(
+ "-XX:+UseNUMA",
+ "-XX:NUMANodesRandom=1",
+ "-XX:+LogNUMANodes",
+ "-version"
+ );
+
+ output.shouldHaveExitValue(0);
+ output.shouldContain("will select 1 nodes from");
+ output.shouldContain("Successfully bound to 1 node(s)");
+
+ System.out.println("PASS: Single node random selection works");
+ }
+
+ private static void testMultipleNodesRandom(int count) throws Exception {
+ System.out.println("\n=== Test Case 2: NUMANodesRandom=" + count + " ===");
+
+ OutputAnalyzer output = ProcessTools.executeTestJava(
+ "-XX:+UseNUMA",
+ "-XX:NUMANodesRandom=" + count,
+ "-XX:+LogNUMANodes",
+ "-version"
+ );
+
+ output.shouldHaveExitValue(0);
+ output.shouldContain("will select " + count + " nodes from");
+ output.shouldContain("Distance from node");
+ output.shouldContain("Successfully bound to " + count + " node(s)");
+
+ System.out.println("PASS: Multiple nodes random selection works");
+ }
+
+ private static void testRandomExceedingNodeCount(int nodeCount) throws Exception {
+ System.out.println("\n=== Test Case 3: NUMANodesRandom exceeds node count ===");
+
+ int exceedingCount = nodeCount + 5;
+ OutputAnalyzer output = ProcessTools.executeTestJava(
+ "-XX:+UseNUMA",
+ "-XX:NUMANodesRandom=" + exceedingCount,
+ "-XX:+LogNUMANodes",
+ "-version"
+ );
+
+ output.shouldHaveExitValue(0);
+ // All available nodes should be used
+ output.shouldContain("will select " + nodeCount + " nodes from " + nodeCount);
+
+ System.out.println("PASS: Exceeding count handled correctly");
+ }
+
+ private static void testRandomZero() throws Exception {
+ System.out.println("\n=== Test Case 4: NUMANodesRandom=0 ===");
+
+ OutputAnalyzer output = ProcessTools.executeTestJava(
+ "-XX:+UseNUMA",
+ "-XX:NUMANodesRandom=0",
+ "-XX:+LogNUMANodes",
+ "-version"
+ );
+
+ output.shouldHaveExitValue(0);
+ output.shouldNotContain("Successfully bound to");
+
+ System.out.println("PASS: NUMANodesRandom=0 disables binding");
+ }
+
+ private static void testEnvVarPreventsReexecution() throws Exception {
+ System.out.println("\n=== Test Case 5: Environment variable prevents re-execution ===");
+
+ ProcessBuilder pb = ProcessTools.createTestJavaProcessBuilder(
+ "-XX:+UseNUMA",
+ "-XX:NUMANodesRandom=1",
+ "-XX:+LogNUMANodes",
+ "-version"
+ );
+
+ pb.environment().put("_JVM_NUMA_BINDING_DONE", "1");
+
+ OutputAnalyzer output = new OutputAnalyzer(pb.start());
+ output.shouldHaveExitValue(0);
+ output.shouldContain("NUMA binding already done");
+ output.shouldNotContain("Successfully bound to");
+
+ System.out.println("PASS: Environment variable prevents re-execution");
+ }
+}
--
2.50.1