From 47fdd055dee9dc20508598e35490d899112673db Mon Sep 17 00:00:00 2001
Date: Thu, 20 Nov 2025 16:00:17 +0800
Subject: [PATCH] Add dynamic max heap size
src/hotspot/cpu/aarch64/globals_aarch64.hpp | 19 ++
.../cpu/aarch64/vm_version_aarch64.cpp | 25 ++
.../cpu/aarch64/vm_version_aarch64.hpp | 10 +-
src/hotspot/os/linux/os_linux.cpp | 8 +-
src/hotspot/os/linux/os_linux.hpp | 4 +-
src/hotspot/share/cds/filemap.cpp | 7 +-
src/hotspot/share/classfile/vmSymbols.hpp | 5 +
src/hotspot/share/gc/g1/g1Arguments.cpp | 19 +-
src/hotspot/share/gc/g1/g1CollectedHeap.cpp | 20 +-
src/hotspot/share/gc/g1/g1CollectedHeap.hpp | 26 +-
.../share/gc/g1/g1HeapSizingPolicy.cpp | 35 ++-
src/hotspot/share/gc/g1/g1MemoryPool.cpp | 1 +
src/hotspot/share/gc/g1/g1MemoryPool.hpp | 7 +
.../share/gc/g1/g1MonitoringSupport.cpp | 8 +
.../share/gc/g1/g1MonitoringSupport.hpp | 3 +
src/hotspot/share/gc/g1/g1Policy.cpp | 7 +-
src/hotspot/share/gc/g1/g1VMOperations.cpp | 129 +++++++++
src/hotspot/share/gc/g1/g1VMOperations.hpp | 12 +
src/hotspot/share/gc/g1/heapRegionManager.cpp | 32 ++-
src/hotspot/share/gc/g1/heapRegionManager.hpp | 18 +-
.../share/gc/parallel/parallelArguments.cpp | 6 +
.../gc/parallel/parallelScavengeHeap.cpp | 34 ++-
.../gc/parallel/parallelScavengeHeap.hpp | 4 +
.../gc/parallel/psGenerationCounters.cpp | 10 +-
.../share/gc/parallel/psMemoryPool.hpp | 2 +-
src/hotspot/share/gc/parallel/psOldGen.cpp | 9 +-
src/hotspot/share/gc/parallel/psOldGen.hpp | 24 +-
.../share/gc/parallel/psVMOperations.cpp | 252 ++++++++++++++++++
.../share/gc/parallel/psVMOperations.hpp | 8 +
.../share/gc/parallel/psVirtualspace.cpp | 7 +-
.../share/gc/parallel/psVirtualspace.hpp | 22 ++
src/hotspot/share/gc/parallel/psYoungGen.cpp | 9 +-
src/hotspot/share/gc/parallel/psYoungGen.hpp | 26 +-
src/hotspot/share/gc/shared/collectedHeap.cpp | 1 +
src/hotspot/share/gc/shared/collectedHeap.hpp | 11 +
.../share/gc/shared/dynamicMaxHeap.cpp | 150 +++++++++++
.../share/gc/shared/dynamicMaxHeap.hpp | 61 +++++
src/hotspot/share/gc/shared/gcArguments.cpp | 9 +
src/hotspot/share/gc/shared/gcCause.cpp | 3 +
src/hotspot/share/gc/shared/gcCause.hpp | 1 +
src/hotspot/share/gc/shared/genArguments.cpp | 9 +-
src/hotspot/share/gc/shared/genArguments.hpp | 15 ++
.../share/gc/shared/generationCounters.cpp | 17 +-
.../share/gc/shared/generationCounters.hpp | 5 +
.../share/gc/shared/referencePolicy.cpp | 3 +
src/hotspot/share/memory/universe.cpp | 3 +
src/hotspot/share/memory/universe.hpp | 45 ++++
src/hotspot/share/runtime/arguments.cpp | 15 ++
src/hotspot/share/runtime/globals.hpp | 3 +
src/hotspot/share/runtime/os.cpp | 4 +
src/hotspot/share/runtime/os.hpp | 2 +
src/hotspot/share/runtime/threads.cpp | 9 +
src/hotspot/share/runtime/vmOperation.hpp | 3 +-
.../share/services/diagnosticCommand.cpp | 117 ++++++++
.../share/services/diagnosticCommand.hpp | 53 ++++
.../share/classes/java/nio/Bits.java | 26 ++
.../share/classes/jdk/internal/misc/VM.java | 6 +
test/hotspot/jtreg/gc/TestSmallHeap.java | 2 +-
.../jtreg/gc/arguments/TestMaxRAMFlags.java | 2 +-
.../jtreg/gc/dynamicmaxheap/BasicTest.java | 94 +++++++
.../dynamicmaxheap/DirectMemoryBasicTest.java | 76 ++++++
.../dynamicmaxheap/LimitDirectMemoryTest.java | 92 +++++++
.../gc/dynamicmaxheap/MemoryPoolTest.java | 139 ++++++++++
.../jtreg/gc/dynamicmaxheap/OptionsCheck.java | 73 +++++
.../gc/dynamicmaxheap/RuntimeMemoryTest.java | 96 +++++++
.../jtreg/gc/dynamicmaxheap/TestBase.java | 50 ++++
.../LimitDirectMemoryTestBasic.java | 63 +++++
.../test_classes/NotActiveDirectMemory.java | 41 +++
.../test_classes/NotActiveHeap.java | 27 ++
69 files changed, 2091 insertions(+), 43 deletions(-)
create mode 100644 src/hotspot/share/gc/shared/dynamicMaxHeap.cpp
create mode 100644 src/hotspot/share/gc/shared/dynamicMaxHeap.hpp
create mode 100644 test/hotspot/jtreg/gc/dynamicmaxheap/BasicTest.java
create mode 100644 test/hotspot/jtreg/gc/dynamicmaxheap/DirectMemoryBasicTest.java
create mode 100644 test/hotspot/jtreg/gc/dynamicmaxheap/LimitDirectMemoryTest.java
create mode 100644 test/hotspot/jtreg/gc/dynamicmaxheap/MemoryPoolTest.java
create mode 100644 test/hotspot/jtreg/gc/dynamicmaxheap/OptionsCheck.java
create mode 100644 test/hotspot/jtreg/gc/dynamicmaxheap/RuntimeMemoryTest.java
create mode 100644 test/hotspot/jtreg/gc/dynamicmaxheap/TestBase.java
create mode 100644 test/hotspot/jtreg/gc/dynamicmaxheap/test_classes/LimitDirectMemoryTestBasic.java
create mode 100644 test/hotspot/jtreg/gc/dynamicmaxheap/test_classes/NotActiveDirectMemory.java
create mode 100644 test/hotspot/jtreg/gc/dynamicmaxheap/test_classes/NotActiveHeap.java
@@ -127,6 +127,25 @@ define_pd_global(intx, InlineSmallCode, 1000);
range(1, 99) \
product(ccstr, UseBranchProtection, "none", \
"Branch Protection to use: none, standard, pac-ret") \
+ \
+ product(size_t, DynamicMaxHeapSizeLimit, ScaleForWordSize(96*M), \
+ "The limit of Dynamic maximum heap size (in bytes)") \
+ \
+ product(uintx, DynamicMaxHeapShrinkMinFreeRatio, 40, \
+ "Minimal ratio of free bytes after dynamic max heap shirnk") \
+ \
+ product(size_t, ElasticMaxHeapSize, ScaleForWordSize(96*M), \
+ "Elastic maximum heap size (in bytes)") \
+ \
+ product(bool, ElasticMaxHeap, false, \
+ "Allow change max heap size during runtime with jcmd") \
+ \
+ product(bool, TraceElasticMaxHeap, false, \
+ "Trace Elastic Max Heap adjustion logs and failure reasons") \
+ \
+ product(uintx, ElasticMaxHeapShrinkMinFreeRatio, 40, \
+ "minimal ratio of free bytes after elastic max heap shirnk") \
+ \
// end of ARCH_FLAGS
@@ -68,6 +68,31 @@ static SpinWait get_spin_wait_desc() {
return SpinWait{};
}
+int VM_Version::get_cpu_model() {
+ int cpu_lines = 0;
+ if (FILE *f = fopen("/proc/cpuinfo", "r")) {
+ char buf[128], *p;
+ while (fgets(buf, sizeof (buf), f) != NULL) {
+ if ((p = strchr(buf, ':')) != NULL) {
+ long v = strtol(p+1, NULL, 0);
+ if (strncmp(buf, "CPU implementer", sizeof "CPU implementer" - 1) == 0) {
+ _cpu = v;
+ cpu_lines++;
+ } else if (strncmp(buf, "CPU variant", sizeof "CPU variant" - 1) == 0) {
+ _variant = v;
+ } else if (strncmp(buf, "CPU part", sizeof "CPU part" - 1) == 0) {
+ if (_model != v) _model2 = _model;
+ _model = v;
+ } else if (strncmp(buf, "CPU revision", sizeof "CPU revision" - 1) == 0) {
+ _revision = v;
+ }
+ }
+ }
+ fclose(f);
+ }
+ return cpu_lines;
+}
+
void VM_Version::initialize() {
_supports_cx8 = true;
_supports_atomic_getset4 = true;
@@ -145,13 +145,21 @@ enum Ampere_CPU_Model {
static bool supports_##name() { return (_features & CPU_##id) != 0; };
CPU_FEATURE_FLAGS(CPU_FEATURE_DETECTION)
#undef CPU_FEATURE_DETECTION
-
+
+ static int get_cpu_model();
static int cpu_family() { return _cpu; }
static int cpu_model() { return _model; }
static int cpu_model2() { return _model2; }
static int cpu_variant() { return _variant; }
static int cpu_revision() { return _revision; }
+ static bool is_hisi_enabled() {
+ if (_cpu == CPU_HISILICON && (_model == 0xd01 || _model == 0xd02)) {
+ return true;
+ }
+ return false;
+ }
+
static bool model_is(int cpu_model) {
return _model == cpu_model || _model2 == cpu_model;
}
@@ -3439,6 +3439,10 @@ bool os::pd_uncommit_memory(char* addr, size_t size, bool exec) {
return res != (uintptr_t) MAP_FAILED;
}
+bool os::pd_free_heap_physical_memory(char *addr, size_t bytes) {
+ return madvise(addr, bytes, MADV_DONTNEED) == 0;
+}
+
static address get_stack_commited_bottom(address bottom, size_t size) {
address nbot = bottom;
address ntop = bottom + size;
@@ -4707,7 +4711,7 @@ os::Linux::heap_vector_add_t os::Linux::_heap_vector_add;
os::Linux::heap_vector_get_next_t os::Linux::_heap_vector_get_next;
os::Linux::heap_vector_free_t os::Linux::_heap_vector_free;
-void os::Linux::load_plugin_library() {
+void os::Linux::load_ACC_library() {
#if INCLUDE_JBOLT
_jboltLog_precalc = CAST_TO_FN_PTR(jboltLog_precalc_t, dlsym(RTLD_DEFAULT, "JBoltLog_PreCalc"));
@@ -4891,7 +4895,7 @@ jint os::init_2(void) {
init_adjust_stacksize_for_guard_pages();
#endif
- Linux::load_plugin_library();
+ Linux::load_ACC_library();
if (UseNUMA || UseNUMAInterleaving) {
Linux::numa_init();
@@ -137,7 +137,7 @@ class os::Linux {
static const char *libc_version() { return _libc_version; }
static const char *libpthread_version() { return _libpthread_version; }
- static void load_plugin_library();
+ static void load_ACC_library();
static void libpthread_init();
static void sched_getcpu_init();
static bool libnuma_init();
@@ -498,7 +498,7 @@ class os::Linux {
}
return _heap_vector_add(val, heap_vector, _inserted);
}
-
+
static void* heap_vector_get_next(void* heap_vector, void* heap_vector_node, int &_cnt, void** &_items) {
if(_heap_vector_get_next == NULL) {
return NULL;
@@ -2093,8 +2093,10 @@ bool FileMapInfo::map_heap_region() {
address heap_end = (address)heap_range.end();
address mapped_heap_region_end = (address)_mapped_heap_memregion.end();
assert(heap_end >= mapped_heap_region_end, "must be");
- assert(heap_end - mapped_heap_region_end < (intx)(HeapRegion::GrainBytes),
+ if (!Universe::is_dynamic_max_heap_enable()) {
+ assert(heap_end - mapped_heap_region_end < (intx)(HeapRegion::GrainBytes),
"must be at the top of the heap to avoid fragmentation");
+ }
#endif
ArchiveHeapLoader::set_mapped();
@@ -2113,6 +2115,9 @@ void FileMapInfo::init_heap_region_relocation() {
address requested_bottom = (address)archive_range.start();
address heap_end = (address)heap_range.end();
+ if (Universe::is_dynamic_max_heap_enable()) {
+ heap_end = (address)heap_range.start() + MaxHeapSize;
+ }
assert(is_aligned(heap_end, HeapRegion::GrainBytes), "must be");
// We map the archive heap region at the very top of the heap to avoid fragmentation.
@@ -788,6 +788,11 @@
template(toFileURL_signature, "(Ljava/lang/String;)Ljava/net/URL;") \
template(url_void_signature, "(Ljava/net/URL;)V") \
\
+ /* ElasticMaxDirectMemory */ \
+ template(java_nio_Bits, "java/nio/Bits") \
+ template(updateMaxMemory_name, "updateMaxMemory") \
+ template(updateMaxMemory_signature, "(J)Ljava/lang/String;") \
+ \
/* Thread.dump_to_file jcmd */ \
template(jdk_internal_vm_ThreadDumper, "jdk/internal/vm/ThreadDumper") \
template(dumpThreads_name, "dumpThreads") \
@@ -37,6 +37,7 @@
#include "runtime/globals.hpp"
#include "runtime/globals_extension.hpp"
#include "runtime/java.hpp"
+#include "memory/universe.hpp"
static size_t calculate_heap_alignment(size_t space_alignment) {
size_t card_table_alignment = CardTable::ct_max_alignment_constraint();
@@ -53,7 +54,18 @@ void G1Arguments::initialize_alignments() {
// There is a circular dependency here. We base the region size on the heap
// size, but the heap size should be aligned with the region size. To get
// around this we use the unaligned values for the heap.
- HeapRegion::setup_heap_region_size(MaxHeapSize);
+
+ if (Universe::is_dynamic_max_heap_enable()) {
+#ifdef AARCH64
+ if (!FLAG_IS_CMDLINE(DynamicMaxHeapSizeLimit) && !FLAG_IS_CMDLINE(ElasticMaxHeapSize)) {
+ guarantee(ElasticMaxHeap, "must be");
+ FLAG_SET_ERGO(DynamicMaxHeapSizeLimit, MaxHeapSize);
+ }
+ HeapRegion::setup_heap_region_size(DynamicMaxHeapSizeLimit);
+#endif //AARCH64
+ } else {
+ HeapRegion::setup_heap_region_size(MaxHeapSize);
+ }
SpaceAlignment = HeapRegion::GrainBytes;
HeapAlignment = calculate_heap_alignment(SpaceAlignment);
@@ -263,6 +275,11 @@ CollectedHeap* G1Arguments::create_heap() {
}
size_t G1Arguments::heap_reserved_size_bytes() {
+#ifdef AARCH64
+ if (Universe::is_dynamic_max_heap_enable()) {
+ return DynamicMaxHeapSizeLimit;
+ }
+#endif //AARCH64
return MaxHeapSize;
}
@@ -2152,6 +2152,12 @@ size_t G1CollectedHeap::unsafe_max_tlab_alloc(Thread* ignored) const {
}
size_t G1CollectedHeap::max_capacity() const {
+ // Dynamic Max Heap
+ if (Universe::is_dynamic_max_heap_enable()) {
+ size_t cur_size = current_max_heap_size();
+ guarantee(cur_size <= max_regions() * HeapRegion::GrainBytes, "must be");
+ return cur_size;
+ }
return max_regions() * HeapRegion::GrainBytes;
}
@@ -2916,7 +2922,7 @@ public:
}
};
-void G1CollectedHeap::rebuild_region_sets(bool free_list_only) {
+void G1CollectedHeap::rebuild_region_sets(bool free_list_only, bool is_dynamic_max_heap_shrink) {
assert_at_safepoint_on_vm_thread();
if (!free_list_only) {
@@ -2932,7 +2938,10 @@ void G1CollectedHeap::rebuild_region_sets(bool free_list_only) {
if (!free_list_only) {
set_used(cl.total_used());
}
- assert_used_and_recalculate_used_equal(this);
+ // don't do this assert if is_dynamic_max_heap_shrink
+ if (!is_dynamic_max_heap_shrink) {
+ assert_used_and_recalculate_used_equal(this);
+ }
}
// Methods for the mutator alloc region
@@ -3170,3 +3179,10 @@ void G1CollectedHeap::finish_codecache_marking_cycle() {
CodeCache::on_gc_marking_cycle_finish();
CodeCache::arm_all_nmethods();
}
+
+bool G1CollectedHeap::change_max_heap(size_t new_size) {
+ assert_heap_not_locked();
+ G1_ChangeMaxHeapOp op(new_size);
+ VMThread::execute(&op);
+ return op.resize_success();
+}
\ No newline at end of file
@@ -168,6 +168,8 @@
// Testing classes.
friend class G1CheckRegionAttrTableClosure;
+ friend class G1_ChangeMaxHeapOp;
+
private:
// GC Overhead Limit functionality related members.
//
@@ -206,7 +208,7 @@ private:
// reflect the contents of the heap. The only exception is the
// humongous set which was not torn down in the first place. If
// free_list_only is true, it will only rebuild the free list.
- void rebuild_region_sets(bool free_list_only);
+ void rebuild_region_sets(bool free_list_only, bool is_dynamic_max_heap_shrink = false);
// Callback for region mapping changed events.
G1RegionMappingChangedListener _listener;
@@ -1317,6 +1319,28 @@ public:
// Used to print information about locations in the hs_err file.
bool print_location(outputStream* st, void* addr) const override;
+
+private:
+ // Dynamic Max Heap
+ // expected DynamicMaxHeap size during full gc (temp value)
+ // 0 means do not adjust
+ // min_gen_size <= _expected_dynamic_max_heap_size <= _reserved size.
+ // will be cleared after DynamicMaxHeap VM operation.
+ size_t _exp_dynamic_max_heap_size;
+public:
+ bool change_max_heap(size_t new_size) override;
+ size_t exp_dynamic_max_heap_size() const {
+ NOT_AARCH64(return 0;)
+ AARCH64_ONLY(return _exp_dynamic_max_heap_size;)
+ }
+ void set_exp_dynamic_max_heap_size(size_t size) {
+ guarantee(size <= _reserved.byte_size(), "must be");
+ _exp_dynamic_max_heap_size = size;
+ }
+ void update_gen_max_counter(size_t size) {
+ guarantee(Universe::is_dynamic_max_heap_enable(), "must be");
+ _monitoring_support->update_max_sizes(size);
+ }
};
// Scoped object that performs common pre- and post-gc heap printing operations.
@@ -31,6 +31,7 @@
#include "runtime/globals.hpp"
#include "utilities/debug.hpp"
#include "utilities/globalDefinitions.hpp"
+#include "memory/universe.hpp"
G1HeapSizingPolicy* G1HeapSizingPolicy::create(const G1CollectedHeap* g1h, const G1Analytics* analytics) {
return new G1HeapSizingPolicy(g1h, analytics);
@@ -197,7 +198,7 @@ size_t G1HeapSizingPolicy::young_collection_expansion_amount() {
return expand_bytes;
}
-static size_t target_heap_capacity(size_t used_bytes, uintx free_ratio) {
+static size_t target_heap_capacity(size_t used_bytes, uintx free_ratio, size_t max_heap_size) {
const double desired_free_percentage = (double) free_ratio / 100.0;
const double desired_used_percentage = 1.0 - desired_free_percentage;
@@ -207,7 +208,7 @@ static size_t target_heap_capacity(size_t used_bytes, uintx free_ratio) {
double desired_capacity_d = used_bytes_d / desired_used_percentage;
// Let's make sure that they are both under the max heap size, which
// by default will make it fit into a size_t.
- double desired_capacity_upper_bound = (double) MaxHeapSize;
+ double desired_capacity_upper_bound = (double) max_heap_size;
desired_capacity_d = MIN2(desired_capacity_d, desired_capacity_upper_bound);
// We can now safely turn it into size_t's.
return (size_t) desired_capacity_d;
@@ -226,8 +227,10 @@ size_t G1HeapSizingPolicy::full_collection_resize_amount(bool& expand) {
// results.
_g1h->eden_regions_count() * HeapRegion::GrainBytes;
- size_t minimum_desired_capacity = target_heap_capacity(used_after_gc, MinHeapFreeRatio);
- size_t maximum_desired_capacity = target_heap_capacity(used_after_gc, MaxHeapFreeRatio);
+ size_t max_heap_size = _g1h->current_max_heap_size();
+
+ size_t minimum_desired_capacity = target_heap_capacity(used_after_gc, MinHeapFreeRatio, max_heap_size);
+ size_t maximum_desired_capacity = target_heap_capacity(used_after_gc, MaxHeapFreeRatio, max_heap_size);
// This assert only makes sense here, before we adjust them
// with respect to the min and max heap size.
@@ -239,7 +242,7 @@ size_t G1HeapSizingPolicy::full_collection_resize_amount(bool& expand) {
// Should not be greater than the heap max size. No need to adjust
// it with respect to the heap min size as it's a lower bound (i.e.,
// we'll try to make the capacity larger than it, not smaller).
- minimum_desired_capacity = MIN2(minimum_desired_capacity, MaxHeapSize);
+ minimum_desired_capacity = MIN2(minimum_desired_capacity, max_heap_size);
// Should not be less than the heap min size. No need to adjust it
// with respect to the heap max size as it's an upper bound (i.e.,
// we'll try to make the capacity smaller than it, not greater).
@@ -257,7 +260,27 @@ size_t G1HeapSizingPolicy::full_collection_resize_amount(bool& expand) {
expand = true;
return expand_bytes;
// No expansion, now see if we want to shrink
- } else if (capacity_after_gc > maximum_desired_capacity) {
+ }
+
+ size_t exp_size = _g1h->exp_dynamic_max_heap_size();
+ if (Universe::is_dynamic_max_heap_enable() &&
+ (exp_size > 0) &&
+ (exp_size < _g1h->capacity()) &&
+ (exp_size >= minimum_desired_capacity) &&
+ (exp_size <= maximum_desired_capacity)) {
+ // shrink to exp_dynamic_max_heap_size when
+ // 1. exp_dynamic_max_heap_size smaller than capacity
+ // 2. exp_dynamic_max_heap_size bigger than minimum_desired_capacity
+ size_t shrink_bytes = _g1h->capacity() - exp_size;
+ log_debug(gc, ergo, heap)("Attempt heap shrinking for dynamic max heap(capacity higher than expected dynamic max heap after Full GC)."
+ "Capacity: " SIZE_FORMAT "B occupancy: " SIZE_FORMAT "B "
+ "expected_dynamic_max_heap: " SIZE_FORMAT "B ",
+ capacity_after_gc, used_after_gc, exp_size);
+ expand = false;
+ return shrink_bytes;
+ }
+
+ if (capacity_after_gc > maximum_desired_capacity) {
// Capacity too large, compute shrinking size
size_t shrink_bytes = capacity_after_gc - maximum_desired_capacity;
@@ -27,6 +27,7 @@
#include "gc/g1/g1MemoryPool.hpp"
#include "gc/g1/heapRegion.hpp"
#include "gc/shared/gc_globals.hpp"
+#include "memory/universe.hpp"
G1MemoryPoolSuper::G1MemoryPoolSuper(G1CollectedHeap* g1h,
const char* name,
@@ -61,6 +61,13 @@ protected:
size_t init_size,
size_t max_size,
bool support_usage_threshold);
+ size_t max_size() const override {
+ if (Universe::is_dynamic_max_heap_enable()) {
+ G1CollectedHeap* heap = static_cast<G1CollectedHeap*>(Universe::heap());
+ return heap->max_capacity();
+ }
+ return MemoryPool::max_size();
+ }
};
// Memory pool that represents the G1 eden.
@@ -323,6 +323,14 @@ void G1MonitoringSupport::update_eden_size() {
}
}
+void G1MonitoringSupport::update_max_sizes(size_t size) {
+ if (UsePerfData) {
+ _young_gen_counters->update_max_size(size);
+ _old_gen_counters->update_max_size(size);
+ update_sizes();
+ }
+}
+
MemoryUsage G1MonitoringSupport::eden_space_memory_usage(size_t initial_size, size_t max_size) {
MutexLocker x(MonitoringSupport_lock, Mutex::_no_safepoint_check_flag);
@@ -198,6 +198,9 @@ public:
void update_eden_size();
+ // Dynamic Max Heap
+ void update_max_sizes(size_t size);
+
// Monitoring support used by
// MemoryService
// jstat counters
@@ -49,6 +49,7 @@
#include "utilities/debug.hpp"
#include "utilities/growableArray.hpp"
#include "utilities/pair.hpp"
+#include "memory/universe.hpp"
#include "gc/shared/gcTraceTime.inline.hpp"
@@ -97,7 +98,11 @@ void G1Policy::init(G1CollectedHeap* g1h, G1CollectionSet* collection_set) {
assert(Heap_lock->owned_by_self(), "Locking discipline.");
- _young_gen_sizer.adjust_max_new_size(_g1h->max_regions());
+ if (Universe::is_dynamic_max_heap_enable()) {
+ _young_gen_sizer.adjust_max_new_size(static_cast<uint>(_g1h->current_max_heap_size() / HeapRegion::GrainBytes));
+ } else {
+ _young_gen_sizer.adjust_max_new_size(_g1h->max_regions());
+ }
_free_regions_at_end_of_collection = _g1h->num_free_regions();
@@ -194,3 +194,132 @@ void VM_G1PauseCleanup::work() {
G1CollectedHeap* g1h = G1CollectedHeap::heap();
g1h->concurrent_mark()->cleanup();
}
+
+G1_ChangeMaxHeapOp::G1_ChangeMaxHeapOp(size_t new_max_heap) :
+ VM_ChangeMaxHeapOp(new_max_heap) {
+}
+
+/*
+ * No need calculate young/old size, shrink will adjust young automatically.
+ * ensure young_list_length, _young_list_max_length, _young_list_target_length align.
+ *
+ * 1. check if need perform gc: new_heap_max >= minimum_desired_capacity
+ * 2. perform full GC if necessary
+ * 3. update new limit
+ * 4. validation
+ */
+void G1_ChangeMaxHeapOp::doit() {
+ G1CollectedHeap* heap = static_cast<G1CollectedHeap*>(Universe::heap());
+ const size_t min_heap_size = MinHeapSize;
+ const size_t max_heap_size = heap->current_max_heap_size();
+ bool is_shrink = _new_max_heap < max_heap_size;
+
+ // step1. calculate maximum_used_percentage for shrink validity check
+ const double minimum_free_percentage = static_cast<double>(MinHeapFreeRatio) / 100.0;
+ const double maximum_used_percentage = 1.0 - minimum_free_percentage;
+
+ // step2. trigger GC as needed and resize
+ if (is_shrink) {
+ trigger_gc_shrink(_new_max_heap, maximum_used_percentage, max_heap_size);
+ }
+
+ log_debug(dynamic, heap)("G1_ElasticMaxHeapOp: current capacity " SIZE_FORMAT "K, new max heap " SIZE_FORMAT "K",
+ heap->capacity() / K, _new_max_heap / K);
+
+ // step3. check if can update new limit
+ if (heap->capacity() <= _new_max_heap) {
+ uint dynamic_max_heap_len = static_cast<uint>(_new_max_heap / HeapRegion::GrainBytes);
+ heap->set_current_max_heap_size(_new_max_heap);
+ heap->_hrm.set_dynamic_max_heap_length(dynamic_max_heap_len);
+ // G1 young/old share same max size
+ heap->update_gen_max_counter(_new_max_heap);
+ _resize_success = true;
+ log_debug(dynamic, heap)("G1_ElasticMaxHeapOp success");
+ } else {
+ log_debug(dynamic, heap)("G1_ElasticMaxHeapOp fail");
+ }
+}
+
+bool DynamicMaxHeap_G1CanShrink(double used_after_gc_d, size_t _new_max_heap, double maximum_used_percentage, size_t max_heap_size) {
+ double minimum_desired_capacity_d = used_after_gc_d / maximum_used_percentage;
+ double desired_capacity_upper_bound = static_cast<double>(max_heap_size);
+ minimum_desired_capacity_d = (minimum_desired_capacity_d < desired_capacity_upper_bound) ? minimum_desired_capacity_d : desired_capacity_upper_bound;
+ size_t minimum_desired_capacity = static_cast<size_t>(minimum_desired_capacity_d);
+ minimum_desired_capacity = (minimum_desired_capacity < max_heap_size)? minimum_desired_capacity : max_heap_size;
+ bool can_shrink = (_new_max_heap >= minimum_desired_capacity);
+ return can_shrink;
+}
+
+void G1_ChangeMaxHeapOp::trigger_gc_shrink(size_t _new_max_heap,
+ double maximum_used_percentage,
+ size_t max_heap_size){
+ G1CollectedHeap* heap = static_cast<G1CollectedHeap*>(Universe::heap());
+ G1CollectorState* collector_state = heap->collector_state();
+ bool triggered_full_gc = false;
+ bool can_shrink = DynamicMaxHeap_G1CanShrink(static_cast<double>(heap->used()), _new_max_heap, maximum_used_percentage, max_heap_size);
+ if (!can_shrink) {
+ // trigger Young GC
+ collector_state->set_in_young_only_phase(true);
+ collector_state->set_in_young_gc_before_mixed(true);
+ GCCauseSetter gccs(heap, _gc_cause);
+ bool minor_gc_succeeded = heap->do_collection_pause_at_safepoint();
+ if (minor_gc_succeeded) {
+ log_debug(dynamic, heap)("G1_ElasticMaxHeapOp heap after Young GC");
+ LogTarget(Debug, dynamic, heap) lt;
+ if (lt.is_enabled()) {
+ LogStream ls(lt);
+ heap->print_on(&ls);
+ }
+ }
+ can_shrink = DynamicMaxHeap_G1CanShrink(static_cast<double>(heap->used()), _new_max_heap, maximum_used_percentage, max_heap_size);
+ if (!can_shrink) {
+ // trigger Full GC and adjust everything in resize_if_necessary_after_full_collection
+ heap->set_exp_dynamic_max_heap_size(_new_max_heap);
+ heap->do_full_collection(true);
+ log_debug(dynamic, heap)("G1_ElasticMaxHeapOp heap after Full GC");
+ LogTarget(Debug, dynamic, heap) lt;
+ if (lt.is_enabled()) {
+ LogStream ls(lt);
+ heap->print_on(&ls);
+ }
+ heap->set_exp_dynamic_max_heap_size(0);
+ triggered_full_gc = true;
+ }
+ }
+
+ if (!triggered_full_gc) {
+ // there may be two situations when entering this branch:
+ // 1. first check passed, no GC triggered
+ // 2. first check failed, triggered Young GC,
+ // second check passed
+ // so the shrink has not been completed and it must be valid to shrink
+ g1_shrink_without_full_gc(_new_max_heap);
+ }
+}
+
+void G1_ChangeMaxHeapOp::g1_shrink_without_full_gc(size_t _new_max_heap) {
+ G1CollectedHeap* heap = static_cast<G1CollectedHeap*>(Universe::heap());
+ size_t capacity_before_shrink = heap->capacity();
+ // _new_max_heap is large enough, do nothing
+ if (_new_max_heap >= capacity_before_shrink) {
+ return;
+ }
+ // Capacity too large, compute shrinking size and shrink
+ size_t shrink_bytes = capacity_before_shrink - _new_max_heap;
+ heap->_verifier->verify_region_sets_optional();
+ heap->_hrm.remove_all_free_regions();
+ heap->shrink_helper(shrink_bytes);
+ heap->rebuild_region_sets(true /* free_list_only */, true /* is_dynamic_max_heap_shrink */);
+ heap->_hrm.verify_optional();
+ heap->_verifier->verify_region_sets_optional();
+ heap->_verifier->verify_after_gc();
+
+ log_debug(dynamic, heap)("G1_ElasticMaxHeapOp: attempt heap shrinking for dynamic max heap %s "
+ "origin capacity " SIZE_FORMAT "K "
+ "new capacity " SIZE_FORMAT "K "
+ "shrink by " SIZE_FORMAT "K",
+ heap->capacity() <= _new_max_heap ? "success" : "fail",
+ capacity_before_shrink / K,
+ heap->capacity() / K,
+ shrink_bytes / K);
+}
\ No newline at end of file
@@ -27,6 +27,7 @@
#include "gc/shared/gcId.hpp"
#include "gc/shared/gcVMOperations.hpp"
+#include "gc/shared/dynamicMaxHeap.hpp"
// VM_operations for the G1 collector.
@@ -109,4 +110,15 @@ public:
void work() override;
};
+// Change Dynamic Max Heap Size
+class G1_ChangeMaxHeapOp : public VM_ChangeMaxHeapOp {
+public:
+ G1_ChangeMaxHeapOp(size_t new_max_heap);
+ void doit() override;
+ void trigger_gc_shrink(size_t _new_max_heap,
+ double maximum_used_percentage,
+ size_t max_heap_size);
+ void g1_shrink_without_full_gc(size_t _new_max_heap);
+};
+
#endif // SHARE_GC_G1_G1VMOPERATIONS_HPP
@@ -34,6 +34,7 @@
#include "jfr/jfrEvents.hpp"
#include "logging/logStream.hpp"
#include "memory/allocation.hpp"
+#include "memory/universe.hpp"
#include "runtime/atomic.hpp"
#include "runtime/mutexLocker.hpp"
#include "runtime/orderAccess.hpp"
@@ -86,6 +87,8 @@ void HeapRegionManager::initialize(G1RegionToSpaceMapper* heap_storage,
_regions.initialize(heap_storage->reserved(), HeapRegion::GrainBytes);
+ _dynamic_max_heap_length = static_cast<uint>(MaxHeapSize / HeapRegion::GrainBytes);
+
_committed_map.initialize(reserved_length());
}
@@ -313,12 +316,15 @@ uint HeapRegionManager::expand_inactive(uint num_regions) {
do {
HeapRegionRange regions = _committed_map.next_inactive_range(offset);
- if (regions.length() == 0) {
+ if (regions.length() == 0 || available() == 0) {
// No more unavailable regions.
break;
}
uint to_expand = MIN2(num_regions - expanded, regions.length());
+ if (Universe::is_dynamic_max_heap_enable()) {
+ to_expand = MIN2(to_expand, available());
+ }
reactivate_regions(regions.start(), to_expand);
expanded += to_expand;
offset = regions.end();
@@ -335,12 +341,15 @@ uint HeapRegionManager::expand_any(uint num_regions, WorkerThreads* pretouch_wor
do {
HeapRegionRange regions = _committed_map.next_committable_range(offset);
- if (regions.length() == 0) {
+ if (regions.length() == 0 || available() == 0) {
// No more unavailable regions.
break;
}
uint to_expand = MIN2(num_regions - expanded, regions.length());
+ if (Universe::is_dynamic_max_heap_enable()) {
+ to_expand = MIN2(to_expand, available());
+ }
expand(regions.start(), to_expand, pretouch_workers);
expanded += to_expand;
offset = regions.end();
@@ -352,6 +361,13 @@ uint HeapRegionManager::expand_any(uint num_regions, WorkerThreads* pretouch_wor
uint HeapRegionManager::expand_by(uint num_regions, WorkerThreads* pretouch_workers) {
assert(num_regions > 0, "Must expand at least 1 region");
+ if (Universe::is_dynamic_max_heap_enable()) {
+ uint available_regions = available();
+ guarantee(dynamic_max_heap_length() >= length(), "The current length must not exceed dynamic max heap length");
+ guarantee(available_regions <= max_length() && available_regions <= dynamic_max_heap_length(), "must be");
+ num_regions = MIN2(num_regions, available_regions);
+ }
+
// First "undo" any requests to uncommit memory concurrently by
// reverting such regions to being available.
uint expanded = expand_inactive(num_regions);
@@ -367,6 +383,14 @@ uint HeapRegionManager::expand_by(uint num_regions, WorkerThreads* pretouch_work
void HeapRegionManager::expand_exact(uint start, uint num_regions, WorkerThreads* pretouch_workers) {
assert(num_regions != 0, "Need to request at least one region");
+
+ if (Universe::is_dynamic_max_heap_enable()) {
+ uint available_regions = available();
+ guarantee(dynamic_max_heap_length() >= length(), "The current length must not exceed dynamic max heap length");
+ guarantee(available_regions <= max_length() && available_regions <= dynamic_max_heap_length(), "must be");
+ num_regions = MIN2(num_regions, available_regions);
+ }
+
uint end = start + num_regions;
for (uint i = start; i < end; i++) {
@@ -535,7 +559,7 @@ uint HeapRegionManager::find_highest_free(bool* expanded) {
// committed, expand at that index.
for (uint curr = reserved_length(); curr-- > 0;) {
HeapRegion *hr = _regions.get_by_index(curr);
- if (hr == nullptr || !is_available(curr)) {
+ if ((hr == nullptr || !is_available(curr)) && available() >= 1) {
// Found uncommitted and free region, expand to make it available for use.
expand_exact(curr, 1, nullptr);
assert(at(curr)->is_free(), "Region (%u) must be available and free after expand", curr);
@@ -559,7 +583,7 @@ bool HeapRegionManager::allocate_containing_regions(MemRegion range, size_t* com
// Ensure that each G1 region in the range is free, returning false if not.
// Commit those that are not yet available, and keep count.
for (uint curr_index = start_index; curr_index <= last_index; curr_index++) {
- if (!is_available(curr_index)) {
+ if (!is_available(curr_index) && available() >= 1) {
commits++;
expand_exact(curr_index, 1, pretouch_workers);
}
@@ -84,6 +84,9 @@ class HeapRegionManager: public CHeapObj<mtGC> {
// Internal only. The highest heap region +1 we allocated a HeapRegion instance for.
uint _allocated_heapregions_length;
+ // The max number of regions controlled by Dynamic Max Heap
+ uint _dynamic_max_heap_length;
+
HeapWord* heap_bottom() const { return _regions.bottom_address_mapped(); }
HeapWord* heap_end() const {return _regions.end_address_mapped(); }
@@ -230,7 +233,12 @@ public:
}
// Return the number of regions available (uncommitted) regions.
- uint available() const { return max_length() - length(); }
+ uint available() const {
+ if(Universe::is_dynamic_max_heap_enable()) {
+ return dynamic_max_heap_length() - length();
+ }
+ return max_length() - length();
+ }
// Return the number of regions currently active and available for use.
uint length() const { return _committed_map.num_active(); }
@@ -241,6 +249,14 @@ public:
// Return maximum number of regions that heap can expand to.
uint max_length() const { return reserved_length(); }
+ // Return the current maximum number of regions in the heap (dynamic max heap).
+ uint dynamic_max_heap_length() const { return _dynamic_max_heap_length; }
+
+ void set_dynamic_max_heap_length(uint len) {
+ guarantee(len <= max_length(), "must be");
+ _dynamic_max_heap_length = len;
+ }
+
MemoryUsage get_auxiliary_data_memory_usage() const;
MemRegion reserved() const { return MemRegion(heap_bottom(), heap_end()); }
@@ -36,6 +36,7 @@
#include "runtime/java.hpp"
#include "utilities/defaultStream.hpp"
#include "utilities/powerOfTwo.hpp"
+#include "memory/universe.hpp"
size_t ParallelArguments::conservative_max_heap_alignment() {
return compute_heap_alignment();
@@ -137,6 +138,11 @@ void ParallelArguments::initialize_heap_flags_and_sizes() {
}
size_t ParallelArguments::heap_reserved_size_bytes() {
+#ifdef AARCH64
+ if (Universe::is_dynamic_max_heap_enable()) {
+ return DynamicMaxHeapSizeLimit;
+ }
+#endif //AARCH64
return MaxHeapSize;
}
@@ -42,6 +42,7 @@
#include "gc/shared/locationPrinter.inline.hpp"
#include "gc/shared/scavengableNMethods.hpp"
#include "gc/shared/suspendibleThreadSet.hpp"
+#include "gc/shared/dynamicMaxHeap.hpp"
#include "logging/log.hpp"
#include "memory/iterator.hpp"
#include "memory/metaspaceCounters.hpp"
@@ -62,6 +63,12 @@ PSAdaptiveSizePolicy* ParallelScavengeHeap::_size_policy = nullptr;
PSGCAdaptivePolicyCounters* ParallelScavengeHeap::_gc_policy_counters = nullptr;
jint ParallelScavengeHeap::initialize() {
+#ifdef AARCH64
+ if (Universe::is_dynamic_max_heap_enable() && !FLAG_IS_CMDLINE(DynamicMaxHeapSizeLimit) && !FLAG_IS_CMDLINE(ElasticMaxHeapSize)) {
+ guarantee(ElasticMaxHeap, "must be");
+ FLAG_SET_ERGO(DynamicMaxHeapSizeLimit, MaxHeapSize);
+ }
+#endif //AARCH64
const size_t reserved_heap_size = ParallelArguments::heap_reserved_size_bytes();
ReservedHeapSpace heap_rs = Universe::reserve_heap(reserved_heap_size, HeapAlignment);
@@ -70,9 +77,13 @@ jint ParallelScavengeHeap::initialize() {
initialize_reserved_region(heap_rs);
// Layout the reserved space for the generations.
- ReservedSpace old_rs = heap_rs.first_part(MaxOldSize);
- ReservedSpace young_rs = heap_rs.last_part(MaxOldSize);
- assert(young_rs.size() == MaxNewSize, "Didn't reserve all of the heap");
+ size_t max_old_size = MaxOldSize;
+ if (Universe::is_dynamic_max_heap_enable()) {
+ max_old_size = GenArguments::max_old_size(reserved_heap_size);
+ }
+ ReservedSpace old_rs = heap_rs.first_part(max_old_size);
+ ReservedSpace young_rs = heap_rs.last_part(max_old_size);
+ assert(young_rs.size() == MaxNewSize || Universe::is_dynamic_max_heap_enable(), "Didn't reserve all of the heap");
PSCardTable* card_table = new PSCardTable(heap_rs.region());
card_table->initialize(old_rs.base(), young_rs.base());
@@ -97,8 +108,8 @@ jint ParallelScavengeHeap::initialize() {
MaxOldSize,
"old", 1);
- assert(young_gen()->max_gen_size() == young_rs.size(),"Consistency check");
- assert(old_gen()->max_gen_size() == old_rs.size(), "Consistency check");
+ assert(young_gen()->max_gen_size() == young_rs.size() || Universe::is_dynamic_max_heap_enable(),"Consistency check");
+ assert(old_gen()->max_gen_size() == old_rs.size() || Universe::is_dynamic_max_heap_enable(), "Consistency check");
double max_gc_pause_sec = ((double) MaxGCPauseMillis)/1000.0;
double max_gc_minor_pause_sec = ((double) MaxGCMinorPauseMillis)/1000.0;
@@ -211,6 +222,12 @@ bool ParallelScavengeHeap::is_maximal_no_gc() const {
size_t ParallelScavengeHeap::max_capacity() const {
size_t estimated = reserved_region().byte_size();
+ // Dynamic Max Heap
+ if (Universe::is_dynamic_max_heap_enable()) {
+ // young_gen()->max_size() is also controlled by DynamicMaxHeap
+ guarantee(current_max_heap_size() <= estimated, "must be");
+ estimated = current_max_heap_size();
+ }
if (UseAdaptiveSizePolicy) {
estimated -= _size_policy->max_survivor_size(young_gen()->max_gen_size());
} else {
@@ -888,3 +905,10 @@ void ParallelScavengeHeap::pin_object(JavaThread* thread, oop obj) {
void ParallelScavengeHeap::unpin_object(JavaThread* thread, oop obj) {
GCLocker::unlock_critical(thread);
}
+
+bool ParallelScavengeHeap::change_max_heap(size_t new_size) {
+ assert(!Heap_lock->owned_by_self(), "this thread should not own the Heap_lock");
+ PS_ChangeMaxHeapOp op(new_size);
+ VMThread::execute(&op);
+ return op.resize_success();
+}
@@ -41,6 +41,7 @@
#include "logging/log.hpp"
#include "utilities/growableArray.hpp"
#include "utilities/ostream.hpp"
+#include "memory/universe.hpp"
class GCHeapSummary;
class HeapBlockClaimer;
@@ -276,6 +277,9 @@ class ParallelScavengeHeap : public CollectedHeap {
void pin_object(JavaThread* thread, oop obj) override;
void unpin_object(JavaThread* thread, oop obj) override;
+
+ // Dynamic Max Heap
+ bool change_max_heap(size_t new_size) override;
};
// Class that can be used to print information about the
@@ -27,6 +27,7 @@
#include "gc/parallel/psGenerationCounters.hpp"
#include "memory/allocation.inline.hpp"
#include "memory/resourceArea.hpp"
+#include "memory/universe.hpp"
PSGenerationCounters::PSGenerationCounters(const char* name,
int ordinal, int spaces,
@@ -57,8 +58,15 @@ PSGenerationCounters::PSGenerationCounters(const char* name,
min_capacity, CHECK);
cname = PerfDataManager::counter_name(_name_space, "maxCapacity");
- PerfDataManager::create_constant(SUN_GC, cname, PerfData::U_Bytes,
+ // Dynamic Max Heap
+ if (Universe::is_dynamic_max_heap_enable()) {
+ _max_size = PerfDataManager::create_variable(SUN_GC, cname,
+ PerfData::U_Bytes, max_capacity, CHECK);
+ } else {
+ _max_size = NULL;
+ PerfDataManager::create_constant(SUN_GC, cname, PerfData::U_Bytes,
max_capacity, CHECK);
+ }
cname = PerfDataManager::counter_name(_name_space, "capacity");
_current_size = PerfDataManager::create_variable(SUN_GC, cname,
@@ -40,7 +40,7 @@ public:
MemoryUsage get_memory_usage();
size_t used_in_bytes() { return _old_gen->used_in_bytes(); }
- size_t max_size() const { return _old_gen->reserved().byte_size(); }
+ size_t max_size() const { return _old_gen->max_gen_size(); }
};
class EdenMutableSpacePool : public CollectedMemoryPool {
@@ -36,11 +36,13 @@
#include "oops/oop.inline.hpp"
#include "runtime/java.hpp"
#include "utilities/align.hpp"
+#include "memory/universe.hpp"
PSOldGen::PSOldGen(ReservedSpace rs, size_t initial_size, size_t min_size,
size_t max_size, const char* perf_data_name, int level):
_min_gen_size(min_size),
- _max_gen_size(max_size)
+ _max_gen_size(Universe::is_dynamic_max_heap_enable() ? rs.size() : max_size),
+ _cur_max_gen_size(Universe::is_dynamic_max_heap_enable() ? max_size : -1)
{
initialize(rs, initial_size, GenAlignment, perf_data_name, level);
}
@@ -58,6 +60,9 @@ void PSOldGen::initialize_virtual_space(ReservedSpace rs,
size_t alignment) {
_virtual_space = new PSVirtualSpace(rs, alignment);
+ if (Universe::is_dynamic_max_heap_enable()) {
+ _virtual_space->set_dynamic_max_heap_size(_cur_max_gen_size);
+ }
if (!_virtual_space->expand_by(initial_size)) {
vm_exit_during_initialization("Could not reserve enough space for "
"object heap");
@@ -66,7 +71,7 @@ void PSOldGen::initialize_virtual_space(ReservedSpace rs,
void PSOldGen::initialize_work(const char* perf_data_name, int level) {
MemRegion const reserved_mr = reserved();
- assert(reserved_mr.byte_size() == max_gen_size(), "invariant");
+ assert(reserved_mr.byte_size() == max_gen_size() || Universe::is_dynamic_max_heap_enable(), "invariant");
// Object start stuff: for all reserved memory
start_array()->initialize(reserved_mr);
@@ -32,6 +32,7 @@
#include "gc/parallel/spaceCounters.hpp"
#include "runtime/mutexLocker.hpp"
#include "runtime/safepoint.hpp"
+#include "memory/universe.hpp"
class PSOldGen : public CHeapObj<mtGC> {
friend class VMStructs;
@@ -48,6 +49,9 @@ class PSOldGen : public CHeapObj<mtGC> {
const size_t _min_gen_size;
const size_t _max_gen_size;
+ // For Dynamic Max Heap
+ size_t _cur_max_gen_size;
+
// Block size for parallel iteration
static const size_t IterateBlockSize = 1024 * 1024;
@@ -108,9 +112,27 @@ class PSOldGen : public CHeapObj<mtGC> {
(HeapWord*)(_virtual_space->high()));
}
- size_t max_gen_size() const { return _max_gen_size; }
+ size_t max_gen_size() const {
+ if (Universe::is_dynamic_max_heap_enable()) {
+ guarantee(_cur_max_gen_size <= _max_gen_size && _cur_max_gen_size >= _min_gen_size, "must be");
+ return _cur_max_gen_size;
+ }
+ return _max_gen_size;
+ }
size_t min_gen_size() const { return _min_gen_size; }
+ // Dynamic Max Heap
+ void set_cur_max_gen_size(size_t new_size) {
+ guarantee(Universe::is_dynamic_max_heap_enable(), "must be");
+ guarantee(new_size <= _max_gen_size && new_size >= _min_gen_size, "must be");
+ guarantee(_max_gen_size == _virtual_space->reserved_size(), "must be");
+ _cur_max_gen_size = new_size;
+ _virtual_space->set_dynamic_max_heap_size(new_size);
+ if (UsePerfData) {
+ _gen_counters->update_max_size(new_size);
+ }
+ }
+
bool is_in(const void* p) const {
return _virtual_space->is_in_committed((void *)p);
}
@@ -27,6 +27,7 @@
#include "gc/parallel/psScavenge.hpp"
#include "gc/parallel/psVMOperations.hpp"
#include "gc/shared/gcLocker.hpp"
+#include "gc/shared/genArguments.hpp"
#include "utilities/dtrace.hpp"
// The following methods are used by the parallel scavenge collector
@@ -76,3 +77,254 @@ void VM_ParallelGCSystemGC::doit() {
_full_gc_succeeded = PSParallelCompact::invoke(false);
}
}
+
+PS_ChangeMaxHeapOp::PS_ChangeMaxHeapOp(size_t new_max_heap) :
+ VM_ChangeMaxHeapOp(new_max_heap)
+{}
+
+bool DynamicMaxHeap_PsOldGenCanShrink(size_t _new_max_heap, size_t old_used_bytes, double min_heap_free_ration, size_t alignment) {
+ double ratio = min_heap_free_ration / 100.0;
+ double ratio_inverse = 1.0 - ratio;
+ double tmp = old_used_bytes * ratio;
+ size_t min_free = static_cast<size_t>(tmp / ratio_inverse);
+ // align_up(min_free, alignment) alignment "must be a power of 2
+ min_free = (min_free + alignment - 1) & ~(alignment - 1);
+ bool can_shrink = (_new_max_heap >= (old_used_bytes + min_free));
+ return can_shrink;
+}
+
+/*
+ * 1. calculate new young/old gen limit size.
+ * 2. trigger Full GC if necessary
+ * 3. check and reset new limitation
+ */
+void PS_ChangeMaxHeapOp::doit() {
+ ParallelScavengeHeap* heap = static_cast<ParallelScavengeHeap*>(Universe::heap());
+ assert(heap->kind() == CollectedHeap::Parallel, "must be a ParallelScavengeHeap");
+
+ // step 1
+ PSOldGen* old_gen = heap->old_gen();
+ PSYoungGen* young_gen = heap->young_gen();
+ size_t cur_heap_limit = heap->current_max_heap_size();
+ size_t cur_old_limit = old_gen->max_gen_size();
+ size_t cur_young_limit = young_gen->max_gen_size();
+ bool is_shrink = _new_max_heap < cur_heap_limit;
+
+ const size_t young_reserved_size = young_gen->reserved().byte_size();
+ const size_t young_min_size = young_gen->min_gen_size();
+ const size_t old_reserved_size = old_gen->reserved().byte_size();
+ const size_t old_min_size = old_gen->min_gen_size();
+
+ guarantee(cur_old_limit + cur_young_limit == cur_heap_limit, "must be");
+
+ // fix with young gen size limitation
+ size_t new_young_limit = GenArguments::scale_by_NewRatio_aligned(_new_max_heap, GenAlignment);
+ new_young_limit = MIN2(new_young_limit, young_reserved_size);
+ new_young_limit = MAX2(new_young_limit, young_min_size);
+ // align shrink/expand direction
+ if ((is_shrink && (new_young_limit > cur_young_limit)) ||
+ (!is_shrink && (new_young_limit < cur_young_limit))) {
+ new_young_limit = cur_young_limit;
+ }
+ size_t new_old_limit = _new_max_heap - new_young_limit;
+
+ if (new_old_limit > old_reserved_size) {
+ new_old_limit = old_reserved_size;
+ new_young_limit = _new_max_heap - new_old_limit;
+ }
+
+ // keep the new_old_limit aligned with shrink/expand direction
+ if ((is_shrink && (new_old_limit > cur_old_limit)) ||
+ (!is_shrink && (new_old_limit < cur_old_limit))) {
+ new_old_limit = cur_old_limit;
+ new_young_limit = _new_max_heap - new_old_limit;
+ }
+
+ // After the final calcuation, check the leagle limit
+ if ((new_old_limit < old_min_size) ||
+ (new_old_limit > old_reserved_size) ||
+ (new_young_limit < young_min_size) ||
+ (new_young_limit > young_reserved_size)) {
+ log_debug(dynamic, heap)("PS_ElasticMaxHeapOp abort: can not calculate new legal limit:"
+ " new_old_limit: " SIZE_FORMAT "K, " "old gen min size: " SIZE_FORMAT "K, old gen reserved size: " SIZE_FORMAT "K"
+ " new_young_limit: " SIZE_FORMAT "K, " "young gen min size: " SIZE_FORMAT "K, young gen reserved size: " SIZE_FORMAT "K" ,
+ (new_old_limit / K), (old_min_size / K), (old_reserved_size / K),
+ (new_young_limit / K), (young_min_size / K), (young_reserved_size / K));
+ return;
+ }
+
+ log_debug(dynamic, heap)("PS_ElasticMaxHeapOp plan: "
+ "desired young gen size (" SIZE_FORMAT "K" "->" SIZE_FORMAT "K), "
+ "desired old gen size (" SIZE_FORMAT "K" "->" SIZE_FORMAT "K)",
+ (cur_young_limit / K),
+ (new_young_limit / K),
+ (cur_old_limit / K),
+ (new_old_limit / K));
+ if (is_shrink) {
+ guarantee(new_old_limit <= cur_old_limit && new_young_limit <= cur_young_limit, "must be");
+ } else {
+ guarantee(new_old_limit >= cur_old_limit && new_young_limit >= cur_young_limit, "must be");
+ }
+
+ // step2
+ // Check resize legality
+ if (is_shrink) {
+ // check whether old/young can be resized, trigger full gc as needed
+ double min_heap_free_ration = MinHeapFreeRatio;
+#ifdef AARCH64
+ if (min_heap_free_ration == 0) {
+ min_heap_free_ration = DynamicMaxHeapShrinkMinFreeRatio;
+ }
+#endif //AARCH64
+ bool can_shrink = DynamicMaxHeap_PsOldGenCanShrink(new_old_limit,
+ heap->old_gen()->used_in_bytes(),
+ min_heap_free_ration,
+ heap->old_gen()->virtual_space()->alignment());
+ if (can_shrink) {
+ can_shrink = (new_young_limit >= heap->young_gen()->virtual_space()->committed_size());
+ }
+ if (!can_shrink) {
+ GCCauseSetter gccs(heap, _gc_cause);
+ heap->do_full_collection(true);
+ log_debug(dynamic, heap)("PS_ElasticMaxHeapOp heap after Full GC");
+ LogTarget(Debug, dynamic, heap) lt;
+ if (lt.is_enabled()) {
+ LogStream ls(lt);
+ heap->print_on(&ls);
+ }
+ if (young_gen->used_in_bytes() != 0) {
+ log_debug(dynamic, heap)("PS_ElasticMaxHeapOp abort: young is not empty after full gc");
+ return;
+ }
+ }
+
+ can_shrink = DynamicMaxHeap_PsOldGenCanShrink(new_old_limit,
+ heap->old_gen()->used_in_bytes(),
+ min_heap_free_ration,
+ heap->old_gen()->virtual_space()->alignment());
+ if (!can_shrink) {
+ log_debug(dynamic, heap)("PS_ElasticMaxHeapOp abort: not enough old free for shrink");
+ return;
+ }
+
+ // step3
+ // shrink generation committed size if needed
+ // 1. old gen
+ // 1 old gen can shrink capacity without full gc
+ // 2 old gen have passed shrink valid check since the code is executed here
+ // 3 old gen can shrink capacity if needed
+ // 2. young gen
+ // 1 young gen must shrink capacity after full gc
+ // 2 there may be three situations after shrink valid check in step2
+ // 1) both old gen and young gen have passed the check,
+ // indicating new_young_limit is big enough,
+ // there is no need to shrink capacity
+ // 2) old gen failed the check and triggered full gc
+ // 3) young gen failed the check and triggered full gc
+
+ if (old_gen->capacity_in_bytes() > new_old_limit) {
+ size_t desired_free = new_old_limit - old_gen->used_in_bytes();
+ char* old_high = old_gen->virtual_space()->committed_high_addr();
+ old_gen->resize(desired_free);
+ char* new_old_high = old_gen->virtual_space()->committed_high_addr();
+ if (old_gen->capacity_in_bytes() > new_old_limit) {
+ log_debug(dynamic, heap)("PS_ElasticMaxHeapOp abort: resize old fail " SIZE_FORMAT "K",
+ old_gen->capacity_in_bytes() / K);
+ return;
+ }
+ log_debug(dynamic, heap)("PS_ElasticMaxHeapOp continue: shrink old success " SIZE_FORMAT "K",
+ old_gen->capacity_in_bytes() / K);
+ if (old_high > new_old_high) {
+ // shrink is caused by dynamic max heap, free physical memory
+ size_t shrink_bytes = old_high - new_old_high;
+ guarantee((shrink_bytes > 0) && (shrink_bytes % os::vm_page_size() == 0), "should be");
+ bool result = os::free_heap_physical_memory(new_old_high, shrink_bytes);
+ guarantee(result, "free heap physical memory should be successful");
+ }
+ }
+
+ if (young_gen->virtual_space()->committed_size() > new_young_limit) {
+ // entering this branch means full gc must have been triggered
+ guarantee(young_gen->eden_space()->is_empty() &&
+ young_gen->to_space()->is_empty() &&
+ young_gen->from_space()->is_empty(),
+ "must be empty");
+
+ char* young_high = young_gen->virtual_space()->committed_high_addr();
+ if (young_gen->shrink_after_full_gc(new_young_limit) == false) {
+ log_debug(dynamic, heap)("PS_ElasticMaxHeapOp abort: shrink young fail");
+ return;
+ }
+ char* new_young_high = young_gen->virtual_space()->committed_high_addr();
+ log_debug(dynamic, heap)("PS_ElasticMaxHeapOp continue: shrink young success " SIZE_FORMAT "K",
+ young_gen->virtual_space()->committed_size() / K);
+ if (young_high > new_young_high) {
+ // shrink is caused by dynamic max heap, free physical memory
+ size_t shrink_bytes = young_high - new_young_high;
+ guarantee((shrink_bytes > 0) && (shrink_bytes % os::vm_page_size() == 0), "should be");
+ bool result = os::free_heap_physical_memory(new_young_high, shrink_bytes);
+ guarantee(result, "free heap physical memory should be successful");
+ }
+ }
+ }
+ // update young/old gen limit, avoid further expand
+ old_gen->set_cur_max_gen_size(new_old_limit);
+ young_gen->set_cur_max_gen_size(new_young_limit);
+ heap->set_current_max_heap_size(_new_max_heap);
+ _resize_success = true;
+ log_debug(dynamic, heap)("PS_ElasticMaxHeapOp success");
+}
+
+// Resize for DynamicHeapSize, shrink to new_size
+bool PSYoungGen::shrink_after_full_gc(size_t new_size) {
+ const size_t alignment = virtual_space()->alignment();
+ ParallelScavengeHeap* heap = static_cast<ParallelScavengeHeap*>(Universe::heap());
+ size_t orig_size = virtual_space()->committed_size();
+ guarantee(eden_space()->is_empty() && to_space()->is_empty() && from_space()->is_empty(), "must be empty");
+ guarantee(new_size % alignment == 0, "must be");
+ guarantee(new_size < orig_size, "must be");
+
+ // shrink virtual space
+ size_t shrink_bytes = virtual_space()->committed_size() - new_size;
+ bool success = virtual_space()->shrink_by(shrink_bytes);
+ log_debug(dynamic, heap)("PSYoungGen::shrink_after_full_gc: shrink virtual space %s "
+ "orig committed " SIZE_FORMAT "K "
+ "current committed " SIZE_FORMAT "K "
+ "shrink by " SIZE_FORMAT "K",
+ success ? "success" : "fail",
+ orig_size / K,
+ virtual_space()->committed_size() / K,
+ shrink_bytes / K);
+
+ if (!success) {
+ return false;
+ }
+
+ // caculate new eden/survivor size
+ // shrink with same ratio, let size policy adjust later
+ size_t current_survivor_ratio = eden_space()->capacity_in_bytes() / from_space()->capacity_in_bytes();
+ current_survivor_ratio = MAX2(current_survivor_ratio, static_cast<size_t>(1));
+ size_t new_survivor_size = new_size / (current_survivor_ratio + 2);
+ new_survivor_size = align_down(new_survivor_size, SpaceAlignment);
+ new_survivor_size = MAX2(new_survivor_size, SpaceAlignment);
+ size_t new_eden_size = new_size - 2 * new_survivor_size;
+
+ guarantee(new_eden_size % SpaceAlignment == 0, "must be");
+ log_debug(dynamic, heap)("PSYoungGen::shrink_after_full_gc: "
+ "new eden size " SIZE_FORMAT "K "
+ "new survivor size " SIZE_FORMAT "K "
+ "new young gen size " SIZE_FORMAT "K",
+ new_eden_size / K,
+ new_survivor_size / K,
+ new_size / K);
+
+ // setup new eden/survivor space
+ set_space_boundaries(new_eden_size, new_survivor_size);
+ post_resize();
+ LogTarget(Debug, dynamic, heap) lt;
+ if (lt.is_enabled()) {
+ LogStream ls(lt);
+ print_on(&ls);
+ }
+ return true;
+}
\ No newline at end of file
@@ -28,6 +28,7 @@
#include "gc/parallel/parallelScavengeHeap.hpp"
#include "gc/shared/gcCause.hpp"
#include "gc/shared/gcVMOperations.hpp"
+#include "gc/shared/dynamicMaxHeap.hpp"
class VM_ParallelGCFailedAllocation : public VM_CollectForAllocation {
public:
@@ -48,4 +49,11 @@ class VM_ParallelGCSystemGC: public VM_GC_Operation {
bool full_gc_succeeded() const { return _full_gc_succeeded; }
};
+// For ParallelScavengeHeap
+class PS_ChangeMaxHeapOp : public VM_ChangeMaxHeapOp {
+public:
+ PS_ChangeMaxHeapOp(size_t new_max_heap);
+ void doit() override;
+};
+
#endif // SHARE_GC_PARALLEL_PSVMOPERATIONS_HPP
@@ -31,7 +31,8 @@
// PSVirtualSpace
PSVirtualSpace::PSVirtualSpace(ReservedSpace rs, size_t alignment) :
- _alignment(alignment)
+ _alignment(alignment),
+ _dynamic_max_heap_size(0)
{
set_reserved(rs);
set_committed(reserved_low_addr(), reserved_low_addr());
@@ -45,11 +46,13 @@ PSVirtualSpace::PSVirtualSpace():
_reserved_high_addr(nullptr),
_committed_low_addr(nullptr),
_committed_high_addr(nullptr),
- _special(false) {
+ _special(false),
+ _dynamic_max_heap_size(0) {
}
// Deprecated.
void PSVirtualSpace::initialize(ReservedSpace rs) {
+ _dynamic_max_heap_size = 0;
set_reserved(rs);
set_committed(reserved_low_addr(), reserved_low_addr());
DEBUG_ONLY(verify());
@@ -27,6 +27,7 @@
#include "memory/allocation.hpp"
#include "memory/virtualspace.hpp"
+#include "memory/universe.hpp"
// VirtualSpace for the parallel scavenge collector.
//
@@ -52,6 +53,9 @@ class PSVirtualSpace : public CHeapObj<mtGC> {
// os::commit_memory() or os::uncommit_memory().
bool _special;
+ // Dynamic Max Heap
+ size_t _dynamic_max_heap_size;
+
public:
PSVirtualSpace(ReservedSpace rs, size_t alignment);
@@ -88,6 +92,17 @@ class PSVirtualSpace : public CHeapObj<mtGC> {
virtual bool expand_by(size_t bytes);
virtual bool shrink_by(size_t bytes);
void release();
+ // Dynamic Max Heap
+ void set_dynamic_max_heap_size(size_t new_size) {
+ guarantee(new_size <= reserved_size(), "must be");
+ guarantee(new_size >= committed_size(), "must be");
+ _dynamic_max_heap_size = new_size;
+ }
+ size_t dynamic_max_heap_size() const {
+ guarantee(_dynamic_max_heap_size <= reserved_size(), "must be");
+ guarantee(_dynamic_max_heap_size >= committed_size(), "must be");
+ return _dynamic_max_heap_size;
+ }
#ifndef PRODUCT
// Debugging
@@ -131,6 +146,9 @@ inline size_t PSVirtualSpace::reserved_size() const {
}
inline size_t PSVirtualSpace::uncommitted_size() const {
+ if (Universe::is_dynamic_max_heap_enable()) {
+ return dynamic_max_heap_size() - committed_size();
+ }
return reserved_size() - committed_size();
}
@@ -138,6 +156,10 @@ inline void PSVirtualSpace::set_reserved(char* low_addr, char* high_addr, bool s
_reserved_low_addr = low_addr;
_reserved_high_addr = high_addr;
_special = special;
+ if (Universe::is_dynamic_max_heap_enable()) {
+ guarantee(_dynamic_max_heap_size == 0, "resize virtual NYI");
+ _dynamic_max_heap_size = high_addr - low_addr;
+ }
}
inline void PSVirtualSpace::set_reserved(ReservedSpace rs) {
@@ -34,6 +34,7 @@
#include "oops/oop.inline.hpp"
#include "runtime/java.hpp"
#include "utilities/align.hpp"
+#include "memory/universe.hpp"
PSYoungGen::PSYoungGen(ReservedSpace rs, size_t initial_size, size_t min_size, size_t max_size) :
_reserved(),
@@ -42,7 +43,8 @@ PSYoungGen::PSYoungGen(ReservedSpace rs, size_t initial_size, size_t min_size, s
_from_space(nullptr),
_to_space(nullptr),
_min_gen_size(min_size),
- _max_gen_size(max_size),
+ _max_gen_size(Universe::is_dynamic_max_heap_enable() ? rs.size() : max_size),
+ _cur_max_gen_size(Universe::is_dynamic_max_heap_enable() ? max_size : -1),
_gen_counters(nullptr),
_eden_counters(nullptr),
_from_counters(nullptr),
@@ -56,6 +58,9 @@ void PSYoungGen::initialize_virtual_space(ReservedSpace rs,
size_t alignment) {
assert(initial_size != 0, "Should have a finite size");
_virtual_space = new PSVirtualSpace(rs, alignment);
+ if (Universe::is_dynamic_max_heap_enable()) {
+ _virtual_space->set_dynamic_max_heap_size(_cur_max_gen_size);
+ }
if (!virtual_space()->expand_by(initial_size)) {
vm_exit_during_initialization("Could not reserve enough space for object heap");
}
@@ -70,7 +75,7 @@ void PSYoungGen::initialize_work() {
_reserved = MemRegion((HeapWord*)virtual_space()->low_boundary(),
(HeapWord*)virtual_space()->high_boundary());
- assert(_reserved.byte_size() == max_gen_size(), "invariant");
+ assert(_reserved.byte_size() == max_gen_size() || Universe::is_dynamic_max_heap_enable(), "invariant");
MemRegion cmr((HeapWord*)virtual_space()->low(),
(HeapWord*)virtual_space()->high());
@@ -30,6 +30,7 @@
#include "gc/parallel/psGenerationCounters.hpp"
#include "gc/parallel/psVirtualspace.hpp"
#include "gc/parallel/spaceCounters.hpp"
+#include "memory/universe.hpp"
class PSYoungGen : public CHeapObj<mtGC> {
friend class VMStructs;
@@ -48,6 +49,9 @@ class PSYoungGen : public CHeapObj<mtGC> {
const size_t _min_gen_size;
const size_t _max_gen_size;
+ // For Dynamic Max Heap
+ size_t _cur_max_gen_size;
+
// Performance counters
PSGenerationCounters* _gen_counters;
SpaceCounters* _eden_counters;
@@ -111,6 +115,9 @@ class PSYoungGen : public CHeapObj<mtGC> {
// not allow us to use these values.
void resize(size_t eden_size, size_t survivor_size);
+ // Resize for DynamicHeapSize, shrink to new_size
+ bool shrink_after_full_gc(size_t new_size);
+
// Size info
size_t capacity_in_bytes() const;
size_t used_in_bytes() const;
@@ -121,7 +128,24 @@ class PSYoungGen : public CHeapObj<mtGC> {
size_t free_in_words() const;
size_t min_gen_size() const { return _min_gen_size; }
- size_t max_gen_size() const { return _max_gen_size; }
+ size_t max_gen_size() const {
+ if (Universe::is_dynamic_max_heap_enable()) {
+ guarantee(_cur_max_gen_size <= _max_gen_size && _cur_max_gen_size >= min_gen_size(), "must be");
+ return _cur_max_gen_size;
+ }
+ return _max_gen_size;
+ }
+
+ void set_cur_max_gen_size(size_t new_size) {
+ guarantee(Universe::is_dynamic_max_heap_enable(), "must be");
+ guarantee(new_size <= _max_gen_size && new_size >= min_gen_size(), "must be");
+ guarantee(_max_gen_size == _reserved.byte_size(), "must be");
+ _cur_max_gen_size = new_size;
+ _virtual_space->set_dynamic_max_heap_size(new_size);
+ if (UsePerfData) {
+ _gen_counters->update_max_size(new_size);
+ }
+ }
bool is_maximal_no_gc() const {
return true; // Never expands except at a GC
@@ -243,6 +243,7 @@ CollectedHeap::CollectedHeap() :
_used_at_last_gc(0),
_is_stw_gc_active(false),
_last_whole_heap_examined_time_ns(os::javaTimeNanos()),
+ _current_max_heap_size(MaxHeapSize),
_total_collections(0),
_total_full_collections(0),
_gc_cause(GCCause::_no_gc),
@@ -128,6 +128,8 @@ class CollectedHeap : public CHeapObj<mtGC> {
// time-warp warnings.
jlong _last_whole_heap_examined_time_ns;
+ size_t _current_max_heap_size;
+
unsigned int _total_collections; // ... started
unsigned int _total_full_collections; // ... started
NOT_PRODUCT(volatile size_t _promotion_failure_alot_count;)
@@ -529,6 +531,15 @@ class CollectedHeap : public CHeapObj<mtGC> {
void reset_promotion_should_fail(volatile size_t* count);
void reset_promotion_should_fail();
#endif // #ifndef PRODUCT
+
+public:
+ // Dynamic Max Heap
+ virtual bool change_max_heap(size_t new_size){ return false; }
+ bool check_new_max_heap_validity(size_t new_size, outputStream* st);
+ size_t current_max_heap_size() const { return _current_max_heap_size; }
+ void set_current_max_heap_size(size_t new_size) {
+ _current_max_heap_size = new_size;
+ }
};
// Class to set and reset the GC cause for a CollectedHeap.
new file mode 100644
@@ -0,0 +1,150 @@
+/*
+ * Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
+ * Copyright (C) 2023 THL A29 Limited, a Tencent company. 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.
+ */
+
+#include "precompiled.hpp"
+#include "dynamicMaxHeap.hpp"
+#include "runtime/globals_extension.hpp"
+#include "os_linux.hpp"
+#include "logging/logConfiguration.hpp"
+
+size_t DynamicMaxHeapConfig::_initial_max_heap_size = 0;
+
+VM_ChangeMaxHeapOp::VM_ChangeMaxHeapOp(size_t new_max_heap) :
+ VM_GC_Operation(0, GCCause::_change_max_heap, 0, true) {
+ _new_max_heap = new_max_heap;
+ _resize_success = false;
+}
+
+bool VM_ChangeMaxHeapOp::skip_operation() const {
+ return false;
+}
+
+/*
+ * validity check
+ * new current max heap must be:
+ * 1. >= min_heap_byte_size
+ * 2. <= max_heap_byte_size
+ * 3. not equal with current_max_heap_size
+ *
+*/
+bool CollectedHeap::check_new_max_heap_validity(size_t new_size, outputStream* st) {
+#ifdef AARCH64
+ if (new_size > DynamicMaxHeapSizeLimit) {
+ st->print_cr("%s " SIZE_FORMAT "K exceeds maximum limit " SIZE_FORMAT "K",
+ Universe::dynamic_max_heap_dcmd_name(),
+ (new_size / K),
+ (DynamicMaxHeapSizeLimit / K));
+ return false;
+ }
+#endif
+ if (new_size < MinHeapSize) {
+ st->print_cr("%s " SIZE_FORMAT "K below minimum limit " SIZE_FORMAT "K",
+ Universe::dynamic_max_heap_dcmd_name(),
+ (new_size / K),
+ (MinHeapSize / K));
+ return false;
+ }
+ // don't print log if it is init shrink triggered by DynamicMaxHeapSizeLimit
+ if (new_size == current_max_heap_size()) {
+ st->print_cr("%s " SIZE_FORMAT "K same with current max heap size " SIZE_FORMAT "K",
+ Universe::dynamic_max_heap_dcmd_name(),
+ (new_size / K),
+ (current_max_heap_size() / K));
+ return false;
+ }
+ return true;
+}
+
+/*
+ common check for Dynamic Max Heap
+ 1. DynamicMaxHeapSizeLimit/ElasticMaxHeapSize should be used together with Xmx
+ 2. only linux aarch hisi
+ 3. can not fix new/old size
+ 4. must support UseAdaptiveSizePolicy, otherwise all size fixed
+ 5. only G1GC/PSGC implemented now
+ 6. should larger than Xmx
+*/
+bool DynamicMaxHeapChecker::common_check() {
+#ifdef AARCH64
+ if (!FLAG_IS_CMDLINE(DynamicMaxHeapSizeLimit) && !FLAG_IS_CMDLINE(ElasticMaxHeapSize) && !ElasticMaxHeap) {
+ return false;
+ }
+ if ((FLAG_IS_CMDLINE(DynamicMaxHeapSizeLimit) || FLAG_IS_CMDLINE(ElasticMaxHeapSize)) && !FLAG_IS_CMDLINE(MaxHeapSize)) {
+ warning_and_disable("should be used together with -Xmx/-XX:MaxHeapSize");
+ return false;
+ }
+#endif
+#if !defined(LINUX) || !defined(AARCH64)
+ warning_and_disable("can only be assigned on Linux aarch64");
+ return false;
+#endif
+#ifdef AARCH64
+ VM_Version::get_cpu_model();
+ if (!VM_Version::is_hisi_enabled()) {
+ warning_and_disable("can only be assigned on HiSi now");
+ return false;
+ }
+#endif
+ if (FLAG_IS_CMDLINE(OldSize) || FLAG_IS_CMDLINE(NewSize) || FLAG_IS_CMDLINE(MaxNewSize)) {
+ warning_and_disable("can not be used with -XX:OldSize/-XX:NewSize/-XX:MaxNewSize");
+ return false;
+ }
+ if (!UseAdaptiveSizePolicy) {
+ warning_and_disable("should be used with -XX:+UseAdaptiveSizePolicy");
+ return false;
+ }
+ if (!UseG1GC && !UseParallelGC) {
+ warning_and_disable("should be used with -XX:+UseG1GC/-XX:+UseParallelGC now");
+ return false;
+ }
+#ifdef AARCH64
+ if ((FLAG_IS_CMDLINE(DynamicMaxHeapSizeLimit) || FLAG_IS_CMDLINE(ElasticMaxHeapSize)) && DynamicMaxHeapSizeLimit <= MaxHeapSize) {
+ warning_and_disable("should be larger than -Xmx/-XX:MaxHeapSize");
+ return false;
+ }
+#endif
+ return true;
+}
+
+bool DynamicMaxHeapChecker::check_dynamic_max_heap_size_limit() {
+#ifdef AARCH64
+ if (TraceElasticMaxHeap) {
+ LogConfiguration::configure_stdout(LogLevel::Debug, false, LOG_TAGS(dynamic, heap));
+ }
+ if (FLAG_IS_CMDLINE(ElasticMaxHeapSize)) {
+ FLAG_SET_ERGO(DynamicMaxHeapSizeLimit, ElasticMaxHeapSize);
+ }
+ if (FLAG_IS_CMDLINE(ElasticMaxHeapShrinkMinFreeRatio)) {
+ FLAG_SET_ERGO(DynamicMaxHeapShrinkMinFreeRatio, ElasticMaxHeapShrinkMinFreeRatio);
+ }
+#endif
+ return common_check();
+}
+
+void DynamicMaxHeapChecker::warning_and_disable(const char *reason) {
+#ifdef AARCH64
+ warning("%s feature are not available for reason -XX:%s %s, automatically disabled",
+ Universe::dynamic_max_heap_option_name(),
+ Universe::dynamic_max_heap_size_limit_option_name(),
+ reason);
+ FLAG_SET_DEFAULT(DynamicMaxHeapSizeLimit, ScaleForWordSize(DynamicMaxHeapChecker::_default_dynamic_max_heap_size_limit * M));
+ Universe::set_dynamic_max_heap_enable(false);
+#endif
+}
\ No newline at end of file
new file mode 100644
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
+ * Copyright (C) 2023 THL A29 Limited, a Tencent company. 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.
+ */
+
+#ifndef SHARE_VM_GC_IMPLEMENTATION_SHARED_DYNAMIC_MAX_HEAP_OPERATION_HPP
+#define SHARE_VM_GC_IMPLEMENTATION_SHARED_DYNAMIC_MAX_HEAP_OPERATION_HPP
+
+#include "utilities/defaultStream.hpp"
+#include "gc/shared/gcVMOperations.hpp"
+
+class VM_ChangeMaxHeapOp : public VM_GC_Operation {
+public:
+ VM_ChangeMaxHeapOp(size_t new_max_heap);
+ VMOp_Type type() const override {
+ return VMOp_DynamicMaxHeap;
+ }
+ bool resize_success() const {
+ return _resize_success;
+ }
+protected:
+ size_t _new_max_heap;
+ bool _resize_success;
+private:
+ bool skip_operation() const override;
+};
+
+class DynamicMaxHeapChecker : private AllStatic {
+public:
+ static bool common_check();
+ static bool check_dynamic_max_heap_size_limit();
+ static void warning_and_disable(const char *reason);
+private:
+ static const int _default_dynamic_max_heap_size_limit = 96;
+};
+
+class DynamicMaxHeapConfig : private AllStatic {
+public:
+ static size_t initial_max_heap_size() { return _initial_max_heap_size; }
+ static void set_initial_max_heap_size(size_t new_size) {
+ _initial_max_heap_size = new_size;
+ }
+private:
+ static size_t _initial_max_heap_size;
+};
+#endif // SHARE_VM_GC_IMPLEMENTATION_SHARED_DYNAMIC_MAX_HEAP_OPERATION_HPP
\ No newline at end of file
@@ -32,6 +32,7 @@
#include "runtime/globals_extension.hpp"
#include "utilities/formatBuffer.hpp"
#include "utilities/macros.hpp"
+#include "memory/universe.hpp"
size_t HeapAlignment = 0;
size_t SpaceAlignment = 0;
@@ -155,6 +156,14 @@ void GCArguments::initialize_heap_flags_and_sizes() {
if (!is_aligned(MaxHeapSize, HeapAlignment)) {
FLAG_SET_ERGO(MaxHeapSize, align_up(MaxHeapSize, HeapAlignment));
}
+#ifdef AARCH64
+ if (Universe::is_dynamic_max_heap_enable() && !is_aligned(DynamicMaxHeapSizeLimit, HeapAlignment)) {
+ size_t _dynamic_max_heap_size_limit = DynamicMaxHeapSizeLimit;
+ FLAG_SET_ERGO(DynamicMaxHeapSizeLimit, align_up(DynamicMaxHeapSizeLimit, HeapAlignment));
+ log_debug(dynamic, heap)("align the DynamicMaxHeapSizeLimit " SIZE_FORMAT " up to " SIZE_FORMAT " for heap alignment " SIZE_FORMAT ,
+ _dynamic_max_heap_size_limit, DynamicMaxHeapSizeLimit, HeapAlignment);
+ }
+#endif //AARCH64
if (!FLAG_IS_DEFAULT(InitialHeapSize) && InitialHeapSize > MaxHeapSize) {
FLAG_SET_ERGO(MaxHeapSize, InitialHeapSize);
@@ -66,6 +66,9 @@ const char* GCCause::to_string(GCCause::Cause cause) {
case _allocation_failure:
return "Allocation Failure";
+ case _change_max_heap:
+ return "Change Max Heap";
+
case _codecache_GC_threshold:
return "CodeCache GC Threshold";
@@ -58,6 +58,7 @@ class GCCause : public AllStatic {
_no_gc,
_no_cause_specified,
_allocation_failure,
+ _change_max_heap,
/* implementation specific */
@@ -31,6 +31,7 @@
#include "runtime/java.hpp"
#include "utilities/align.hpp"
#include "utilities/globalDefinitions.hpp"
+#include "memory/universe.hpp"
size_t MinNewSize = 0;
@@ -377,7 +378,13 @@ void GenArguments::assert_flags() {
void GenArguments::assert_size_info() {
GCArguments::assert_size_info();
// GenArguments::initialize_size_info may update the MaxNewSize
- assert(MaxNewSize < MaxHeapSize, "Ergonomics decided on incompatible maximum young and heap sizes");
+ if (Universe::is_dynamic_max_heap_enable()) {
+#ifdef AARCH64
+ assert(MaxNewSize < MAX2(MaxHeapSize, DynamicMaxHeapSizeLimit), "Ergonomics decided on incompatible maximum young and heap sizes");
+#endif //AARCH64
+ } else {
+ assert(MaxNewSize < MaxHeapSize, "Ergonomics decided on incompatible maximum young and heap sizes");
+ }
assert(MinNewSize <= NewSize, "Ergonomics decided on incompatible minimum and initial young gen sizes");
assert(NewSize <= MaxNewSize, "Ergonomics decided on incompatible initial and maximum young gen sizes");
assert(MinNewSize % GenAlignment == 0, "_min_young_size alignment");
@@ -26,7 +26,9 @@
#define SHARE_GC_SHARED_GENARGUMENTS_HPP
#include "gc/shared/gcArguments.hpp"
+#include "gc/shared/gc_globals.hpp"
#include "utilities/debug.hpp"
+#include "memory/universe.hpp"
extern size_t MinNewSize;
@@ -36,6 +38,7 @@ extern size_t MaxOldSize;
extern size_t GenAlignment;
class GenArguments : public GCArguments {
+ friend class PS_ChangeMaxHeapOp;
friend class TestGenCollectorPolicy; // Testing
private:
virtual void initialize_alignments();
@@ -51,6 +54,18 @@ private:
protected:
virtual void initialize_heap_flags_and_sizes();
+public:
+ //dynamic max heap size
+ static size_t max_old_size(size_t size) {
+ if (Universe::is_dynamic_max_heap_enable()) {
+ size_t young_limit = scale_by_NewRatio_aligned(size, GenAlignment);
+ young_limit = MAX3(young_limit, MinNewSize, NewSize);
+ size_t old_limit = size - young_limit;
+ guarantee(old_limit >= MinOldSize && old_limit >= OldSize, "must be");
+ return old_limit;
+ }
+ return MaxOldSize;
+ }
};
#endif // SHARE_GC_SHARED_GENARGUMENTS_HPP
@@ -26,6 +26,7 @@
#include "gc/shared/generationCounters.hpp"
#include "memory/allocation.inline.hpp"
#include "memory/resourceArea.hpp"
+#include "memory/universe.hpp"
#include "runtime/perfData.hpp"
void GenerationCounters::initialize(const char* name, int ordinal, int spaces,
@@ -52,8 +53,15 @@ void GenerationCounters::initialize(const char* name, int ordinal, int spaces,
min_capacity, CHECK);
cname = PerfDataManager::counter_name(_name_space, "maxCapacity");
- PerfDataManager::create_constant(SUN_GC, cname, PerfData::U_Bytes,
- max_capacity, CHECK);
+ // Dynamic Max Heap
+ if (Universe::is_dynamic_max_heap_enable()) {
+ _max_size = PerfDataManager::create_variable(SUN_GC, cname, PerfData::U_Bytes,
+ max_capacity, CHECK);
+ } else {
+ _max_size = NULL;
+ PerfDataManager::create_constant(SUN_GC, cname, PerfData::U_Bytes,
+ max_capacity, CHECK);
+ }
cname = PerfDataManager::counter_name(_name_space, "capacity");
_current_size =
@@ -88,3 +96,8 @@ void GenerationCounters::update_all() {
assert(_virtual_space != nullptr, "otherwise, override this method");
_current_size->set_value(_virtual_space->committed_size());
}
+
+void GenerationCounters::update_max_size(size_t size) {
+ guarantee(Universe::is_dynamic_max_heap_enable(), "must be");
+ _max_size->set_value(size);
+}
@@ -40,6 +40,8 @@ private:
size_t curr_capacity);
protected:
+ // Dynamic Max Heap
+ PerfVariable* _max_size; // max size can be change when Dynamic Max Heap is on
PerfVariable* _current_size;
VirtualSpace* _virtual_space;
@@ -72,6 +74,9 @@ private:
virtual void update_all();
+ // Dynamic Max Heap
+ void update_max_size(size_t size);
+
const char* name_space() const { return _name_space; }
};
@@ -64,6 +64,9 @@ LRUMaxHeapPolicy::LRUMaxHeapPolicy() {
// Capture state (of-the-VM) information needed to evaluate the policy
void LRUMaxHeapPolicy::setup() {
size_t max_heap = MaxHeapSize;
+ if (Universe::is_dynamic_max_heap_enable()) {
+ max_heap = Universe::heap()->current_max_heap_size();
+ }
max_heap -= Universe::heap()->used_at_last_gc();
max_heap /= M;
@@ -168,6 +168,9 @@ OopStorage* Universe::_vm_global = nullptr;
CollectedHeap* Universe::_collectedHeap = nullptr;
+// Dynamic Max Heap
+bool Universe::_enable_dynamic_max_heap = false;
+
objArrayOop Universe::the_empty_class_array () {
return (objArrayOop)_the_empty_class_array.resolve();
}
@@ -29,6 +29,7 @@
#include "oops/array.hpp"
#include "oops/oopHandle.hpp"
#include "runtime/handles.hpp"
+#include "runtime/globals_extension.hpp"
#include "utilities/growableArray.hpp"
// Universe is a name space holding known system classes and objects in the VM.
@@ -195,6 +196,9 @@ class Universe: AllStatic {
static int _verify_count; // number of verifies done
static long verify_flags;
+ // Dynamic Max Heap
+ static bool _enable_dynamic_max_heap;
+
static uintptr_t _verify_oop_mask;
static uintptr_t _verify_oop_bits;
@@ -400,6 +404,47 @@ class Universe: AllStatic {
// Compiler support
static int base_vtable_size() { return _base_vtable_size; }
+
+ // Dynamic Max Heap
+ static const char* dynamic_max_heap_dcmd_name() {
+#ifdef AARCH64
+ if (FLAG_IS_CMDLINE(ElasticMaxHeapSize)) {
+ return "GC.elastic_max_heap";
+ }
+ if (FLAG_IS_CMDLINE(DynamicMaxHeapSizeLimit)) {
+ return "GC.change_max_heap";
+ }
+#endif //AARCH64
+ return "GC.elastic_max_heap";
+ }
+ static const char* dynamic_max_heap_option_name() {
+#ifdef AARCH64
+ if (FLAG_IS_CMDLINE(ElasticMaxHeapSize)) {
+ return "ElasticMaxHeap";
+ }
+ if (FLAG_IS_CMDLINE(DynamicMaxHeapSizeLimit)) {
+ return "DynamicMaxHeap";
+ }
+#endif //AARCH64
+ return "ElasticMaxHeap";
+ }
+ static const char* dynamic_max_heap_size_limit_option_name() {
+#ifdef AARCH64
+ if (FLAG_IS_CMDLINE(ElasticMaxHeapSize)) {
+ return "ElasticMaxHeapSize";
+ }
+ if (FLAG_IS_CMDLINE(DynamicMaxHeapSizeLimit)) {
+ return "DynamicMaxHeapSizeLimit";
+ }
+#endif //AARCH64
+ return "+ElasticMaxHeap";
+ }
+ static bool is_dynamic_max_heap_enable() {
+ NOT_AARCH64(return false;);
+ AARCH64_ONLY(return _enable_dynamic_max_heap;);
+ }
+
+ static void set_dynamic_max_heap_enable(bool a) { _enable_dynamic_max_heap = a; }
};
#endif // SHARE_MEMORY_UNIVERSE_HPP
@@ -31,6 +31,7 @@
#include "classfile/stringTable.hpp"
#include "classfile/symbolTable.hpp"
#include "compiler/compilerDefinitions.hpp"
+#include "gc/shared/dynamicMaxHeap.hpp"
#include "gc/shared/gcArguments.hpp"
#include "gc/shared/gcConfig.hpp"
#include "gc/shared/stringdedup/stringDedup.hpp"
@@ -1470,6 +1471,20 @@ void Arguments::set_use_compressed_oops() {
// to use UseCompressedOops are InitialHeapSize and MinHeapSize.
size_t max_heap_size = MAX3(MaxHeapSize, InitialHeapSize, MinHeapSize);
+#ifdef AARCH64
+ // DynamicMaxHeap
+ // 1. align DynamicMaxHeapSizeLimit
+ // 2. use DynamicMaxHeapSizeLimit to check whether compressedOops can enabled
+ bool dynamic_max_heap_enable = DynamicMaxHeapChecker::check_dynamic_max_heap_size_limit();
+ if (dynamic_max_heap_enable) {
+ Universe::set_dynamic_max_heap_enable(true);
+ DynamicMaxHeapConfig::set_initial_max_heap_size((size_t)MaxHeapSize);
+ size_t _heap_alignment = GCArguments::compute_heap_alignment();
+ uintx aligned_max_heap_size_limit = align_up(DynamicMaxHeapSizeLimit, _heap_alignment);
+ FLAG_SET_ERGO(DynamicMaxHeapSizeLimit, aligned_max_heap_size_limit);
+ max_heap_size = MAX2(max_heap_size, DynamicMaxHeapSizeLimit);
+ }
+#endif // AARCH64
if (max_heap_size <= max_heap_for_compressed_oops()) {
if (FLAG_IS_DEFAULT(UseCompressedOops)) {
FLAG_SET_ERGO(UseCompressedOops, true);
@@ -2030,6 +2030,9 @@ const int ObjectAlignmentInBytes = 8;
product(bool, StressSecondarySupers, false, DIAGNOSTIC, \
"Use a terrible hash function in order to generate many collisions.") \
\
+ product(bool, ElasticMaxDirectMemory, false, \
+ "Allow change max direct memory size during runtime with jcmd") \
+ \
// end of RUNTIME_FLAGS
@@ -1971,6 +1971,10 @@ bool os::uncommit_memory(char* addr, size_t bytes, bool executable) {
return res;
}
+bool os::free_heap_physical_memory(char *addr, size_t bytes) {
+ return pd_free_heap_physical_memory(addr, bytes);
+}
+
bool os::release_memory(char* addr, size_t bytes) {
assert_nonempty_range(addr, bytes);
bool res;
@@ -205,6 +205,7 @@ class os: AllStatic {
size_t alignment_hint,
bool executable, const char* mesg);
static bool pd_uncommit_memory(char* addr, size_t bytes, bool executable);
+ static bool pd_free_heap_physical_memory(char* addr, size_t bytes);
static bool pd_release_memory(char* addr, size_t bytes);
static char* pd_attempt_map_memory_to_file_at(char* addr, size_t bytes, int file_desc);
@@ -449,6 +450,7 @@ class os: AllStatic {
size_t alignment_hint,
bool executable, const char* mesg);
static bool uncommit_memory(char* addr, size_t bytes, bool executable = false);
+ static bool free_heap_physical_memory(char* addr, size_t bytes);
static bool release_memory(char* addr, size_t bytes);
// Does the platform support trimming the native heap?
@@ -858,6 +858,15 @@ jint Threads::create_vm(JavaVMInitArgs* args, bool* canTryAgain) {
}
#endif // INCLUDE_JBOLT
+ // Dynamic Max Heap: reset heap initial size to MaxHeapSize
+ if (Universe::is_dynamic_max_heap_enable()) {
+ bool success = Universe::heap()->change_max_heap(MaxHeapSize);
+ if (!success) {
+ log_error(dynamic, heap)("VM failed to initialize heap to Xmx " SIZE_FORMAT "K", (MaxHeapSize / K));
+ vm_exit(1);
+ }
+ }
+
return JNI_OK;
}
@@ -117,7 +117,8 @@
template(GTestStopSafepoint) \
template(JFROldObject) \
template(JvmtiPostObjectFree) \
- template(RendezvousGCThreads)
+ template(RendezvousGCThreads) \
+ template(DynamicMaxHeap)
class Thread;
class outputStream;
@@ -34,6 +34,7 @@
#include "compiler/compileBroker.hpp"
#include "compiler/directivesParser.hpp"
#include "gc/shared/gcVMOperations.hpp"
+#include "gc/shared/gcArguments.hpp"
#include "jvm.h"
#include "memory/metaspace/metaspaceDCmd.hpp"
#include "memory/resourceArea.hpp"
@@ -106,6 +107,9 @@ void DCmd::register_dcmds(){
DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<RunFinalizationDCmd>(full_export, true, false));
DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<HeapInfoDCmd>(full_export, true, false));
DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<FinalizerInfoDCmd>(full_export, true, false));
+ DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<ChangeMaxHeapDCmd>(full_export, true, false));
+ DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<ElasticMaxHeapDCmd>(full_export, true, false));
+ DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<ElasticMaxDirectMemoryDCmd>(full_export, true, false));
#if INCLUDE_SERVICES
DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<HeapDumpDCmd>(DCmd_Source_Internal | DCmd_Source_AttachAPI, true, false));
DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<ClassHistogramDCmd>(full_export, true, false));
@@ -465,6 +469,119 @@ void FinalizerInfoDCmd::execute(DCmdSource source, TRAPS) {
}
}
+ChangeMaxHeapDCmd::ChangeMaxHeapDCmd(outputStream* output, bool heap) :
+ DCmdWithParser(output, heap),
+ _new_max_heap_size("change_max_heap", "New max size of heap", "MEMORY SIZE", true) {
+ _dcmdparser.add_dcmd_argument(&_new_max_heap_size);
+}
+
+int ChangeMaxHeapDCmd::num_arguments(ChangeMaxHeapDCmd* dcmd) {
+ if (dcmd != NULL) {
+ DCmdMark mark(dcmd);
+ return dcmd->_dcmdparser.num_arguments();
+ } else {
+ return 0;
+ }
+}
+
+int ChangeMaxHeapDCmd::num_arguments() {
+ ResourceMark rm;
+ ChangeMaxHeapDCmd* dcmd = new ChangeMaxHeapDCmd(NULL, false);
+ return ChangeMaxHeapDCmd::num_arguments(dcmd);
+}
+
+void ChangeMaxHeapDCmd::execute(DCmdSource source, TRAPS) {
+ if (!Universe::is_dynamic_max_heap_enable()) {
+ output()->print_cr("not supported because -XX:DynamicMaxHeapSizeLimit/-XX:ElasticMaxHeapSize was not specified");
+ return;
+ }
+
+#ifdef AARCH64
+ jlong input_max_heap_size = _new_max_heap_size.value()._size;
+ jlong new_max_heap_size = align_up((size_t)input_max_heap_size, HeapAlignment);
+ if (new_max_heap_size != input_max_heap_size) {
+ output()->print_cr("align the given value " SIZE_FORMAT " up to " SIZE_FORMAT "K for heap alignment " SIZE_FORMAT "K",
+ input_max_heap_size,
+ (new_max_heap_size / K),
+ (HeapAlignment / K));
+ }
+
+ bool is_validate = Universe::heap()->check_new_max_heap_validity(new_max_heap_size, output());
+ if (!is_validate) {
+ output()->print_cr("%s fail", Universe::dynamic_max_heap_dcmd_name());
+ return;
+ }
+ output()->print_cr("%s (" SIZE_FORMAT "K" "->" SIZE_FORMAT "K)(" SIZE_FORMAT "K)",
+ Universe::dynamic_max_heap_dcmd_name(),
+ (Universe::heap()->current_max_heap_size() / K),
+ (new_max_heap_size / K),
+ (DynamicMaxHeapSizeLimit / K));
+
+ bool success = Universe::heap()->change_max_heap(new_max_heap_size);
+ if (success) {
+ output()->print_cr("%s success", Universe::dynamic_max_heap_dcmd_name());
+ } else {
+ output()->print_cr("%s fail", Universe::dynamic_max_heap_dcmd_name());
+ }
+#endif // AARCH64
+}
+
+ElasticMaxHeapDCmd::ElasticMaxHeapDCmd(outputStream* output, bool heap) :
+ ChangeMaxHeapDCmd(output, heap) {
+}
+
+int ElasticMaxHeapDCmd::num_arguments() {
+ ResourceMark rm;
+ ElasticMaxHeapDCmd* dcmd = new ElasticMaxHeapDCmd(NULL, false);
+ return ChangeMaxHeapDCmd::num_arguments(dcmd);
+}
+
+ElasticMaxDirectMemoryDCmd::ElasticMaxDirectMemoryDCmd(outputStream* output, bool heap) :
+ DCmdWithParser(output, heap),
+ _new_max_direct_memory("elastic_max_direct_memory", "New max size of direct memory", "MEMORY SIZE", true) {
+ _dcmdparser.add_dcmd_argument(&_new_max_direct_memory);
+}
+
+int ElasticMaxDirectMemoryDCmd::num_arguments() {
+ ResourceMark rm;
+ ElasticMaxDirectMemoryDCmd* dcmd = new ElasticMaxDirectMemoryDCmd(NULL, false);
+ if (dcmd != NULL) {
+ DCmdMark mark(dcmd);
+ return dcmd->_dcmdparser.num_arguments();
+ } else {
+ return 0;
+ }
+}
+
+void ElasticMaxDirectMemoryDCmd::execute(DCmdSource source, TRAPS) {
+ if (!ElasticMaxDirectMemory) {
+ output()->print_cr("not supported because -XX:+ElasticMaxDirectMemory was not specified");
+ return;
+ }
+
+ jlong new_max_direct_memory = _new_max_direct_memory.value()._size;
+ Symbol* klass = vmSymbols::java_nio_Bits();
+ Klass* k = SystemDictionary::resolve_or_fail(klass, true, CHECK);
+
+ // invoke the updateMaxMemory method
+ JavaValue result(T_OBJECT);
+ JavaCallArguments args;
+ args.push_long(new_max_direct_memory);
+ JavaCalls::call_static(&result,
+ k,
+ vmSymbols::updateMaxMemory_name(),
+ vmSymbols::updateMaxMemory_signature(),
+ &args,
+ CHECK);
+ oop msg = cast_to_oop(result.get_jobject());
+ if (msg != NULL) {
+ char* text = java_lang_String::as_utf8_string(msg);
+ if (text != NULL) {
+ output()->print_cr("%s", text);
+ }
+ }
+}
+
#if INCLUDE_SERVICES // Heap dumping/inspection supported
HeapDumpDCmd::HeapDumpDCmd(outputStream* output, bool heap) :
DCmdWithParser(output, heap),
@@ -312,6 +312,59 @@ public:
virtual void execute(DCmdSource source, TRAPS);
};
+class ChangeMaxHeapDCmd : public DCmdWithParser {
+protected:
+ DCmdArgument<MemorySizeArgument> _new_max_heap_size;
+ static int num_arguments(ChangeMaxHeapDCmd* dcmd);
+public:
+ ChangeMaxHeapDCmd(outputStream* output, bool heap);
+ static const char* name() { return "GC.change_max_heap"; }
+ static const char* description() {
+ return "change dynamic max heap size during runtime.";
+ }
+ static const char* impact() {
+ return "Medium";
+ }
+ static const JavaPermission permission() {
+ JavaPermission p = {"java.lang.management.ManagementPermission",
+ "monitor", NULL};
+ return p;
+ }
+ static int num_arguments();
+ void execute(DCmdSource source, TRAPS) override;
+};
+
+class ElasticMaxHeapDCmd : public ChangeMaxHeapDCmd {
+public:
+ ElasticMaxHeapDCmd(outputStream* output, bool heap);
+ static const char* name() { return "GC.elastic_max_heap"; }
+ static const char* description() {
+ return "try elastic max heap size during runtime.";
+ }
+ static int num_arguments();
+};
+
+class ElasticMaxDirectMemoryDCmd : public DCmdWithParser {
+protected:
+ DCmdArgument<MemorySizeArgument> _new_max_direct_memory;
+public:
+ ElasticMaxDirectMemoryDCmd(outputStream* output, bool heap);
+ static const char* name() { return "GC.elastic_max_direct_memory"; }
+ static const char* description() {
+ return "try elastic max direct memory during runtime.";
+ }
+ static const char* impact() {
+ return "Medium";
+ }
+ static const JavaPermission permission() {
+ JavaPermission p = {"java.lang.management.ManagementPermission",
+ "monitor", NULL};
+ return p;
+ }
+ static int num_arguments();
+ void execute(DCmdSource source, TRAPS) override;
+};
+
#if INCLUDE_SERVICES // Heap dumping supported
// See also: dump_heap in attachListener.cpp
class HeapDumpDCmd : public DCmdWithParser {
@@ -210,6 +210,32 @@ class Bits { // package-private
assert cnt >= 0 && reservedMem >= 0 && totalCap >= 0;
}
+ static String updateMaxMemory(long newSize) {
+ long reservedMem = RESERVED_MEMORY.get();
+ StringBuilder sb = new StringBuilder();
+ if (newSize >= reservedMem) {
+ sb.append("GC.elastic_max_direct_memory (");
+ sb.append(MAX_MEMORY / 1024).append("K->");
+ sb.append(newSize / 1024).append("K)");
+ sb.append("\n");
+ sb.append("GC.elastic_max_direct_memory success");
+ // update VM.maxDirectMemory and Bits.MAX_MEMORY
+ // specially, if shrink, in a multi-threaded scenario,
+ // VM.maxDirectMemory() may be inconsistent with the actual direct memory usage.
+ // the new maxMemory will take effect the next time direct memory is allocated.
+ VM.setMaxDirectMemory(newSize);
+ MAX_MEMORY = VM.maxDirectMemory();
+ } else {
+ sb.append("GC.elastic_max_direct_memory ");
+ sb.append(newSize / 1024).append("K below current reserved direct memory ");
+ sb.append(reservedMem / 1024).append("K");
+ sb.append("\n");
+ sb.append("GC.elastic_max_direct_memory fail");
+ }
+ String output = sb.toString();
+ return output;
+ }
+
static final BufferPool BUFFER_POOL = new BufferPool() {
@Override
public String getName() {
@@ -145,6 +145,12 @@ public class VM {
return directMemory;
}
+ // ElasticMaxDirectMemory
+ // update max direct memory size
+ public static void setMaxDirectMemory(long size) {
+ directMemory = size;
+ }
+
// User-controllable flag that determines if direct buffers should be page
// aligned. The "-XX:+PageAlignDirectMemory" option can be used to force
// buffers, allocated by ByteBuffer.allocateDirect, to be page aligned.
@@ -104,7 +104,7 @@ public class TestSmallHeap {
analyzer.shouldHaveExitValue(0);
expectedMaxHeap = Math.max(expectedMaxHeap, minMaxHeap);
- long maxHeapSize = Long.parseLong(analyzer.firstMatch("MaxHeapSize.+=\\s+(\\d+)",1));
+ long maxHeapSize = Long.parseLong(analyzer.firstMatch("\\s+MaxHeapSize\\s+=\\s+(\\d+)",1));
long actualHeapSize = Long.parseLong(analyzer.firstMatch(VerifyHeapSize.actualMsg + "(\\d+)",1));
Asserts.assertEQ(maxHeapSize, expectedMaxHeap);
Asserts.assertLessThanOrEqual(actualHeapSize, maxHeapSize);
@@ -90,7 +90,7 @@ public class TestMaxRAMFlags {
}
private static String getFlagValue(String flag, String where) {
- Matcher m = Pattern.compile(flag + "\\s+:?=\\s+\\d+").matcher(where);
+ Matcher m = Pattern.compile("\\s+" + flag + "\\s+:?=\\s+\\d+").matcher(where);
if (!m.find()) {
throw new RuntimeException("Could not find value for flag " + flag + " in output string");
}
new file mode 100644
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
+ * Copyright (C) 2023, 2024 THL A29 Limited, a Tencent company. 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.
+ */
+
+package gc.dynamicmaxheap;
+
+/*
+ * @test
+ * @summary Test Basic Elastic Max Heap resize
+ * @requires (os.family == "linux") & (os.arch == "aarch64")
+ * @library /test/lib
+ * @build gc.dynamicmaxheap.TestBase
+ * @compile test_classes/NotActiveHeap.java
+ * @run driver gc.dynamicmaxheap.BasicTest
+ */
+
+import java.lang.reflect.Field;
+import jdk.test.lib.process.OutputAnalyzer;
+import jdk.test.lib.JDKToolFinder;
+import jdk.test.lib.process.ProcessTools;
+import jdk.test.lib.Asserts;
+
+public class BasicTest extends TestBase {
+ public static void main(String[] args) throws Exception {
+ test("-XX:+UseParallelGC");
+ test("-XX:+UseG1GC");
+ }
+
+ private static void test(String heap_type_or_process_count) throws Exception {
+ String architecture = System.getProperty("os.arch");
+ // Xms = 100M - 1B, Xmx = 600M - 1B, ElasticMaxHeapSize = 1G - 1B
+ // unaligned arguments should be fine
+ ProcessBuilder pb = ProcessTools.createLimitedTestJavaProcessBuilder(heap_type_or_process_count, "-XX:+ElasticMaxHeap", "-Xms104857599", "-Xmx629145599", "-XX:ElasticMaxHeapSize=1073741823", "NotActiveHeap");
+ Process p = pb.start();
+ try {
+ long pid = p.pid();
+ System.out.println(pid);
+
+ // shrink to 500M should be fine for any GC
+ String[] contains1 = {
+ "GC.elastic_max_heap success",
+ "GC.elastic_max_heap (",
+ };
+ resizeAndCheck(pid, "500M", contains1, null);
+
+ // expand to 800M should be fine for any GC
+ String[] contains2 = {
+ "GC.elastic_max_heap success",
+ "GC.elastic_max_heap (",
+ };
+ resizeAndCheck(pid, "800M", contains2, null);
+
+ // expand to 2G should fail
+ String[] contains3 = {
+ "GC.elastic_max_heap fail",
+ "2097152K exceeds maximum limit",
+ };
+ resizeAndCheck(pid, "2G", contains3, null);
+
+ // epxand to 1G should be fine
+ String[] contains4 = {
+ "GC.elastic_max_heap success",
+ "GC.elastic_max_heap (",
+ };
+ resizeAndCheck(pid, "1G", contains4, null);
+
+ // shrink to 300M should be fine
+ // unaligned arguments should be fine, new_size = 300M -1B
+ String[] contains5 = {
+ "GC.elastic_max_heap success",
+ "GC.elastic_max_heap (",
+ };
+ resizeAndCheck(pid, "314572799", contains5, null);
+ } finally {
+ p.destroy();
+ }
+ }
+}
new file mode 100644
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
+ * Copyright (C) 2023, 2024 THL A29 Limited, a Tencent company. 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.
+ */
+
+/*
+ * @test
+ * @summary Test Basic Elastic Max Direct Memory resize
+ * @requires (os.family == "linux") & (os.arch == "aarch64")
+ * @library /test/lib
+ * @build gc.dynamicmaxheap.TestBase
+ * @compile test_classes/NotActiveDirectMemory.java
+ * @run driver gc.dynamicmaxheap.DirectMemoryBasicTest
+ */
+
+package gc.dynamicmaxheap;
+
+import jdk.test.lib.process.OutputAnalyzer;
+import jdk.test.lib.JDKToolFinder;
+import jdk.test.lib.process.ProcessTools;
+import jdk.test.lib.Asserts;
+
+public class DirectMemoryBasicTest extends TestBase {
+ public static void main(String[] args) throws Exception {
+ test("-XX:+UseParallelGC");
+ test("-XX:+UseG1GC");
+ }
+
+ private static void test(String heap_type) throws Exception {
+ String architecture = System.getProperty("os.arch");
+ ProcessBuilder pb = ProcessTools.createLimitedTestJavaProcessBuilder(heap_type,
+ "-XX:+ElasticMaxDirectMemory",
+ "-Xms100M",
+ "-Xmx100M",
+ "-XX:MaxDirectMemorySize=200M",
+ "NotActiveDirectMemory");
+ Process p = pb.start();
+ long pid;
+ try {
+ pid = p.pid();
+ System.out.println(pid);
+
+ // NotActiveDirectMemory will alloc 100M direct memory
+ // expand to 300M should be fine for any GC
+ String[] contains1 = {
+ "GC.elastic_max_direct_memory (",
+ "GC.elastic_max_direct_memory success"
+ };
+ resizeAndCheck(pid, "300M", contains1, null, "GC.elastic_max_direct_memory");
+
+ // shrink to 50M should be fail for any GC,
+ String[] contains2 = {
+ "below current reserved direct memory",
+ "GC.elastic_max_direct_memory fail"
+ };
+ resizeAndCheck(pid, "50M", contains2, null, "GC.elastic_max_direct_memory");
+ } finally {
+ p.destroy();
+ }
+ }
+}
new file mode 100644
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
+ * Copyright (C) 2023, 2024 THL A29 Limited, a Tencent company. 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.
+ */
+
+/*
+ * @test
+ * @summary Test max direct memory can take effect after resize
+ * @requires (os.family == "linux") & (os.arch == "aarch64")
+ * @library /test/lib
+ * @build gc.dynamicmaxheap.TestBase
+ * @compile test_classes/LimitDirectMemoryTestBasic.java
+ * @run driver gc.dynamicmaxheap.LimitDirectMemoryTest
+ */
+
+package gc.dynamicmaxheap;
+
+import jdk.test.lib.process.OutputAnalyzer;
+import jdk.test.lib.process.ProcessTools;
+
+public class LimitDirectMemoryTest extends TestBase {
+ public static void main(String[] args) throws Exception {
+ // Test1
+ // init max direct memory is 200M
+ // expand to 300M and alloc 300M direct memory should be fine
+ String[] contains1 = {
+ "allocation finish!"
+ };
+ String[] not_contains1 = {
+ "java.lang.OutOfMemoryError: Cannot reserve "
+ };
+ Test("-XX:+UseParallelGC", "300M", "300", contains1, not_contains1);
+ Test("-XX:+UseG1GC", "300M", "300", contains1, not_contains1);
+
+ // Test2
+ // init max direct memory is 200M
+ // shrink to 50M and alloc 100M direct memory should oom
+ String[] contains2 = {
+ "java.lang.OutOfMemoryError: Cannot reserve "
+ };
+ String[] not_contains2 = {
+ "allocation finish!"
+ };
+ Test("-XX:+UseParallelGC", "50M", "100", contains2, not_contains2);
+ Test("-XX:+UseG1GC", "50M", "100", contains2, not_contains2);
+ }
+
+ private static void Test(String heap_type, String new_size, String alloc_size, String[] contains, String[] not_contains) throws Exception {
+ ProcessBuilder pb = ProcessTools.createLimitedTestJavaProcessBuilder(heap_type,
+ "-Dtest.jdk=" + System.getProperty("test.jdk"),
+ "-XX:+ElasticMaxDirectMemory",
+ "-XX:MaxDirectMemorySize=200M",
+ "-Xms100M",
+ "-Xmx100M",
+ "LimitDirectMemoryTestBasic",
+ new_size,
+ alloc_size);
+ OutputAnalyzer output = new OutputAnalyzer(pb.start());
+ CheckOutput(output, contains, not_contains);
+ }
+
+ public static void CheckOutput(OutputAnalyzer output, String[] contains, String[] not_contains) throws Exception {
+ System.out.println(output.getOutput());
+ if (contains != null) {
+ for (String s : contains) {
+ output.shouldContain(s);
+ }
+ }
+ if (not_contains != null) {
+ for (String s : not_contains) {
+ output.shouldNotContain(s);
+ }
+ }
+ }
+}
+
+
\ No newline at end of file
new file mode 100644
@@ -0,0 +1,139 @@
+/*
+ * Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
+ * Copyright (C) 2023, 2024 THL A29 Limited, a Tencent company. 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.
+ */
+
+package gc.dynamicmaxheap;
+
+import java.lang.management.*;
+import java.util.*;
+import jdk.test.lib.process.OutputAnalyzer;
+import jdk.test.lib.JDKToolFinder;
+import jdk.test.lib.process.ProcessTools;
+import jdk.test.lib.Asserts;
+
+/**
+ * @test MemoryPoolTest
+ * @summary test MemoryPool MemoryUsage returns correct max size
+ * @requires (os.family == "linux") & (os.arch == "aarch64")
+ * @library /test/lib
+ * @build gc.dynamicmaxheap.TestBase
+ * @run main/othervm -Xms50M -Xmx2G -XX:+ElasticMaxHeap -XX:+UseParallelGC -Xlog:dynamic+heap=debug gc.dynamicmaxheap.MemoryPoolTest
+ * @run main/othervm -Xms50M -Xmx2G -XX:+ElasticMaxHeap -XX:+UseG1GC -Xlog:dynamic+heap=debug gc.dynamicmaxheap.MemoryPoolTest
+ */
+public class MemoryPoolTest extends TestBase {
+ static Object[] root_array;
+ static final long M = 1024L * 1024L;
+ static long edenMaxSize;
+ static long survivorMaxSize;
+ static long oldGenMaxSize;
+ public static void main(String[] args) throws Exception {
+ String architecture = System.getProperty("os.arch");
+ long pid = ProcessTools.getProcessId();
+ /*
+ * Steps: start with 2G heap
+ * 1. start and allocate about 1G object
+ * 2. get MemoryPool and usage as expected
+ * 3. launch jcmd resize to 100M and expect success
+ * 4. get MemoryPool and usage as expected
+ */
+ alloc_and_free(1024L * 1024L * 1024L);
+ MemoryMXBean mem = ManagementFactory.getMemoryMXBean();
+ MemoryUsage usage = mem.getHeapMemoryUsage();
+ long max = usage.getMax() / M;
+ long committed = usage.getCommitted() / M;
+ long used = usage.getUsed() / M;
+ System.out.println("After alloc -- Heap Max: " + max +
+ "M, Committed: " + committed +
+ "M, Used: " + used + "M");
+ Asserts.assertGT(max, 1024L);
+ Asserts.assertGTE(max, committed);
+ Asserts.assertGTE(committed, used);
+
+ // check eden, survivor and old memory pool after alloc
+ get_memory_info();
+ System.out.println("After alloc -- Eden Max: " + edenMaxSize +
+ "M, Survivor Max: " + survivorMaxSize +
+ "M, Old Max: " + oldGenMaxSize + "M");
+ long orig_old = oldGenMaxSize;
+ long orig_eden = edenMaxSize;
+ Asserts.assertGT(edenMaxSize + survivorMaxSize + oldGenMaxSize, 1024L);
+ Asserts.assertGTE(max, oldGenMaxSize);
+ Asserts.assertGTE(oldGenMaxSize, edenMaxSize);
+ Asserts.assertGTE(oldGenMaxSize, survivorMaxSize);
+
+ root_array = null; // release
+
+ // shrink to 500M should be fine for any GC
+ String[] contains1 = {
+ "GC.elastic_max_heap (",
+ "GC.elastic_max_heap success"
+ };
+ resizeAndCheck(pid, "100M", contains1, null);
+ mem = ManagementFactory.getMemoryMXBean();
+ usage = mem.getHeapMemoryUsage();
+ max = usage.getMax() / M;
+ committed = usage.getCommitted() / M;
+ used = usage.getUsed() / M;
+ System.out.println("After resize -- Heap Max: " + max +
+ "M, Committed: " + committed +
+ "M, Used: " + used + "M");
+ long target_size = 100L;
+ if (architecture.equals("aarch64")) {
+ target_size = 128L;
+ }
+ Asserts.assertLTE(max, target_size);
+ Asserts.assertGTE(max, committed);
+ Asserts.assertGTE(committed, used);
+
+ // check eden, survivor and old memory pool after resize
+ get_memory_info();
+ System.out.println("After resize -- Eden Max: " + edenMaxSize +
+ "M, Survivor Max: " + survivorMaxSize +
+ "M, Old Max: " + oldGenMaxSize + "M");
+ Asserts.assertLT(oldGenMaxSize, orig_old);
+ Asserts.assertLTE(edenMaxSize, orig_eden);
+ }
+
+ static void alloc_and_free(long size) {
+ // suppose compressed
+ // each int array size is 8(MarkOop + len) + 4 * len
+ // each object is 1k int[254]
+ int root_len = (int)(size / 1024L);
+ root_array = new Object[root_len];
+ for (int i = 0; i < root_len; i++) {
+ root_array[i] = new int[254];
+ }
+ }
+
+ static void get_memory_info() {
+ List<MemoryPoolMXBean> memoryPoolMXBeans = ManagementFactory.getMemoryPoolMXBeans();
+ for (MemoryPoolMXBean memoryPoolMXBean : memoryPoolMXBeans) {
+ String name = memoryPoolMXBean.getName();
+ MemoryUsage usage = memoryPoolMXBean.getUsage();
+ long max_size = usage.getMax() / M;
+ if (name.contains("Eden")) {
+ edenMaxSize = max_size;
+ } else if (name.contains("Survivor")) {
+ survivorMaxSize = max_size;
+ } else if (name.contains("Old")) {
+ oldGenMaxSize = max_size;
+ }
+ }
+ }
+}
new file mode 100644
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
+ * Copyright (C) 2023 THL A29 Limited, a Tencent company. 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.
+ */
+
+package gc.dynamicmaxheap;
+
+import jdk.test.lib.process.OutputAnalyzer;
+import jdk.test.lib.JDKToolFinder;
+import jdk.test.lib.process.ProcessTools;
+import jdk.test.lib.Asserts;
+
+/**
+ * @test OptionsCheck
+ * @summary test invalid options combinations with elastic max heap
+ * @requires (os.family == "linux") & (os.arch == "aarch64")
+ * @library /test/lib
+ * @build gc.dynamicmaxheap.TestBase
+ * @run driver gc.dynamicmaxheap.OptionsCheck
+ */
+public class OptionsCheck extends TestBase {
+ public static void main(String[] args) throws Exception {
+ String[] key_output = {
+ "can not be used with",
+ };
+ String[] key_output2 = {
+ "should be used with",
+ };
+ LaunchAndCheck(key_output, null, "-XX:+ElasticMaxHeap", "-Xmn200M", "-version");
+ LaunchAndCheck(key_output, null, "-XX:+ElasticMaxHeap", "-XX:MaxNewSize=300M", "-version");
+ LaunchAndCheck(key_output, null, "-XX:+ElasticMaxHeap", "-XX:OldSize=1G", "-version");
+ LaunchAndCheck(key_output2, null, "-XX:+ElasticMaxHeap", "-XX:-UseAdaptiveSizePolicy", "-version");
+ String[] contains1 = {
+ "-XX:ElasticMaxHeapSize should be used together with -Xmx/-XX:MaxHeapSize"
+ };
+ LaunchAndCheck(contains1, null, "-XX:+ElasticMaxHeap", "-XX:ElasticMaxHeapSize=100M", "-version");
+ String[] contains2 = {
+ "-XX:ElasticMaxHeapSize should be larger than -Xmx/-XX:MaxHeapSize"
+ };
+ LaunchAndCheck(contains2, null, "-XX:+ElasticMaxHeap", "-XX:ElasticMaxHeapSize=1G", "-Xmx2G", "-version");
+ }
+
+ public static void LaunchAndCheck(String[] contains, String[] not_contains, String... command) throws Exception {
+ ProcessBuilder pb = ProcessTools.createLimitedTestJavaProcessBuilder(command);
+ OutputAnalyzer output = new OutputAnalyzer(pb.start());
+ System.out.println(output.getOutput());
+ if (contains != null) {
+ for (String s : contains) {
+ output.shouldContain(s);
+ }
+ }
+ if (not_contains != null) {
+ for (String s : contains) {
+ output.shouldNotContain(s);
+ }
+ }
+ }
+}
new file mode 100644
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
+ * Copyright (C) 2023, 2024 THL A29 Limited, a Tencent company. 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.
+ */
+
+package gc.dynamicmaxheap;
+
+import jdk.test.lib.process.OutputAnalyzer;
+import jdk.test.lib.JDKToolFinder;
+import jdk.test.lib.process.ProcessTools;
+import jdk.test.lib.Asserts;
+
+/**
+ * @test RuntimeMemoryTest
+ * @summary test java.lang.Runtime max memory and total memory
+ * @requires (os.family == "linux") & (os.arch == "aarch64")
+ * @library /test/lib
+ * @build gc.dynamicmaxheap.TestBase
+ * @run main/othervm -Xms50M -Xmx2G -XX:+ElasticMaxHeap -XX:+UseParallelGC gc.dynamicmaxheap.RuntimeMemoryTest
+ * @run main/othervm -Xms50M -Xmx2G -XX:+ElasticMaxHeap -XX:+UseG1GC gc.dynamicmaxheap.RuntimeMemoryTest
+ */
+public class RuntimeMemoryTest extends TestBase {
+ static Object[] root_array;
+ static final long M = 1024L * 1024L;
+ public static void main(String[] args) throws Exception {
+ String architecture = System.getProperty("os.arch");
+ long pid = ProcessTools.getProcessId();
+ /*
+ * Steps: start with 2G heap
+ * 1. start and allocate about 1G object
+ * 2. get totalMemory/maxMemory/freeMemory as expected
+ * 3. launch jcmd resize to 100M and expect success
+ * 4. get totalMemory/maxMemory/freeMemory as expected
+ */
+ Runtime r = Runtime.getRuntime();
+ // GC rarely happens between these call, should align with size
+ long max = r.maxMemory() / M;
+ long total = r.totalMemory() / M;
+ long free = r.freeMemory() / M;
+ System.out.println("Before alloc -- Max: " + max + "M, Total: " + total + "M, Free: " + free + "M");
+ alloc_and_free(1024L * 1024L * 1024L);
+
+ max = r.maxMemory() / M;
+ total = r.totalMemory() / M;
+ free = r.freeMemory() / M;
+ root_array = null; // release
+ System.out.println("After alloc -- Max: " + max + "M, Total: " + total + "M, Free: " + free + "M");
+ Asserts.assertGT(max, 1024L);
+ Asserts.assertGTE(max, total);
+ Asserts.assertGT(max, free);
+
+ // shrink to 500M should be fine for any GC
+ String[] contains1 = {
+ "GC.elastic_max_heap (",
+ "GC.elastic_max_heap success"
+ };
+ resizeAndCheck(pid, "100M", contains1, null);
+ max = r.maxMemory() / M;
+ total = r.totalMemory() / M;
+ free = r.freeMemory() / M;
+ System.out.println("After resize -- Max: " + max + "M, Total: " + total + "M, Free: " + free + "M");
+ long target_size = 101L;
+ if (architecture.equals("aarch64")) {
+ target_size = 129L;
+ }
+ Asserts.assertLT(max, target_size);
+ Asserts.assertGTE(max, total);
+ Asserts.assertGT(max, free);
+ }
+
+ static void alloc_and_free(long size) {
+ // suppose compressed
+ // each int array size is 8(MarkOop + len) + 4 * len
+ // each object is 1k int[254]
+ int root_len = (int)(size / 1024L);
+ root_array = new Object[root_len];
+ for (int i = 0; i < root_len; i++) {
+ root_array[i] = new int[254];
+ }
+ }
+}
new file mode 100644
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
+ * Copyright (C) 2023 THL A29 Limited, a Tencent company. 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.
+ */
+
+package gc.dynamicmaxheap;
+
+import java.lang.reflect.Field;
+import jdk.test.lib.process.OutputAnalyzer;
+import jdk.test.lib.JDKToolFinder;
+import jdk.test.lib.process.ProcessTools;
+import jdk.test.lib.Asserts;
+
+public class TestBase {
+ // start jcmd and check output string
+ public static void resizeAndCheck(long pid, String new_size, String[] contains, String[] not_contains) throws Exception {
+ resizeAndCheck(pid, new_size, contains, not_contains, "GC.elastic_max_heap");
+ }
+ public static void resizeAndCheck(long pid, String new_size, String[] contains, String[] not_contains, String type) throws Exception {
+ ProcessBuilder pb = new ProcessBuilder();
+ pb.command(new String[] { JDKToolFinder.getJDKTool("jcmd"), Long.toString(pid), type, new_size});
+ OutputAnalyzer output = new OutputAnalyzer(pb.start());
+ System.out.println(output.getOutput());
+ if (contains != null) {
+ for (String s : contains) {
+ output.shouldContain(s);
+ }
+ }
+ if (not_contains != null) {
+ for (String s : not_contains) {
+ output.shouldNotContain(s);
+ }
+ }
+ }
+}
new file mode 100644
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
+ * Copyright (C) 2023 THL A29 Limited, a Tencent company. 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.
+ */
+
+import jdk.test.lib.process.ProcessTools;
+import jdk.test.lib.JDKToolFinder;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.nio.ByteBuffer;
+
+public class LimitDirectMemoryTestBasic {
+ public static void main(String[] args) throws Exception {
+ long pid = ProcessTools.getProcessId();
+ String new_size = args[0];
+ int alloc_size = Integer.parseInt(args[1]);
+
+ resize(pid, new_size);
+
+ try {
+ // alloc direct memory
+ int single_alloc_size = 1 * 1024 * 1024;
+ ByteBuffer[] buffers = new ByteBuffer[alloc_size];
+ for (int i = 0; i < alloc_size; i++) {
+ buffers[i] = ByteBuffer.allocateDirect(single_alloc_size);
+ }
+ } catch (OutOfMemoryError e) {
+ System.out.println(e);
+ throw e;
+ }
+ System.out.println("allocation finish!");
+ }
+
+ static void resize(long pid, String new_size) {
+ try {
+ Process process = Runtime.getRuntime().exec(JDKToolFinder.getJDKTool("jcmd") + " " + pid + " GC.elastic_max_direct_memory " + new_size);
+ BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
+ String line;
+ while ((line = reader.readLine()) != null) {
+ System.out.println(line);
+ }
+ reader.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+}
\ No newline at end of file
new file mode 100644
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
+ * Copyright (C) 2023 THL A29 Limited, a Tencent company. 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.
+ */
+
+import java.nio.ByteBuffer;
+
+public class NotActiveDirectMemory {
+ public static void main(String[] args) throws Exception {
+ // alloc 100M direct memory
+ try {
+ int single_alloc_size = 1 * 1024 * 1024;
+ ByteBuffer[] buffers = new ByteBuffer[100];
+ for (int i = 0; i < 100; i++) {
+ buffers[i] = ByteBuffer.allocateDirect(single_alloc_size);
+ }
+ } catch (OutOfMemoryError e) {
+ System.out.println(e);
+ throw e;
+ }
+ System.out.println("allocation finish!");
+ while (true) {
+ Thread.sleep(1000);
+ }
+ }
+}
new file mode 100644
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
+ * Copyright (C) 2023 THL A29 Limited, a Tencent company. 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.
+ */
+
+public class NotActiveHeap {
+ public static void main(String[] args) throws Exception {
+ while (true) {
+ Thread.sleep(1000);
+ }
+ }
+}
--
2.50.1