From 8ba600d6f314e2162bd63230623e8776333858a0 Mon Sep 17 00:00:00 2001 From: Aleksey Shipilev Date: Wed, 3 Jun 2026 04:32:58 +0000 Subject: [PATCH 01/61] 8385333: Shenandoah: Final mark spins up workers without work to do Reviewed-by: xpeng, kdnilsen --- .../gc/shenandoah/shenandoahClosures.hpp | 12 ++ .../shenandoah/shenandoahConcurrentMark.cpp | 107 ++++++++---------- .../gc/shenandoah/shenandoahPhaseTimings.hpp | 1 + 3 files changed, 62 insertions(+), 58 deletions(-) diff --git a/src/hotspot/share/gc/shenandoah/shenandoahClosures.hpp b/src/hotspot/share/gc/shenandoah/shenandoahClosures.hpp index 9ab45380c61..976a505c713 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahClosures.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahClosures.hpp @@ -253,4 +253,16 @@ public: }; #endif // ASSERT +class ShenandoahMultiThreadClosure : public ThreadClosure { + ThreadClosure& _cl1; + ThreadClosure& _cl2; +public: + ShenandoahMultiThreadClosure(ThreadClosure& cl1, ThreadClosure& cl2) : + _cl1(cl1), _cl2(cl2) {} + inline void do_thread(Thread* thread) override { + _cl1.do_thread(thread); + _cl2.do_thread(thread); + } +}; + #endif // SHARE_GC_SHENANDOAH_SHENANDOAHCLOSURES_HPP diff --git a/src/hotspot/share/gc/shenandoah/shenandoahConcurrentMark.cpp b/src/hotspot/share/gc/shenandoah/shenandoahConcurrentMark.cpp index 4db8821399f..31ffbc817f1 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahConcurrentMark.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahConcurrentMark.cpp @@ -59,6 +59,10 @@ public: ShenandoahWorkerTimingsTracker timer(ShenandoahPhaseTimings::conc_mark, ShenandoahPhaseTimings::Work, worker_id, true); SuspendibleThreadSetJoiner stsj; _cm->mark_loop(worker_id, _terminator, GENERATION, true /*cancellable*/); + // Concurrent marking loop flushes Java thread buffers, coordinating with a handshake. + // Here, a GC worker has completed marking work, so it is a good time to flush its SATB buffers too. + SATBMarkQueueSet& satb_mq_set = ShenandoahBarrierSet::satb_mark_queue_set(); + satb_mq_set.flush_queue(ShenandoahThreadLocalData::satb_mark_queue(Thread::current())); } }; @@ -67,32 +71,14 @@ class ShenandoahFinalMarkingTask : public WorkerTask { private: ShenandoahConcurrentMark* _cm; TaskTerminator* _terminator; - ThreadsClaimTokenScope _threads_claim_token_scope; // needed for Threads::possibly_parallel_threads_do public: ShenandoahFinalMarkingTask(ShenandoahConcurrentMark* cm, TaskTerminator* terminator) : - WorkerTask("Shenandoah Final Mark"), _cm(cm), _terminator(terminator), - _threads_claim_token_scope() { - } + WorkerTask("Shenandoah Final Mark"), _cm(cm), _terminator(terminator) {} void work(uint worker_id) { - ShenandoahHeap* heap = ShenandoahHeap::heap(); - ShenandoahWorkerTimingsTracker timer(ShenandoahPhaseTimings::finish_mark, ShenandoahPhaseTimings::Work, worker_id, true); ShenandoahParallelWorkerSession worker_session(worker_id); - // First drain remaining SATB buffers. - { - ShenandoahObjToScanQueue* q = _cm->get_queue(worker_id); - ShenandoahObjToScanQueue* old_q = _cm->get_old_queue(worker_id); - - ShenandoahSATBBufferClosure cl(q, old_q); - SATBMarkQueueSet& satb_mq_set = ShenandoahBarrierSet::satb_mark_queue_set(); - while (satb_mq_set.apply_closure_to_completed_buffer(&cl)) {} - assert(!heap->has_forwarded_objects(), "Not expected"); - - ShenandoahFlushSATB tc(satb_mq_set); - Threads::possibly_parallel_threads_do(true /* is_par */, &tc); - } _cm->mark_loop(worker_id, _terminator, GENERATION, false /*not cancellable*/); assert(_cm->task_queues()->is_empty(), "Should be empty"); } @@ -255,49 +241,54 @@ void ShenandoahConcurrentMark::finish_mark() { } void ShenandoahConcurrentMark::finish_mark_work() { - // Finally mark everything else we've got in our queues during the previous steps. - // It does two different things for concurrent vs. mark-compact GC: - // - For concurrent GC, it starts with empty task queues, drains the remaining - // SATB buffers, and then completes the marking closure. - // - For mark-compact GC, it starts out with the task queues seeded by initial - // root scan, and completes the closure, thus marking through all live objects - // The implementation is the same, so it's shared here. ShenandoahHeap* const heap = ShenandoahHeap::heap(); - ShenandoahGCPhase phase(ShenandoahPhaseTimings::finish_mark); - uint nworkers = heap->workers()->active_workers(); - task_queues()->reserve(nworkers); + SATBMarkQueueSet& satb_mq_set = ShenandoahBarrierSet::satb_mark_queue_set(); - TaskTerminator terminator(nworkers, task_queues()); - - switch (_generation->type()) { - case YOUNG:{ - ShenandoahFinalMarkingTask task(this, &terminator); - heap->workers()->run_task(&task); - break; - } - case OLD:{ - ShenandoahFinalMarkingTask task(this, &terminator); - heap->workers()->run_task(&task); - break; - } - case GLOBAL:{ - ShenandoahFinalMarkingTask task(this, &terminator); - heap->workers()->run_task(&task); - break; - } - case NON_GEN:{ - ShenandoahFinalMarkingTask task(this, &terminator); - heap->workers()->run_task(&task); - break; - } - default: - ShouldNotReachHere(); + // First drain all remaining SATB buffers and put them to SATB MQ. + // Also, while we are iterating threads, mark the invisible roots. + { + ShenandoahTimingsTracker t(ShenandoahPhaseTimings::final_mark_flush_satb_roots); + ShenandoahInvisibleRootsMarkClosure invisible_cl; + ShenandoahFlushSATB flush_cl(satb_mq_set); + ShenandoahMultiThreadClosure mux(flush_cl, invisible_cl); + Threads::threads_do(&mux); } - if (!generation()->is_old() && heap->is_concurrent_young_mark_in_progress()) { - // Lastly, ensure all the invisible roots are marked. - ShenandoahInvisibleRootsMarkClosure cl; - Threads::java_threads_do(&cl); + + // There is a very high chance we have already completed the marking. + // But if there is outstanding work, finish it now. + if (!task_queues()->is_empty() || satb_mq_set.completed_buffers_num() > 0) { + ShenandoahGCPhase phase(ShenandoahPhaseTimings::finish_mark); + + uint nworkers = heap->workers()->active_workers(); + task_queues()->reserve(nworkers); + TaskTerminator terminator(nworkers, task_queues()); + + switch (_generation->type()) { + case YOUNG:{ + ShenandoahFinalMarkingTask task(this, &terminator); + heap->workers()->run_task(&task); + break; + } + case OLD:{ + ShenandoahFinalMarkingTask task(this, &terminator); + heap->workers()->run_task(&task); + break; + } + case GLOBAL:{ + ShenandoahFinalMarkingTask task(this, &terminator); + heap->workers()->run_task(&task); + break; + } + case NON_GEN:{ + ShenandoahFinalMarkingTask task(this, &terminator); + heap->workers()->run_task(&task); + break; + } + default: + ShouldNotReachHere(); + } } assert(task_queues()->is_empty(), "Should be empty"); + assert(satb_mq_set.completed_buffers_num() == 0, "Should be empty"); } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahPhaseTimings.hpp b/src/hotspot/share/gc/shenandoah/shenandoahPhaseTimings.hpp index 385ab10893c..bc52d755139 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahPhaseTimings.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahPhaseTimings.hpp @@ -70,6 +70,7 @@ class outputStream; SHENANDOAH_SIMPLE_PHASE_DEF(f, final_mark_gross, "Pause Final Mark (G)") \ SHENANDOAH_SIMPLE_PHASE_DEF(f, final_mark, "Pause Final Mark (N)") \ SHENANDOAH_SIMPLE_PHASE_DEF(f, final_mark_verify, " Verify") \ + SHENANDOAH_SIMPLE_PHASE_DEF(f, final_mark_flush_satb_roots, " Flush SATB and Roots") \ SHENANDOAH_WORKER_PHASE_DEF(f, finish_mark, " Finish Mark", \ " FM: ") \ SHENANDOAH_SIMPLE_PHASE_DEF(f, final_mark_propagate_gc_state, " Propagate GC State") \ From 39de79eae23410e335d2d1ced8fe3b4d7937a541 Mon Sep 17 00:00:00 2001 From: Erik Gahlin Date: Wed, 3 Jun 2026 06:33:32 +0000 Subject: [PATCH 02/61] 8367584: Implement JEP 536: JFR In-Process Data Redaction Reviewed-by: mgronlun --- src/hotspot/share/jfr/dcmd/jfrDcmds.cpp | 106 ++- src/hotspot/share/jfr/dcmd/jfrDcmds.hpp | 3 +- .../share/jfr/periodic/jfrOSInterface.cpp | 33 +- .../share/jfr/periodic/jfrOSInterface.hpp | 3 +- .../share/jfr/periodic/jfrPeriodic.cpp | 34 +- .../share/jfr/periodic/jfrRedactedEvents.cpp | 663 ++++++++++++++++++ .../share/jfr/periodic/jfrRedactedEvents.hpp | 223 ++++++ .../share/jfr/recorder/jfrRecorder.cpp | 4 + .../jfr/recorder/service/jfrOptionSet.cpp | 38 +- src/hotspot/share/logging/logTag.hpp | 1 + .../share/services/diagnosticFramework.hpp | 5 +- src/java.base/share/man/java.md | 43 +- test/jdk/jdk/jfr/startupargs/Application.java | 32 + .../jdk/jfr/startupargs/TestOptionsHelp.java | 47 ++ test/jdk/jdk/jfr/startupargs/TestRedact.java | 456 ++++++++++++ .../jfr/startupargs/redacted-arguments.txt | 6 + .../jdk/jdk/jfr/startupargs/redacted-keys.txt | 2 + 17 files changed, 1621 insertions(+), 78 deletions(-) create mode 100644 src/hotspot/share/jfr/periodic/jfrRedactedEvents.cpp create mode 100644 src/hotspot/share/jfr/periodic/jfrRedactedEvents.hpp create mode 100644 test/jdk/jdk/jfr/startupargs/Application.java create mode 100644 test/jdk/jdk/jfr/startupargs/TestOptionsHelp.java create mode 100644 test/jdk/jdk/jfr/startupargs/TestRedact.java create mode 100644 test/jdk/jdk/jfr/startupargs/redacted-arguments.txt create mode 100644 test/jdk/jdk/jfr/startupargs/redacted-keys.txt diff --git a/src/hotspot/share/jfr/dcmd/jfrDcmds.cpp b/src/hotspot/share/jfr/dcmd/jfrDcmds.cpp index 549d879de99..a41515edfbb 100644 --- a/src/hotspot/share/jfr/dcmd/jfrDcmds.cpp +++ b/src/hotspot/share/jfr/dcmd/jfrDcmds.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2026, Oracle and/or its affiliates. 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 @@ -27,6 +27,7 @@ #include "jfr/dcmd/jfrDcmds.hpp" #include "jfr/jfr.hpp" #include "jfr/jni/jfrJavaSupport.hpp" +#include "jfr/periodic/jfrRedactedEvents.hpp" #include "jfr/recorder/jfrRecorder.hpp" #include "jfr/recorder/service/jfrOptionSet.hpp" #include "jfr/support/jfrThreadLocal.hpp" @@ -401,11 +402,25 @@ JfrConfigureFlightRecorderDCmd::JfrConfigureFlightRecorderDCmd(outputStream* out }; void JfrConfigureFlightRecorderDCmd::print_help(const char* name) const { - outputStream* out = output(); + print_help(output(), false); +} + +static void print_filters(outputStream* out, JfrRedactedEvents::StringArray* filters) { + for (int i = 0; i < filters->length(); i++) { + out->print_cr(" %s", filters->at(i)->text()); + } +} + +void JfrConfigureFlightRecorderDCmd::print_help(outputStream* out, bool startup) { // 0123456789001234567890012345678900123456789001234567890012345678900123456789001234567890 + if (startup) { + out->print_cr("Syntax : -XX:FlightRecorderOptions:[options]"); + out->print_cr(""); + } out->print_cr("Options:"); out->print_cr(""); - out->print_cr(" globalbuffercount (Optional) Number of global buffers. This option is a legacy"); + out->print_cr( " %-19s (Optional) Number of global buffers. This option is a legacy", + startup ? "numglobalbuffers": "globalbuffercount"); out->print_cr(" option: change the memorysize parameter to alter the number of"); out->print_cr(" global buffers. This value cannot be changed once JFR has been"); out->print_cr(" initialized. (STRING, default determined by the value for"); @@ -427,7 +442,8 @@ void JfrConfigureFlightRecorderDCmd::print_help(const char* name) const { out->print_cr(" gigabytes. This value cannot be changed once JFR has been"); out->print_cr(" initialized. (STRING, 10M)"); out->print_cr(""); - out->print_cr(" repositorypath (Optional) Path to the location where recordings are stored until"); + out->print_cr( " %-19s (Optional) Path to the location where recordings are stored until", + startup ? "repository" : "repositorypath"); out->print_cr(" they are written to a permanent file. (STRING, The default"); out->print_cr(" location is the temporary directory for the operating system. On"); out->print_cr(" Linux operating systems, the temporary directory is /tmp. On"); @@ -443,7 +459,8 @@ void JfrConfigureFlightRecorderDCmd::print_help(const char* name) const { out->print_cr(" degradation. This value cannot be changed once JFR has been"); out->print_cr(" initialized. (LONG, 64)"); out->print_cr(""); - out->print_cr(" thread_buffer_size (Optional) Local buffer size for each thread in bytes if one of"); + out->print_cr( " %-19s (Optional) Local buffer size for each thread in bytes if one of", + startup ? "threadbuffersize" : "thread_buffer_size"); out->print_cr(" the following suffixes is not used: 'k' or 'K' for kilobytes or"); out->print_cr(" 'm' or 'M' for megabytes. Overriding this parameter could reduce"); out->print_cr(" performance and is not recommended. This value cannot be changed"); @@ -452,14 +469,77 @@ void JfrConfigureFlightRecorderDCmd::print_help(const char* name) const { out->print_cr(" preserve-repository (Optional) Preserve files stored in the disk repository after the"); out->print_cr(" Java Virtual Machine has exited. (BOOLEAN, false)"); out->print_cr(""); - out->print_cr("Options must be specified using the or = syntax."); - out->print_cr(""); - out->print_cr("Example usage:"); - out->print_cr(""); - out->print_cr(" $ jcmd JFR.configure"); - out->print_cr(" $ jcmd JFR.configure repositorypath=/temporary"); - out->print_cr(" $ jcmd JFR.configure stackdepth=256"); - out->print_cr(" $ jcmd JFR.configure memorysize=100M"); + if (startup) { + out->print_cr(" old-object-queue-size (Optional) Maximum number of old objects to track. By default,"); + out->print_cr(" the number of objects is set to 256. (LONG, 256)"); + out->print_cr(""); + out->print_cr(" redact-argument (Optional) Replace command-line arguments that match a"); + out->print_cr(" semicolon-separated list of glob patterns, for example,"); + out->print_cr(" *secret*;password*. Matching is case-insensitive, and the"); + out->print_cr(" supported wildcards are '*' and '?'. To redact multiple arguments,"); + out->print_cr(" use a literal space (' ') as a separator. For example, to match"); + out->print_cr(" the two arguments --auth username:token, use the filter"); + out->print_cr(" --auth *:*. Filters containing spaces must be quoted as a single"); + out->print_cr(" command-line argument, for example,"); + out->print_cr(" -XX:FlightRecorderOptions:'redact-argument=--auth *:*'."); + out->print_cr(" Arguments containing spaces might not be matched as expected."); + out->print_cr(" The option redact-argument is best-effort and applies only to"); + out->print_cr(" command-line arguments in the jdk.JVMInformation event and to"); + out->print_cr(" the java.command system property in the jdk.InitialSystemProperty"); + out->print_cr(" event. Other events, such as jdk.ProcessStart (child processes),"); + out->print_cr(" are not redacted."); + out->print_cr(""); + out->print_cr(" If the redact-argument option is not specified, the following"); + out->print_cr(" filters are used by default:"); + out->print_cr(""); + print_filters(out, JfrRedactedEvents::argument_filters()); + out->print_cr(""); + out->print_cr(" To load patterns from a file (one per line), use @."); + out->print_cr(" To add to the default patterns instead of replacing them, prefix"); + out->print_cr(" the whole list with '+', for example, +*foo*;@redact.txt."); + out->print_cr(" Use 'none' (lowercase) to disable all redaction filters for"); + out->print_cr(" command-line arguments. Redacted arguments will be replaced"); + out->print_cr(" with '[REDACTED]'. (STRING, default filters)"); + out->print_cr(""); + out->print_cr(" redact-key (Optional) Replace the value of environment variables and system"); + out->print_cr(" properties whose key matches a semicolon-separated list of glob"); + out->print_cr(" patterns, for example, *password*;*token*. Matching is"); + out->print_cr(" case-insensitive, and the supported wildcards are '*' and '?'."); + out->print_cr(" The option redact-key is best-effort and applies only to the"); + out->print_cr(" jdk.InitialSystemProperty, jdk.InitialEnvironmentVariable and"); + out->print_cr(" jdk.JVMInformation (-Dkey...) events. Other events, such as"); + out->print_cr(" jdk.InitialSecurityProperty, are not redacted."); + out->print_cr(""); + out->print_cr(" If the redact-key option is not specified, the"); + out->print_cr(" following filters are used by default:"); + out->print_cr(""); + print_filters(out, JfrRedactedEvents::key_filters()); + out->print_cr(""); + out->print_cr(" To load patterns from a file (one per line), use @."); + out->print_cr(" To add to the default patterns instead of replacing them, prefix"); + out->print_cr(" the whole list with '+', for example, +*cred*;@keys.txt."); + out->print_cr(" Use 'none' (lowercase) to disable all redaction filters for key"); + out->print_cr(" matching. Redacted values will be replaced with '[REDACTED]'."); + out->print_cr(" (STRING, default filters)"); + out->print_cr(""); + out->print_cr("Options must be specified using the = syntax. Multiple options are separated"); + out->print_cr("with a comma."); + out->print_cr(""); + out->print_cr("Example usage:"); + out->print_cr(""); + out->print_cr(" -XX:FlightRecorderOptions:repository=/temporary,stackdepth=256"); + out->print_cr(" -XX:FlightRecorderOptions:'redact-key=+*confidential*;*private*,redact-argument=+https://*:*@*'"); + out->print_cr(" -XX:FlightRecorderOptions:'redact-argument=+-*private *'"); + } else { + out->print_cr("Options must be specified using the or = syntax."); + out->print_cr(""); + out->print_cr("Example usage:"); + out->print_cr(""); + out->print_cr(" $ jcmd JFR.configure"); + out->print_cr(" $ jcmd JFR.configure repositorypath=/temporary"); + out->print_cr(" $ jcmd JFR.configure stackdepth=256"); + out->print_cr(" $ jcmd JFR.configure memorysize=100M"); + } out->print_cr(""); } diff --git a/src/hotspot/share/jfr/dcmd/jfrDcmds.hpp b/src/hotspot/share/jfr/dcmd/jfrDcmds.hpp index 8e7dad5a20c..aecd187a2e0 100644 --- a/src/hotspot/share/jfr/dcmd/jfrDcmds.hpp +++ b/src/hotspot/share/jfr/dcmd/jfrDcmds.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2026, Oracle and/or its affiliates. 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 @@ -202,6 +202,7 @@ class JfrConfigureFlightRecorderDCmd : public DCmdWithParser { return "Low"; } static int num_arguments() { return 10; } + static void print_help(outputStream* out, bool startup); virtual void execute(DCmdSource source, TRAPS); virtual void print_help(const char* name) const; }; diff --git a/src/hotspot/share/jfr/periodic/jfrOSInterface.cpp b/src/hotspot/share/jfr/periodic/jfrOSInterface.cpp index 18b2d7c5785..71bcf7e02bf 100644 --- a/src/hotspot/share/jfr/periodic/jfrOSInterface.cpp +++ b/src/hotspot/share/jfr/periodic/jfrOSInterface.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2026, Oracle and/or its affiliates. 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 @@ -268,37 +268,6 @@ const char* JfrOSInterface::virtualization_name() { return "No virtualization detected"; } -int JfrOSInterface::generate_initial_environment_variable_events() { - if (os::get_environ() == nullptr) { - return OS_ERR; - } - - if (EventInitialEnvironmentVariable::is_enabled()) { - // One time stamp for all events, so they can be grouped together - JfrTicks time_stamp = JfrTicks::now(); - for (char** p = os::get_environ(); *p != nullptr; p++) { - char* variable = *p; - char* equal_sign = strchr(variable, '='); - if (equal_sign != nullptr) { - // Extract key/value - ResourceMark rm; - ptrdiff_t key_length = equal_sign - variable; - char* key = NEW_RESOURCE_ARRAY(char, key_length + 1); - char* value = equal_sign + 1; - strncpy(key, variable, key_length); - key[key_length] = '\0'; - EventInitialEnvironmentVariable event(UNTIMED); - event.set_starttime(time_stamp); - event.set_endtime(time_stamp); - event.set_key(key); - event.set_value(value); - event.commit(); - } - } - } - return OS_OK; -} - int JfrOSInterface::system_processes(SystemProcess** sys_processes, int* no_of_sys_processes) { return instance()._impl->system_processes(sys_processes, no_of_sys_processes); } diff --git a/src/hotspot/share/jfr/periodic/jfrOSInterface.hpp b/src/hotspot/share/jfr/periodic/jfrOSInterface.hpp index 141e7505efb..9e8d4837a52 100644 --- a/src/hotspot/share/jfr/periodic/jfrOSInterface.hpp +++ b/src/hotspot/share/jfr/periodic/jfrOSInterface.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2026, Oracle and/or its affiliates. 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 @@ -53,7 +53,6 @@ class JfrOSInterface: public JfrCHeapObj { static int cpu_loads_process(double* pjvmUserLoad, double* pjvmKernelLoad, double* psystemTotalLoad); static int os_version(char** os_version); static const char* virtualization_name(); - static int generate_initial_environment_variable_events(); static int system_processes(SystemProcess** system_processes, int* no_of_sys_processes); static int network_utilization(NetworkInterface** network_interfaces); }; diff --git a/src/hotspot/share/jfr/periodic/jfrPeriodic.cpp b/src/hotspot/share/jfr/periodic/jfrPeriodic.cpp index 92f406bd095..83d58c704fc 100644 --- a/src/hotspot/share/jfr/periodic/jfrPeriodic.cpp +++ b/src/hotspot/share/jfr/periodic/jfrPeriodic.cpp @@ -41,6 +41,7 @@ #include "jfr/periodic/jfrNativeMemoryEvent.hpp" #include "jfr/periodic/jfrNetworkUtilization.hpp" #include "jfr/periodic/jfrOSInterface.hpp" +#include "jfr/periodic/jfrRedactedEvents.hpp" #include "jfr/periodic/jfrThreadCPULoadEvent.hpp" #include "jfr/periodic/jfrThreadDumpEvent.hpp" #include "jfr/recorder/jfrRecorder.hpp" @@ -100,17 +101,8 @@ TRACE_REQUEST_FUNC(ResidentSetSize) { } TRACE_REQUEST_FUNC(JVMInformation) { - ResourceMark rm; - EventJVMInformation event; - event.set_jvmName(VM_Version::vm_name()); - event.set_jvmVersion(VM_Version::internal_vm_info_string()); - event.set_javaArguments(Arguments::java_command()); - event.set_jvmArguments(Arguments::jvm_args()); - event.set_jvmFlags(Arguments::jvm_flags()); - event.set_jvmStartTime(Management::vm_init_done_time()); - event.set_pid(os::current_process_id()); - event.commit(); - } + JfrRedactedEvents::emit_jvm_information(); +} TRACE_REQUEST_FUNC(OSInformation) { ResourceMark rm; @@ -169,7 +161,9 @@ TRACE_REQUEST_FUNC(NativeLibrary) { } TRACE_REQUEST_FUNC(InitialEnvironmentVariable) { - JfrOSInterface::generate_initial_environment_variable_events(); + if (!JfrRedactedEvents::emit_initial_environment_variables()) { + log_debug(jfr, system)( "Unable to generate periodic event InitialEnvironmentVariable"); + } } TRACE_REQUEST_FUNC(CPUInformation) { @@ -393,7 +387,7 @@ TRACE_REQUEST_FUNC(BooleanFlag) { } TRACE_REQUEST_FUNC(StringFlag) { - SEND_FLAGS_OF_TYPE(StringFlag, ccstr); + JfrRedactedEvents::emit_string_flags(); } class VM_GC_SendObjectCountEvent : public VM_GC_HeapInspection { @@ -476,19 +470,7 @@ TRACE_REQUEST_FUNC(YoungGenerationConfiguration) { } TRACE_REQUEST_FUNC(InitialSystemProperty) { - SystemProperty* p = Arguments::system_properties(); - JfrTicks time_stamp = JfrTicks::now(); - while (p != nullptr) { - if (!p->internal()) { - EventInitialSystemProperty event(UNTIMED); - event.set_key(p->key()); - event.set_value(p->value()); - event.set_starttime(time_stamp); - event.set_endtime(time_stamp); - event.commit(); - } - p = p->next(); - } + JfrRedactedEvents::emit_initial_system_properties(); } TRACE_REQUEST_FUNC(ThreadAllocationStatistics) { diff --git a/src/hotspot/share/jfr/periodic/jfrRedactedEvents.cpp b/src/hotspot/share/jfr/periodic/jfrRedactedEvents.cpp new file mode 100644 index 00000000000..5ace0d0fae4 --- /dev/null +++ b/src/hotspot/share/jfr/periodic/jfrRedactedEvents.cpp @@ -0,0 +1,663 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#include "jfr/jfrEvents.hpp" +#include "jfr/periodic/jfrRedactedEvents.hpp" +#include "jfr/utilities/jfrTime.hpp" +#include "logging/log.hpp" +#include "logging/logMessage.hpp" +#include "runtime/arguments.hpp" +#include "runtime/flags/jvmFlag.hpp" +#include "runtime/os.hpp" +#include "runtime/vm_version.hpp" +#include "services/diagnosticArgument.hpp" +#include "services/diagnosticFramework.hpp" +#include "services/management.hpp" +#include "utilities/ostream.hpp" + +#include +#include +#include + +using StringArray = JfrRedactedEvents::StringArray; +using String = JfrRedactedEvents::String; +using StringFlag = JfrRedactedEvents::StringFlag; +using StringKeyValueArray = GrowableArray*; + +static const char REDACTED[] = "[REDACTED]"; +static const char DELIMITER[] = " "; +static const char REDACT_ARGUMENT_EQUAL[] = "redact-argument="; + +static const size_t REDACTED_LENGTH = sizeof(REDACTED) -1; +static const size_t DELIMITER_LENGTH = sizeof(DELIMITER) -1; +static const size_t REDACT_ARGUMENT_EQUAL_LENGTH = sizeof(REDACT_ARGUMENT_EQUAL) -1; + +String* JfrRedactedEvents::_redacted_java_command_line = nullptr; +String* JfrRedactedEvents::_redacted_jvm_command_line = nullptr; +String* JfrRedactedEvents::_redacted_flags_command_line = nullptr; +String* JfrRedactedEvents::_redacted_flight_recorder_options = nullptr; + +StringKeyValueArray JfrRedactedEvents::_initial_environment_variables = nullptr; +StringKeyValueArray JfrRedactedEvents::_initial_system_properties = nullptr; +GrowableArray* JfrRedactedEvents::_string_flags = nullptr; + +StringArray* JfrRedactedEvents::_redacted_arguments = nullptr; +StringArray* JfrRedactedEvents::_argument_filters = nullptr; +StringArray* JfrRedactedEvents::_key_filters = nullptr; + +bool JfrRedactedEvents::_initialized = false; + +bool JfrRedactedEvents::set_argument_filter(const char* filters) { + assert (_argument_filters == nullptr, "invariant"); + assert (filters != nullptr, "invariant"); + _argument_filters = new StringArray(); + return append_filters(_argument_filters, true, filters); +} + +bool JfrRedactedEvents::set_key_filter(const char* filters) { + assert (_key_filters == nullptr, "invariant"); + assert (filters != nullptr, "invariant"); + _key_filters = new StringArray(); + return append_filters(_key_filters, false, filters); +} + +void JfrRedactedEvents::log_redaction() { + //At this point JFR is not enabled so events will not be emitted + ensure_initialized(); + emit_initial_environment_variables(true); + emit_initial_system_properties(true); + emit_jvm_information(true); + emit_string_flags(true); +} + +StringArray* JfrRedactedEvents::key_filters() { + ensure_initialized(); + return _key_filters; +} + +StringArray* JfrRedactedEvents::argument_filters() { + ensure_initialized(); + return _argument_filters; +} + +void JfrRedactedEvents::add_default_filters(StringArray* target, bool argument) { + if (argument) { + target->add("-*api*key *"); + target->add("-*client*secret *"); + target->add("-*credential *"); + target->add("-*jaas*config *"); + target->add("-*passphrase *"); + target->add("-*passwd *"); + target->add("-*password *"); + target->add("-*private*key *"); + target->add("-*pwd *"); + target->add("-*secret *"); + target->add("-*token *"); + } else { + target->add("*auth*"); + } + target->add("*api*key*"); + target->add("*client*secret*"); + target->add("*credential*"); + target->add("*jaas*config*"); + target->add("*passphrase*"); + target->add("*passwd*"); + target->add("*password*"); + target->add("*private*key*"); + target->add("*pwd*"); + target->add("*secret*"); + target->add("*token*"); +} + +bool JfrRedactedEvents::append_filters(StringArray* target, bool argument, const char* filters) { + const char* option_name = argument ? "redact-argument": "redact-key"; + + if (filters == nullptr) { + log_warning(jfr, redact)("Missing value for option -XX:FlightRecorderOptions:%s", option_name); + return false; + } + if (filters[0] == '\0') { + LogMessage(jfr, redact) msg; + msg.warning("Default redaction filters are replaced. Specify:"); + msg.warning("-XX:FlightRecorderOptions:%s=none to disable filters without a warning.", option_name); + return true; + } + if (strcmp(filters, "none") == 0) { + return true; + } + if (filters[0] == '+') { + filters++; + add_default_filters(target, argument); + } else { + if (strncmp(filters, "none;", 5) != 0) { + LogMessage(jfr, redact) msg; + msg.warning("Default redaction filters are replaced. Prepend with '+' to add filters to the"); + msg.warning("defaults, or specify -XX:FlightRecorderOptions:'%s=none;'", option_name); + msg.warning("to replace without a warning."); + } else { + filters += 5; + } + } + StringArray* filter_array = split(filters, ';'); + bool success = true; + for (int i = 0; i < filter_array->length(); i++) { + const String* filter = filter_array->at(i); + if (strcmp(filter->text(), "none") == 0) { + log_error(jfr, redact)("'none' can only be used as the first filter."); + success = false; + break; + } + if (filter->at(0) == '@') { + const char* filename = filter->text() + 1; + if (!read_file(target, filename)) { + success = false; + } + } else { + target->add_contents(filter); + } + } + delete filter_array; + return success; +} + +char* JfrRedactedEvents::new_redacted_text() { + char* result = NEW_C_HEAP_ARRAY(char, REDACTED_LENGTH + 1, mtTracing); + strncpy(result, REDACTED, REDACTED_LENGTH + 1); + return result; +} + +bool JfrRedactedEvents::emit_initial_environment_variables(bool log) { + if (_initial_environment_variables == nullptr) { + ensure_initialized(); + char** envp = os::get_environ(); + if (envp == nullptr) { + return false; + } + _initial_environment_variables = make_array(0); + for (char** p = envp; *p != nullptr; ++p) { + char* variable = *p; + char* equal_sign = strchr(variable, '='); + if (equal_sign != nullptr) { + ptrdiff_t length = equal_sign - variable; + String* key = new String(variable, (size_t)length); + const char* value = equal_sign + 1; + if (is_redacted_key(key->text())) { + value = REDACTED; + if (log) { + log_debug(jfr, redact)("Redacted initial environment variable named '%s'", key->text()); + } + } + _initial_environment_variables->append(new StringKeyValue(key, value)); + } + } + } + JfrTicks time_stamp = JfrTicks::now(); + for (int i = 0; i < _initial_environment_variables->length(); i++) { + StringKeyValue* pair = _initial_environment_variables->at(i); + EventInitialEnvironmentVariable event(UNTIMED); + event.set_starttime(time_stamp); + event.set_endtime(time_stamp); + event.set_key(pair->key()); + event.set_value(pair->value()); + event.commit(); + } + return true; +} + +void JfrRedactedEvents::emit_initial_system_properties(bool log) { + if (_initial_system_properties == nullptr) { + ensure_initialized(); + _initial_system_properties = make_array(0); + for (SystemProperty* p = Arguments::system_properties(); p != nullptr; p = p->next()) { + if (!p->internal()) { + const char* value = p->value(); + if (strcmp(p->key(), "sun.java.command") == 0) { + value = String::c_str(_redacted_java_command_line); + } + if (is_redacted_key(p->key())) { + value = REDACTED; + } + if (log && value != nullptr && strstr(value, REDACTED) != nullptr) { + log_debug(jfr, redact)("Redacted initial system property named '%s'", p->key()); + } + _initial_system_properties->append(new StringKeyValue(p->key(), value)); + } + } + } + + JfrTicks time_stamp = JfrTicks::now(); + for (int i = 0; i < _initial_system_properties->length(); i++) { + StringKeyValue* pair = _initial_system_properties->at(i); + EventInitialSystemProperty event(UNTIMED); + event.set_key(pair->key()); + event.set_value(pair->value()); + event.set_starttime(time_stamp); + event.set_endtime(time_stamp); + event.commit(); + } +} + +bool JfrRedactedEvents::match_flag(const char* flag_name, const char* arg) { + if (flag_name == nullptr || arg == nullptr) { + return false; + } + while (*flag_name) { + if (*arg != *flag_name) { + return false; + } + flag_name++; + arg++; + } + return *arg == '\0' || *arg == '='; +} + +void JfrRedactedEvents::emit_string_flags(bool log) { + if (_string_flags == nullptr) { + ensure_initialized(); + _string_flags = make_array(0); + JVMFlag *flag = JVMFlag::flags; + while (flag->name() != nullptr) { + if (flag->is_ccstr() && flag->is_unlocked()) { + const char* value = nullptr; + bool redacted = false; + for (int i = 0; i < _redacted_arguments->length(); i++) { + if (match_flag(flag->name(), _redacted_arguments->at(i)->text())) { + value = REDACTED; + redacted = true; + break; + } + } + if (strcmp("FlightRecorderOptions", flag->name()) == 0) { + if (_redacted_flight_recorder_options != nullptr) { + value = _redacted_flight_recorder_options->text(); + redacted = true; + } + } + if (log && redacted) { + log_debug(jfr, redact)("Redacted string flag '%s'", flag->name()); + } + _string_flags->append(new StringFlag(flag, value)); + } + ++flag; + } + } + for (int i = 0; i < _string_flags->length(); i++) { + StringFlag* sf = _string_flags->at(i); + const JVMFlag* flag = sf->jvm_flag(); + EventStringFlag event; + event.set_name(flag->name()); + if (sf->redacted_value() != nullptr) { + // If a flag is redacted and later changed at runtime, + // the event will still show the redacted text. + event.set_value(sf->redacted_value()); + } else { + // If a flag is not redacted the first time, + // it will not be redacted later if it's changed at runtime. + event.set_value(flag->get_ccstr()); + } + event.set_origin(static_cast(flag->get_origin())); + event.commit(); + } +} + +void JfrRedactedEvents::log_jvm_information_redaction(const char* type, const String* redacted) { + const char* text = String::c_str(redacted); + if (text != nullptr && strstr(text, REDACTED) != nullptr) { + log_debug(jfr, redact)("Redacted %s '%s'", type, text); + } +} + +void JfrRedactedEvents::emit_jvm_information(bool log) { + ensure_initialized(); + EventJVMInformation event; + event.set_jvmName(VM_Version::vm_name()); + event.set_jvmVersion(VM_Version::internal_vm_info_string()); + event.set_javaArguments(String::c_str(_redacted_java_command_line)); + event.set_jvmArguments(String::c_str(_redacted_jvm_command_line)); + event.set_jvmFlags(String::c_str(_redacted_flags_command_line)); + event.set_jvmStartTime(Management::vm_init_done_time()); + event.set_pid(os::current_process_id()); + event.commit(); + if (log) { + log_jvm_information_redaction("Java Arguments", _redacted_java_command_line); + log_jvm_information_redaction("JVM Arguments", _redacted_jvm_command_line); + log_jvm_information_redaction("JVM Flags", _redacted_flags_command_line); + } +} + +void JfrRedactedEvents::ensure_initialized() { + if (_initialized) { + return; + } + if (_key_filters == nullptr) { + _key_filters = new StringArray(); + add_default_filters(_key_filters, false); + } + if (_argument_filters == nullptr) { + _argument_filters = new StringArray(); + add_default_filters(_argument_filters, true); + } + if (FlightRecorderOptions != nullptr) { + if (strstr(FlightRecorderOptions, REDACT_ARGUMENT_EQUAL) != nullptr) { + DCmdIter iterator(FlightRecorderOptions, ','); + stringStream result; + size_t pos = 0; + while(iterator.has_next()) { + CmdLine line = iterator.next(); + const char* start = line.cmd_addr(); + if (strncmp(start, REDACT_ARGUMENT_EQUAL, REDACT_ARGUMENT_EQUAL_LENGTH) == 0) { + result.print(REDACT_ARGUMENT_EQUAL); + result.print(REDACTED); + // Preserve ',' if there are more tokens + pos = iterator.has_next() ? iterator.cursor() - 1 : iterator.cursor(); + } + while (pos < iterator.cursor()) { + result.write(FlightRecorderOptions + pos, 1); + pos++; + } + } + _redacted_flight_recorder_options = new String(result.base()); + } else { + _redacted_flight_recorder_options = new String(FlightRecorderOptions); + } + } + + _redacted_arguments = new StringArray(); + + StringArray* java_args = make_java_args_array(); + _redacted_java_command_line = redact_command_line(java_args); + delete java_args; + + StringArray* jvm_args = make_jvm_args_array(Arguments::jvm_args_array(), Arguments::num_jvm_args()); + _redacted_jvm_command_line = redact_command_line(jvm_args); + delete jvm_args; + + StringArray* flags_args = make_jvm_args_array(Arguments::jvm_flags_array(), Arguments::num_jvm_flags()); + _redacted_flags_command_line = redact_command_line(flags_args); + delete flags_args; + + _initialized = true; +} + +String* JfrRedactedEvents::redact_command_line(StringArray* arguments) { + if (arguments == nullptr) { + return nullptr; + } + GrowableArray* filters = make_array(_argument_filters->length()); + for (int i = 0 ; i < _argument_filters->length(); i++) { + filters->append(make_filter_array(_argument_filters->at(i)->text())); + } + StringArray* result = new StringArray(); + int arg_index = 0; + while (arg_index < arguments->length()) { + int next_index = arg_index; + for (int i = 0; i < filters->length(); i++) { + next_index = match_arguments(filters->at(i), arguments, arg_index); + if (next_index > arg_index) { + break; + } + } + if (next_index > arg_index) { + for (int j = arg_index; j < next_index; j++) { + result->add(REDACTED); + const char* arg = arguments->at(j)->text(); + if (arg != nullptr && strncmp(arg, "-XX:", 4) == 0) { + _redacted_arguments->add(arg + 4); + } + } + arg_index = next_index; + } else { + result->add_contents(arguments->at(arg_index)); + arg_index++; + } + } + destroy_array(filters); + String* ret = concatenate(result); + delete result; + return ret; +} + +String* JfrRedactedEvents::concatenate(StringArray* strings) { + size_t length = 0; + for (int i = 0; i < strings->length(); i++) { + length += strings->at(i)->length(); + if (i != 0) { + length += 1; + } + } + String* text = new String(length); + size_t index = 0; + for (int i = 0; i < strings->length(); i++) { + if (i != 0) { + text->set(index++, ' '); + } + const String* s = strings->at(i); + size_t len = s->length(); + for (size_t j = 0; j < len; j++) { + text->set(index++, s->at(j)); + } + } + return text; +} + +bool JfrRedactedEvents::equals_case_insensitive(char a, char b) { + return tolower((unsigned char)a) == tolower((unsigned char)b); +} + +bool JfrRedactedEvents::is_redacted_key(const char* key) { + return match_key(_key_filters, key); +} + +bool JfrRedactedEvents::is_separator(char c) { + return c == ' ' || c == '\t' || c == '\n' || c == '\r'; +} + +bool JfrRedactedEvents::is_whitespace(char c) { + return c == ' '; +} + +StringArray* JfrRedactedEvents::make_filter_array(const char* filter) { + StringArray* result = new StringArray(); + + while (*filter) { + const char* q = strstr(filter, DELIMITER); + if (q == nullptr) { + size_t len = strlen(filter); + if (len > 0) { + result->add(filter, len); + } + break; + } + size_t len = (size_t)(q - filter); + if (len > 0) { + result->add(filter, len); + } + filter = q + DELIMITER_LENGTH; + } + + return result; +} + +StringArray* JfrRedactedEvents::make_jvm_args_array(char** jvm_args_array, int array_length) { + if (jvm_args_array == nullptr) { + return nullptr; + } + StringArray* result = new StringArray(array_length); + for(int i = 0; i < array_length; i++) { + char* argument = jvm_args_array[i]; + if (_redacted_flight_recorder_options != nullptr && + strncmp(argument, "-XX:FlightRecorderOptions", 25) == 0) { + const char* text = _redacted_flight_recorder_options->text(); + size_t length = _redacted_flight_recorder_options->length(); + // Length must be at least 26 or the JVM will not start. + result->add(new String(argument, 26, text, length)); + continue; + } + if (strncmp(argument, "-D", 2) == 0) { + const char* key_start = argument + 2; + const char* eq = strchr(key_start, '='); + if (eq != nullptr) { + size_t key_length = (size_t)(eq - key_start); + String* key_tmp = new String(key_start, key_length); + bool redact = match_key(_key_filters, key_tmp->text()); + delete key_tmp; + if (redact) { + size_t unsensitive_length = (size_t)(eq - argument) + 1; + result->add(new String(argument, unsensitive_length, REDACTED, REDACTED_LENGTH)); + continue; + } + } + } + result->add(argument); + } + return result; +} + +StringArray* JfrRedactedEvents::make_java_args_array() { + StringArray* array = new StringArray(); + const char* p = Arguments::java_command(); + if (p == nullptr) { + return array; + } + const char* end = p + strlen(p); + while (p < end) { + while (p < end && is_whitespace(*p)) { // Launcher separates arguments with ' '. + p++; + } + if (p >= end) { + break; + } + const char* start = p; + while (p < end && !is_whitespace(*p)) { // Launcher separates arguments with ' '. + p++; + } + size_t length = (size_t)(p - start); + if (length > 0) { + array->add(start, length); + } + } + return array; +} + +bool JfrRedactedEvents::match(const char* filter, const char* text) { + const char* filter_last = nullptr; + const char* text_last = nullptr; + while (*text != '\0') { + if (*filter == '*') { + filter_last = filter; + text_last = text; + filter++; + } else if (*filter == '?' || equals_case_insensitive(*filter, *text)) { + filter++; + text++; + } else if (filter_last != nullptr) { + text_last++; + text = text_last; + filter = filter_last + 1; + } else { + return false; + } + } + while (*filter == '*') { + filter++; + } + return *filter == '\0'; +} + +int JfrRedactedEvents::match_arguments(StringArray* filter, StringArray* arguments, int arg_index) { + int index = arg_index; + if (arg_index + filter->length() <= arguments->length()) { + for (int i = 0; i < filter->length(); i++) { + if (!match(filter->at(i)->text(), arguments->at(index)->text())) { + return arg_index; + } + index++; + } + } + return index; +} + +bool JfrRedactedEvents::match_key(StringArray* filters, const char* text) { + for (int i = 0; i < filters->length(); i++) { + const char* filter = filters->at(i)->text(); + if (match(filter, text)) { + return true; + } + } + return false; +} + +bool JfrRedactedEvents::read_file(StringArray* target, const char* filename) { + FILE* file = os::fopen(filename, "r"); + if (file == nullptr) { + log_error(jfr, redact)("Failed to open redaction file: %s", filename); + return false; + } + + stringStream ss; + while (true) { + const int c = fgetc(file); + if (c == EOF && ferror(file) != 0) { + log_error(jfr, redact)("Error reading redaction file: %s", filename); + fclose(file); + return false; + } + if (c == EOF || c == '\n') { + const char* line = ss.base(); + size_t end = ss.size(); + while (end > 0 && is_separator(line[end - 1])) { + end--; + } + if (end > 0) { + target->add(line, end); + } + ss.reset(); + if (c == EOF) { + break; + } + } else { + ss.put((char)c); + } + } + fclose(file); + log_debug(jfr, redact)("Redaction file %s read successfully", filename); + return true; +} + +StringArray* JfrRedactedEvents::split(const char* text, char separator) { + const char* last = text; + StringArray* result = new StringArray(); + for (const char* position = text; *position != '\0'; position++) { + if (*position == separator) { + if (position > last) { + result->add(last, (size_t)(position - last)); + } + last = position + 1; + } + } + if (*last != '\0') { + result->add(last, strlen(last)); + } + return result; +} diff --git a/src/hotspot/share/jfr/periodic/jfrRedactedEvents.hpp b/src/hotspot/share/jfr/periodic/jfrRedactedEvents.hpp new file mode 100644 index 00000000000..38c93310365 --- /dev/null +++ b/src/hotspot/share/jfr/periodic/jfrRedactedEvents.hpp @@ -0,0 +1,223 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef SHARE_JFR_PERIODIC_JFRREDACTEDEVENTS_HPP +#define SHARE_JFR_PERIODIC_JFRREDACTEDEVENTS_HPP + +#include "memory/allocation.hpp" +#include "memory/allStatic.hpp" +#include "utilities/globalDefinitions.hpp" +#include "utilities/growableArray.hpp" + +#include + +class JVMFlag; + +class JfrRedactedEvents: public AllStatic { + public: + class StringArray; + // Called at startup + static void log_redaction(); + static char* new_redacted_text(); + static bool set_argument_filter(const char* filters); + static bool set_key_filter(const char* filters); + static StringArray* argument_filters(); + static StringArray* key_filters(); + + // Synchronized in Java + static bool emit_initial_environment_variables(bool log = false); + static void emit_initial_system_properties(bool log = false); + static void emit_jvm_information(bool log = false); + static void emit_string_flags(bool log = false); + + template static GrowableArray* make_array(int size) { + return new (mtTracing) GrowableArray(size, mtTracing); + } + + template static void destroy_array(GrowableArray* array) { + if (array == nullptr) { + return; + } + for (int i = 0; i < array->length(); ++i) { + delete array->at(i); + } + delete array; + } + + class String: public CHeapObj { + public: + String(size_t length) : _length(length) { + _text = NEW_C_HEAP_ARRAY(char, length + 1, mtTracing); + memset(_text, 0, length + 1); + } + String(const char* source, size_t length) : String(length) { + memcpy(_text, source, length); + } + String(const char* source) : String(source, strlen(source)) { + } + String(const char* prefix, size_t prefix_length, const char* postfix, size_t postfix_length) + : String(prefix_length + postfix_length) { + memcpy(_text, prefix, prefix_length); + memcpy(_text + prefix_length, postfix, postfix_length); + } + ~String() { + FREE_C_HEAP_ARRAY(_text); + } + const char* text() const { + return _text; + } + char at(size_t index) const { + assert(index < _length, "out of bounds"); + return _text[index]; + } + void set(size_t index, char c) { + assert(index < _length, "out of bounds"); + _text[index] = c; + } + size_t length() const { + return _length; + } + static String* from_cstr(const char* s) { + return s == nullptr ? nullptr : new String(s); + } + static const char* c_str(const String* s) { + return s == nullptr ? nullptr : s->text(); + } + private: + const size_t _length; + char* _text; + }; + + class StringArray: public CHeapObj { + public: + StringArray() : _array(make_array(0)) { + } + StringArray(int capacity) : _array(make_array(capacity)) { + } + StringArray(const char* const array[], int count) : _array(make_array(count)) { + for (int i = 0; i < count; ++i) { + _array->append(new String(array[i])); + } + } + ~StringArray() { + destroy_array(_array); + } + int length() const { + return _array->length(); + } + const String* at(int i) const { + return _array->at(i); + } + void add(String* s) { + _array->append(s); + } + void add_contents(const String* s) { + add(s->text()); + } + void add(const char* s) { + _array->append(new String(s)); + } + void add(const char* s, size_t length) { + _array->append(new String(s, length)); + } + private: + GrowableArray* const _array; + }; + + class StringKeyValue: public CHeapObj { + public: + StringKeyValue(String* key, const char* value): _key(key), _value(String::from_cstr(value)) { + } + StringKeyValue(const char* key, const char* value) : StringKeyValue(new String(key), value) { + } + ~StringKeyValue() { + delete _key; + delete _value; + } + const char* key() const { + return _key->text(); + } + const char* value() const { + return String::c_str(_value); + } + private: + String* const _key; + String* const _value; + }; + + class StringFlag: public CHeapObj { + public: + StringFlag(const JVMFlag* flag, const char* redacted_value) + : _flag(flag), + _redacted_value(String::from_cstr(redacted_value)) { + } + ~StringFlag() { + delete _redacted_value; + } + const JVMFlag* jvm_flag() const { + return _flag; + } + const char* redacted_value() const { + return _redacted_value == nullptr ? nullptr : _redacted_value->text(); + } + private: + const JVMFlag* const _flag; + String* _redacted_value; + }; + + private: + static bool _initialized; + static StringArray* _argument_filters; + static StringArray* _key_filters; + static StringArray* _redacted_arguments; + static String* _redacted_java_command_line; + static String* _redacted_jvm_command_line; + static String* _redacted_flags_command_line; + static String* _redacted_flight_recorder_options; + static GrowableArray* _initial_system_properties; + static GrowableArray* _initial_environment_variables; + static GrowableArray* _string_flags; + + static bool append_filters(StringArray* target, bool argument, const char* filters); + static void add_default_filters(StringArray* target, bool argument); + static String* concatenate(StringArray* strings); + static bool equals_case_insensitive(char a, char b); + static bool is_redacted_key(const char* key); + static bool is_separator(char c); + static bool is_whitespace(char c); + static void ensure_initialized(); + static StringArray* make_java_args_array(); + static StringArray* make_jvm_args_array(char** jvm_args_array, int array_length); + static StringArray* make_filter_array(const char* filter); + static void log_jvm_information_redaction(const char* type, const String* redacted); + static bool match(const char* pattern, const char* text); + static bool match_flag(const char* flag_name, const char* argument); + static int match_arguments(StringArray* filter_array, StringArray* arguments, int arg_index); + static bool match_key(StringArray* array, const char* text); + static bool read_file(StringArray* target, const char* filename); + static String* redact_command_line(StringArray* arguments); + static StringArray* split(const char* text, char separator); +}; + +#endif // SHARE_JFR_PERIODIC_JFRREDACTEDEVENTS_HPP diff --git a/src/hotspot/share/jfr/recorder/jfrRecorder.cpp b/src/hotspot/share/jfr/recorder/jfrRecorder.cpp index 5c12d5213ca..bb17b1d0eec 100644 --- a/src/hotspot/share/jfr/recorder/jfrRecorder.cpp +++ b/src/hotspot/share/jfr/recorder/jfrRecorder.cpp @@ -29,6 +29,7 @@ #include "jfr/jni/jfrJavaSupport.hpp" #include "jfr/leakprofiler/sampling/objectSampler.hpp" #include "jfr/periodic/jfrOSInterface.hpp" +#include "jfr/periodic/jfrRedactedEvents.hpp" #include "jfr/periodic/sampling/jfrCPUTimeThreadSampler.hpp" #include "jfr/periodic/sampling/jfrThreadSampler.hpp" #include "jfr/recorder/checkpoint/jfrCheckpointManager.hpp" @@ -243,6 +244,9 @@ bool JfrRecorder::on_create_vm_2() { } bool JfrRecorder::on_create_vm_3() { + if (log_is_enabled(Debug, jfr, redact)) { + JfrRedactedEvents::log_redaction(); + } JVMTI_ONLY( assert(JvmtiEnvBase::get_phase() == JVMTI_PHASE_LIVE, "invalid init sequence, phase is %d", (int)JvmtiEnvBase::get_phase()); ) return CDSConfig::is_dumping_archive() || launch_command_line_recordings(JavaThread::current()); } diff --git a/src/hotspot/share/jfr/recorder/service/jfrOptionSet.cpp b/src/hotspot/share/jfr/recorder/service/jfrOptionSet.cpp index ec8ab36d75a..bdd42ba881e 100644 --- a/src/hotspot/share/jfr/recorder/service/jfrOptionSet.cpp +++ b/src/hotspot/share/jfr/recorder/service/jfrOptionSet.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2026, Oracle and/or its affiliates. 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 @@ -25,6 +25,7 @@ #include "cds/cdsConfig.hpp" #include "classfile/javaClasses.hpp" #include "jfr/dcmd/jfrDcmds.hpp" +#include "jfr/periodic/jfrRedactedEvents.hpp" #include "jfr/recorder/service/jfrMemorySizer.hpp" #include "jfr/recorder/service/jfrOptionSet.hpp" #include "jfr/utilities/jfrAllocation.hpp" @@ -262,6 +263,20 @@ static DCmdArgument _dcmd_preserve_repository( false, default_preserve_repository); +static DCmdArgument _dcmd_redact_argument( + "redact-argument", + "Redact command line arguments", + "STRING", + false, + nullptr); + +static DCmdArgument _dcmd_redact_key( + "redact-key", + "Redact environment variables and system properties", + "STRING", + false, + nullptr); + static DCmdParser _parser; static void register_parser_options() { @@ -277,6 +292,8 @@ static void register_parser_options() { _parser.add_dcmd_option(&_dcmd_retransform); _parser.add_dcmd_option(&_dcmd_old_object_queue_size); _parser.add_dcmd_option(&_dcmd_preserve_repository); + _parser.add_dcmd_option(&_dcmd_redact_argument); + _parser.add_dcmd_option(&_dcmd_redact_key); DEBUG_ONLY(_parser.add_dcmd_option(&_dcmd_sample_protection);) } @@ -285,6 +302,11 @@ static bool parse_flight_recorder_options_internal(TRAPS) { return true; } const size_t length = strlen((const char*)FlightRecorderOptions); + if (strcmp(FlightRecorderOptions, "help") == 0) { + JfrConfigureFlightRecorderDCmd::print_help(tty, true); + vm_exit(0); + } + CmdLine cmdline((const char*)FlightRecorderOptions, length, true); _parser.parse(&cmdline, ',', THREAD); if (HAS_PENDING_EXCEPTION) { @@ -332,6 +354,20 @@ bool JfrOptionSet::initialize(JavaThread* thread) { set_retransform(_dcmd_retransform.value()); } set_old_object_queue_size(_dcmd_old_object_queue_size.value()); + if (_dcmd_redact_argument.is_set()) { + if (!JfrRedactedEvents::set_argument_filter(_dcmd_redact_argument.value())) { + return false; + } + _dcmd_redact_argument.destroy_value(); + _dcmd_redact_argument.set_value(JfrRedactedEvents::new_redacted_text()); + } + if (_dcmd_redact_key.is_set()) { + if (!JfrRedactedEvents::set_key_filter(_dcmd_redact_key.value())) { + return false; + } + _dcmd_redact_key.destroy_value(); + _dcmd_redact_key.set_value(JfrRedactedEvents::new_redacted_text()); + } return adjust_memory_options(); } diff --git a/src/hotspot/share/logging/logTag.hpp b/src/hotspot/share/logging/logTag.hpp index 848ff78678a..15f45d04af3 100644 --- a/src/hotspot/share/logging/logTag.hpp +++ b/src/hotspot/share/logging/logTag.hpp @@ -169,6 +169,7 @@ class outputStream; LOG_TAG(ptrqueue) \ LOG_TAG(purge) \ LOG_TAG(record) \ + LOG_TAG(redact) \ LOG_TAG(redefine) \ LOG_TAG(ref) \ LOG_TAG(refine) \ diff --git a/src/hotspot/share/services/diagnosticFramework.hpp b/src/hotspot/share/services/diagnosticFramework.hpp index 5f9253b492e..5fd0182fd77 100644 --- a/src/hotspot/share/services/diagnosticFramework.hpp +++ b/src/hotspot/share/services/diagnosticFramework.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2026, Oracle and/or its affiliates. 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 @@ -85,6 +85,9 @@ public: // instance to the caller. return line; } + size_t cursor() const { + return _cursor; + } }; // Iterator class to iterate over diagnostic command arguments diff --git a/src/java.base/share/man/java.md b/src/java.base/share/man/java.md index 290f729c0ca..70824046824 100644 --- a/src/java.base/share/man/java.md +++ b/src/java.base/share/man/java.md @@ -1148,8 +1148,10 @@ These `java` options control the runtime behavior of the Java HotSpot VM. option is disabled. [`-XX:FlightRecorderOptions=`]{#-XX_FlightRecorderOptions}*parameter*`=`*value* (or) `-XX:FlightRecorderOptions:`*parameter*`=`*value* -: Sets the parameters that control the behavior of JFR. Multiple parameters can be specified - by separating them with a comma. +: Sets the parameters that control the behavior of JFR. + `-XX:FlightRecorderOptions:help` prints the available options, default + redaction filters, and example command lines. Multiple parameters can be + specified by separating them with a comma. The following list contains the available JFR *parameter*`=`*value* entries: @@ -1196,6 +1198,43 @@ These `java` options control the runtime behavior of the Java HotSpot VM. false, instrumentation is added when event classes are loaded. By default, this parameter is enabled. + `redact-argument=`argument-filter + : Replace command-line arguments that match a semicolon-separated list + of glob patterns, for example, `*secret*;password*`. Matching is + case-insensitive, and the supported wildcards are `*` and `?`. To redact + multiple arguments, use a literal space (`' '`) as a separator. + For example, to match the two arguments `--auth username:token`, use the + filter `--auth *:*`. Filters containing spaces must be quoted as a single + command-line argument, for example, + `-XX:FlightRecorderOptions='redact-argument=--auth *:*'`. + Arguments containing spaces might not be matched as expected. To load + patterns from a file (one per line) use `@`. To add to the + default patterns instead of replacing them, prefix the whole list with + `+`, for example, `+*foo*;@redact.txt`. Use `none` (lowercase) to disable + all redaction filters for command-line arguments. Redacted arguments will + be replaced with `[REDACTED]`. The option `redact-argument` is best-effort + and applies only to command-line arguments in the `jdk.JVMInformation` + event and to the `java.command` system property in the + `jdk.InitialSystemProperty` event. Other events, such as `jdk.ProcessStart` + (child processes), are not redacted. Use `-XX:FlightRecorderOptions:help` + to see the default filters used by the `redact-argument` option. + + `redact-key=`key-filter + : Replace the value of environment variables and system properties + whose key matches a semicolon-separated list of glob patterns, + for example, `*password*;*token*`. Matching is case-insensitive, and + the supported wildcards are `*` and `?`. To load patterns from a file + (one per line), use `@`. To add to the default patterns + instead of replacing them, prefix the whole list with `+`, + for example, `+*cred*;@keys.txt`. Use `none` (lowercase) to + disable all redaction filters for key matching. Redacted values + will be replaced with `[REDACTED]`. The option `redact-key` is + best-effort and applies only to the `jdk.InitialSystemProperty`, + `jdk.InitialEnvironmentVariable` and `jdk.JVMInformation` (-Dkey=...) + events. Other events, such as `jdk.InitialSecurityProperty`, are not + redacted. Use `-XX:FlightRecorderOptions:help` to see the default filters + used by the `redact-key` option. + `stackdepth=`*depth* : Stack depth for stack traces. By default, the depth is set to 64 method calls. The maximum is 2048. Values greater than 64 could create diff --git a/test/jdk/jdk/jfr/startupargs/Application.java b/test/jdk/jdk/jfr/startupargs/Application.java new file mode 100644 index 00000000000..74235b04f7e --- /dev/null +++ b/test/jdk/jdk/jfr/startupargs/Application.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.jfr.startupargs; + +// Purpose of this class is to allow tests capture environment variables, +// system properties and command line arguments. Using 'java -version' will +// not work as Java command line arguments are not captured. +public class Application { + public static void main(String... args) { + } +} diff --git a/test/jdk/jdk/jfr/startupargs/TestOptionsHelp.java b/test/jdk/jdk/jfr/startupargs/TestOptionsHelp.java new file mode 100644 index 00000000000..16f7ce8500c --- /dev/null +++ b/test/jdk/jdk/jfr/startupargs/TestOptionsHelp.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.jfr.startupargs; + +import jdk.test.lib.Asserts; +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; + +/** + * @test + * @requires vm.flagless + * @requires vm.hasJFR + * @library /test/lib /test/jdk + * @run main jdk.jfr.startupargs.TestOptionsHelp + */ +public class TestOptionsHelp { + + public static void main(String... args) throws Exception { + ProcessBuilder pb = ProcessTools.createTestJavaProcessBuilder("-XX:FlightRecorderOptions:help"); + OutputAnalyzer out = ProcessTools.executeProcess(pb); + out.shouldContain("Syntax : -XX:FlightRecorderOptions:[options]"); + out.shouldContain("numglobalbuffers"); + out.shouldContain("Multiple options are separated"); + out.shouldHaveExitValue(0); + } +} diff --git a/test/jdk/jdk/jfr/startupargs/TestRedact.java b/test/jdk/jdk/jfr/startupargs/TestRedact.java new file mode 100644 index 00000000000..2d96408a3f5 --- /dev/null +++ b/test/jdk/jdk/jfr/startupargs/TestRedact.java @@ -0,0 +1,456 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.jfr.startupargs; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.time.Duration; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; + +import jdk.jfr.FlightRecorder; +import jdk.jfr.Recording; +import jdk.jfr.RecordingState; +import jdk.jfr.consumer.EventStream; +import jdk.jfr.consumer.RecordingFile; +import jdk.test.lib.Asserts; +import jdk.test.lib.jfr.CommonHelper; +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; + +/** + * @test + * @summary Test that redaction of sensitive data works + * @requires vm.flagless + * @requires vm.hasJFR + * @library /test/lib /test/jdk + * @build jdk.jfr.startupargs.Application + * @run main/timeout=900 jdk.jfr.startupargs.TestRedact + */ +public class TestRedact { + private static Path TEST_DIRECTORY = Path.of(System.getProperty("test.src", ".")).toAbsolutePath(); + private static Path FILE_REDACTED_ARGUMENTS = TEST_DIRECTORY.resolve("redacted-arguments.txt"); + private static Path FILE_REDACTED_KEYS = TEST_DIRECTORY.resolve("redacted-keys.txt"); + + private record Execution( + Path file, + String jvmArgs, + String jvmFlags, + String javaArgs, + Map environment, + Map systemProperties, + Map stringFlags, + OutputAnalyzer output + ) { + + void assertUnredacted(String text) throws Exception { + if (jvmArgs != null && jvmArgs.contains(text)) { + return; + } + if (javaArgs != null && javaArgs.contains(text)) { + return; + } + if (systemProperties.containsValue(text) || environment.containsValue(text)) { + return; + } + printSystemProperties(); + printEnvironment(); + printCommandLine(); + throw new Exception("Could not find text '" + text + "'. Likely it's been incorrectly redacted."); + } + + private void printCommandLine() { + System.out.println("JVM Args: " + jvmArgs); + System.out.println("JVM Flags: " + jvmFlags); + System.out.println("Java Args: " + javaArgs); + System.out.println(); + } + + void assertRedactedKey(String key) throws Exception { + String v = systemProperties.get(key); + if (v != null) { + printSystemProperties(); + if (!v.equals("[REDACTED]")) { + throw new Exception("Expected system property value of key '" + key + "' to be [REDACTED]"); + } + return; + } + v = environment.get(key); + if (v == null) { + printEnvironment(); + printSystemProperties(); + throw new Exception("Expected key '" + key + "' in environment variable or system property"); + } + if (!v.equals("[REDACTED]")) { + printEnvironment(); + throw new Exception("Expected environment value of key '" + key + "' to be [REDACTED]"); + } + } + + void printEnvironment() { + printProperties("Environment Variables", environment); + } + + void printStringFlags() { + printProperties("String Flags", stringFlags); + } + + void printSystemProperties() { + printProperties("System Properties", systemProperties); + } + + private void printProperties(String title, Map map) { + System.out.println(title); + System.out.println("=".repeat(title.length())); + for (var entry : map.entrySet()) { + System.out.println(entry.getKey() + " = " + entry.getValue()); + } + System.out.println(); + } + + boolean hasCapitalizedLetter(String text) { + return text.chars().anyMatch(Character::isUpperCase); + } + + void assertRedactedArgument(String argument) throws Exception { + if (!hasCapitalizedLetter(argument)) { + throw new IllegalArgumentException("Redacted arguments in test must have at least one capitalized letter to avoid clash with random UUID in temporary filenames."); + } + checkArgument("JVM arguments", jvmArgs, argument); + checkArgument("Java arguments", javaArgs, argument); + checkArgument("Flags", jvmFlags, argument); + checkArgument("sun.java.command", systemProperties.get("sun.java.command"), argument); + } + + private void checkArgument(String kind, String arguments, String argument) throws Exception { + if (arguments != null && arguments.contains(argument)) { + System.out.println("ARGS: " + arguments); + throw new Exception("Found '" + argument + "' in " + kind + ". Should have been [REDACTED]"); + } + } + + public void print() { + printCommandLine(); + printEnvironment(); + printSystemProperties(); + printStringFlags(); + } + } + + public static void main(String... args) throws Exception { + testRedactionOfRedacted(); + testRedactKey(); + testRedactArgument(); + testRedactMultiple(); + testWildcards(); + testDefaults(); + testRedactFile(); + testAppendable(); + testEmpty(); + testBinary(); + } + + private static void testRedactionOfRedacted() throws Exception { + Execution e = run( + "-XX:FlightRecorderOptions:" + + "maxchunksize=1MB,redact-argument=Zebra," + + "old-object-queue-size=256," + + "redact-key=tiger", + "Zebra", + "Tiger" + ); + e.assertRedactedArgument("Zebra"); + e.assertRedactedArgument("redact-argument=Zebra"); + e.assertUnredacted("Tiger"); + } + + private static void testAppendable() throws Exception { + Execution e = run( + Map.of( + "Secret", "Thing", // For default filter + "Cat", "Bird" // For user-defined filter + ), + Map.of( + "Secret", "Stuff", // For default filter + "Cat", "Dog" // For user-defined filter + ), + "-XX:FlightRecorderOptions:redact-argument=+Foo;Bar,redact-key=+Cat", + "Foo", // For user-defined + "Bar", // For user-defined + "Secret"); // For default filter + e.assertRedactedArgument("Foo"); // Matched by user-defined filter + e.assertRedactedArgument("Bar"); // Matched by user-defined filter + e.assertRedactedKey("Cat"); // Matched by user-defined filter + e.assertRedactedArgument("Secret"); // Matched by default filter *secret* + e.assertRedactedKey("Secret"); // Matched by default filter *secret* + } + + private static void testWildcards() throws Exception { + Execution e = run( + "-XX:FlightRecorderOptions:redact-argument=*PPLE;ORAN*;C*HE*RY;N?2?4?;A?C*?F", + "APPLE", "ORANGE", "CHERRY", "N12345", "ABCDEF", "N4711" + ); + e.assertRedactedArgument("APPLE"); + e.assertRedactedArgument("ORANGE"); + e.assertRedactedArgument("CHERRY"); + e.assertRedactedArgument("N12345"); + e.assertRedactedArgument("ABCDEF"); + e.assertUnredacted("N4711"); + // Tests that only fully matched filters are redacted + Execution f = run( + "-XX:FlightRecorderOptions:redact-argument=-*TIGER* *", + "--TIGER" + ); + f.assertUnredacted("--TIGER"); + } + + private static void testBinary() throws Exception { + Execution unredacted = run( + Map.of("stuff1", "hammock"), + Map.of("stuff2", "haberdashery"), + "-XX:FlightRecorderOptions:stackdepth=64", + "hammock" + ); + if (!contains(unredacted.file, "hammock")) { + throw new Exception("Expected 'hammock' to be in recording file"); + } + if (!contains(unredacted.file, "haberdashery")) { + throw new Exception("Expected 'haberdashery' to be in recording file"); + } + + Execution redacted = run( + Map.of("what", "zippers"), + Map.of("where", "haberdashery"), + "-XX:FlightRecorderOptions:redact-argument=zippers,redact-key=what;where", + "zippers" + ); + if (contains(redacted.file, "zippers")) { + redacted.print(); + throw new Exception("Expected all occurrences of 'zippers' to be redacted"); + } + if (contains(redacted.file, "haberdashery")) { + redacted.print(); + throw new Exception("Expected all occurrences of 'haberdashery' to be redacted"); + } + } + + private static boolean contains(Path file, String text) throws IOException { + byte[] bytes = Files.readAllBytes(file); + byte[] chars = text.getBytes(StandardCharsets.UTF_8); + for (int i = 0; i <= bytes.length - chars.length; i++) { + if (matchesAt(bytes, chars, i)) { + System.out.print("Found '" + text + "' at position " + i); + System.out.println(" in file " + file.toAbsolutePath()); + return true; + } + } + return false; + } + + private static boolean matchesAt(byte[] bytes, byte[] chars, int position) { + for (int j = 0; j < chars.length; j++) { + if (bytes[position + j] != chars[j]) { + return false; + } + } + return true; + } + + private static void testRedactFile() throws Exception { + Execution e1 = run( + Map.of("stuff1", "Snake"), + Map.of("stuff2", "Snake"), + "-XX:FlightRecorderOptions:redact-argument=@" + FILE_REDACTED_ARGUMENTS, + "https://John:Smith@www.example.com/myresource", + "-conf-Key=chicken", + "/Foo/Dog", + "-Header", "Authorization:Bearer", "Banana", + "Apple"); + e1.assertRedactedArgument("https://John:Smith@www.example.com/myresource"); + e1.assertRedactedArgument("conf-Key=chicken"); + e1.assertRedactedArgument("/Foo/Dog"); + e1.assertRedactedArgument("-Header"); + e1.assertRedactedArgument("Authorization:Bearer"); + e1.assertRedactedArgument("Banana"); + e1.assertRedactedArgument("Apple"); + e1.assertUnredacted("Snake"); + + Execution e2 = run( + Map.of("confidential", "apple"), + Map.of("very-sensitive", "banana"), + "-XX:FlightRecorderOptions:redact-key=@" + FILE_REDACTED_KEYS, + "Snake"); + e2.assertRedactedKey("confidential"); + e2.assertRedactedKey("very-sensitive"); + e2.assertUnredacted("Snake"); + } + + private static void testEmpty() throws Exception { + var environment = Map.of("API_TOKEN", "Zebra1"); + var properties = Map.of("API_KEY", "Zebra2"); + Execution e1 = run(environment, properties, + "-XX:FlightRecorderOptions:redact-key=,redact-argument=", "Zebra3" + ); + e1.output().shouldContain("Default redaction filters are replaced."); + e1.output().shouldContain("redact-key=none to disable filters without a warning"); + e1.output().shouldContain("redact-argument=none to disable filters without a warning"); + e1.assertUnredacted("Zebra1"); + e1.assertUnredacted("Zebra2"); + e1.assertUnredacted("Zebra3"); + + Execution e2 = run(environment, properties, + "-XX:FlightRecorderOptions:redact-argument=none,redact-key=none", "Zebra3" + ); + e2.output().shouldNotContain("Default redaction filters are replaced."); + e2.output().shouldNotContain("redact-key=none to disable filters without a warning"); + e2.output().shouldNotContain("redact-argument=none to disable filters without a warning"); + e2.assertUnredacted("Zebra1"); + e2.assertUnredacted("Zebra2"); + e2.assertUnredacted("Zebra3"); + } + + private static void testDefaults() throws Exception { + Execution e = run( + Map.of("apiKey","thing"), + Map.of("apiKey", "stuff"), + "-DapiKey=stuff", + "Secret", + "-password=Foo" + ); + e.assertRedactedArgument("Secret"); + e.assertRedactedArgument("-password=Foo"); + e.assertRedactedKey("apiKey"); + } + + private static void testRedactArgument() throws Exception { + Execution e = run( + "-XX:FlightRecorderOptions:redact-argument=" + + // FILTERS + "Plain;" + + "--option *;" + + "--pass-phrase *;" + + "--login * *;" + + "--colon-based:*;" + + "https://*:*@*", + // SENSITIVE INFORMATION + "Plain", + "--option", "Option-value", + "--pass-phrase", "Cant-be-whitespace-outage", + "--login", "John", "N4711", + "--colon-based:Hello", + "https://Smith:abc123@example.com/path", + // UNSENSITIVE INFORMATION + "Banana" + ); + e.assertRedactedArgument("Plain"); + e.assertRedactedArgument("Option-value"); + e.assertRedactedArgument("Cant-be-whitespace-outage"); + e.assertRedactedArgument("Hello"); + e.assertRedactedArgument("John"); + e.assertRedactedArgument("N4711"); + e.assertRedactedArgument("Smith:abc123"); + e.assertUnredacted("Banana"); + } + + private static void testRedactMultiple() throws Exception { + Execution e = run( + Map.of("foo", "bird", "bar, ", "orange"), + Map.of("foo", "tiger", "bar", "banana"), + "-XX:FlightRecorderOptions:redact-key=foo;bar,redact-argument=Baz;Quz", "Baz", "Quz"); + e.assertRedactedKey("foo"); + e.assertRedactedKey("bar"); + e.assertRedactedArgument("Baz"); + e.assertRedactedArgument("Quz"); + } + + private static void testRedactKey() throws Exception { + Execution e = run( + Map.of("cart", "wheel", "banana", "split", "rose", "bud"), + Map.of("banana", "split", "cat", "milk", "carpet","magic", "rose", "bud"), + "-XX:FlightRecorderOptions:redact-key=banana;ca*t", + "rose" + ); + e.assertRedactedKey("banana"); + e.assertRedactedKey("cat"); + e.assertRedactedKey("carpet"); + e.assertUnredacted("rose"); + } + + private static Execution run(String options, String... args) throws Exception { + return run(Map.of(), Map.of(), options, args); + } + + private static Execution run(Map environment, Map properties, String options, String... args) throws Exception { + List arguments = new ArrayList<>(); + Path file = Path.of("file.jfr"); + for (var entry : properties.entrySet()) { + arguments.add("-D" + entry.getKey() + "=" + entry.getValue()); + } + arguments.add("-XX:StartFlightRecording:filename=" + file.toAbsolutePath().toString()); + arguments.add(options); + arguments.add("jdk.jfr.startupargs.Application"); + arguments.addAll(Arrays.asList(args)); + + ProcessBuilder pb = ProcessTools.createLimitedTestJavaProcessBuilder(arguments); + pb.environment().put("env.secret", "confidential"); + // An environment variable redacted by default filters + pb.environment().putAll(environment); + + OutputAnalyzer output = new OutputAnalyzer(pb.start()); + System.out.println(output.getOutput()); + output.shouldHaveExitValue(0); + var environmentVariables = new HashMap(); + var systemProperties = new HashMap(); + var stringFlags = new HashMap(); + var jvmArgs = new AtomicReference(); + var jvmFlags= new AtomicReference(); + var javaArgs = new AtomicReference(); + try (var es = EventStream.openFile(file)) { + es.onEvent("jdk.InitialSystemProperty", e -> { + systemProperties.put(e.getString("key"), e.getString("value")); + }); + es.onEvent("jdk.InitialEnvironmentVariable", e -> { + environmentVariables.put(e.getString("key"), e.getString("value")); + }); + es.onEvent("jdk.JVMInformation", e -> { + jvmArgs.set(e.getString("jvmArguments")); + javaArgs.set(e.getString("javaArguments")); + jvmFlags.set(e.getString("jvmFlags")); + }); + es.onEvent("jdk.StringFlag", e -> { + stringFlags.put(e.getString("name"), e.getString("value")); + }); + es.start(); + } + return new Execution(file, + jvmArgs.get(), jvmFlags.get(), javaArgs.get(), + environmentVariables, systemProperties, stringFlags, output); + } +} diff --git a/test/jdk/jdk/jfr/startupargs/redacted-arguments.txt b/test/jdk/jdk/jfr/startupargs/redacted-arguments.txt new file mode 100644 index 00000000000..8f7ffb22569 --- /dev/null +++ b/test/jdk/jdk/jfr/startupargs/redacted-arguments.txt @@ -0,0 +1,6 @@ +https://*:*@* +apple +-conf-key=* +/foo/* +--passphrase * +-header Authorization:Bearer * diff --git a/test/jdk/jdk/jfr/startupargs/redacted-keys.txt b/test/jdk/jdk/jfr/startupargs/redacted-keys.txt new file mode 100644 index 00000000000..b63aa66cc70 --- /dev/null +++ b/test/jdk/jdk/jfr/startupargs/redacted-keys.txt @@ -0,0 +1,2 @@ +*sensitive +confidential From 2688bf7305ff323e02636bfcc37497ebd1563ab5 Mon Sep 17 00:00:00 2001 From: Vladimir Petko Date: Wed, 3 Jun 2026 06:35:57 +0000 Subject: [PATCH 03/61] 8385738: Javadoc does not produce reproducible output due to the snippet ids Reviewed-by: nbenalla --- .../formats/html/taglets/SnippetTaglet.java | 8 +- .../doclet/testSnippetTag/TestSnippetTag.java | 95 ++++++++++++++++++- 2 files changed, 98 insertions(+), 5 deletions(-) diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/SnippetTaglet.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/SnippetTaglet.java index 38baa7b2826..b666a14fd95 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/SnippetTaglet.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/SnippetTaglet.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2026, Oracle and/or its affiliates. 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 @@ -49,6 +49,7 @@ import com.sun.source.util.DocTreePath; import jdk.javadoc.doclet.Taglet; import jdk.javadoc.internal.doclets.formats.html.HtmlConfiguration; +import jdk.javadoc.internal.doclets.formats.html.HtmlDocletWriter; import jdk.javadoc.internal.doclets.formats.html.markup.HtmlStyles; import jdk.javadoc.internal.doclets.formats.html.taglets.snippet.Action; import jdk.javadoc.internal.doclets.formats.html.taglets.snippet.ParseException; @@ -124,7 +125,8 @@ public class SnippetTaglet extends BaseTaglet { if (id != null && !id.isBlank()) { pre.put(HtmlAttr.ID, id); } else { - pre.put(HtmlAttr.ID, config.htmlIds.forSnippet(element, ids).name()); + var set = ids.computeIfAbsent(tagletWriter.htmlWriter, _ -> new HashSet<>()); + pre.put(HtmlAttr.ID, config.htmlIds.forSnippet(element, set).name()); } var code = HtmlTree.CODE() .addUnchecked(Text.EMPTY); // Make sure the element is always rendered @@ -207,7 +209,7 @@ public class SnippetTaglet extends BaseTaglet { return snippetContainer.add(pre.add(code)); } - private final Set ids = new HashSet<>(); + private final HashMap> ids = new HashMap<>(); private static final class BadSnippetException extends Exception { diff --git a/test/langtools/jdk/javadoc/doclet/testSnippetTag/TestSnippetTag.java b/test/langtools/jdk/javadoc/doclet/testSnippetTag/TestSnippetTag.java index 26030629738..24a7b5aa5da 100644 --- a/test/langtools/jdk/javadoc/doclet/testSnippetTag/TestSnippetTag.java +++ b/test/langtools/jdk/javadoc/doclet/testSnippetTag/TestSnippetTag.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2026, Oracle and/or its affiliates. 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 @@ -23,7 +23,7 @@ /* * @test - * @bug 8266666 8275788 8276964 8299080 8276966 + * @bug 8266666 8275788 8276964 8299080 8276966 8385738 * @summary Implementation for snippets * @library /tools/lib ../../lib * @modules jdk.compiler/com.sun.tools.javac.api @@ -39,6 +39,7 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardOpenOption; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Optional; import java.util.concurrent.atomic.AtomicInteger; @@ -2685,4 +2686,94 @@ public class TestSnippetTag extends SnippetTester { """); checkNoCrashes(); } + + @Test + public void testSnippetIdCounterResetsPerPage(Path base) throws IOException { + Path src = base.resolve("src"); + + tb.writeJavaFiles(src, + """ + package p; + import java.lang.annotation.Target; + import java.lang.annotation.ElementType; + @Target({ElementType.TYPE, ElementType.METHOD}) + public @interface ACustomAnnotation { + } + """, + """ + package p; + import java.lang.annotation.Target; + import java.lang.annotation.ElementType; + @Target({ElementType.TYPE, ElementType.METHOD}) + public @interface ZCustomAnnotation { + } + """, + """ + package p; + /** Class A. */ + public class A { + /** + * First snippet on A.foo: + * {@snippet : + * int x = 1; // first + * } + * Second snippet on A.foo: + * {@snippet : + * int y = 2; // second + * } + */ + @ACustomAnnotation + @ZCustomAnnotation + public void foo() {} + } + """, + """ + package p; + /** Class B. */ + public class B { + /** + * First snippet on B.foo: + * {@snippet : + * int x = 1; // first + * } + * Second snippet on B.foo: + * {@snippet : + * int y = 2; // second + * } + */ + @ACustomAnnotation + @ZCustomAnnotation + public void foo() {} + } + """); + + javadoc("-d", base.resolve("out").toString(), + "-use", + "-sourcepath", src.toString(), + "p"); + checkExit(Exit.OK); + for (String cls : new String[] { + "A.html", + "B.html", + "class-use/ACustomAnnotation.html", + "class-use/ZCustomAnnotation.html"}) { + var file = base + .resolve("out") + .resolve("p") + .resolve(cls); + String content = Files.readString(file); + for (var snippetId : new String[] {"snippet-foo()1", "snippet-foo()2"}) { + checking("id \"" + snippetId + "\" present in " + cls); + if (content.contains("id=\"" + snippetId + "\"")) { + passed("found"); + } else { + failed("" + snippetId + " not found in " + + String.join("\n", Arrays.asList(content.split("\n")) + .stream() + .filter(l -> l.contains("pre class=\"snippet\"")) + .toList())); + } + } + } + } } From 1900d661bdf88ce9e8439ca35670087c2ceb9c72 Mon Sep 17 00:00:00 2001 From: Fredrik Bredberg Date: Wed, 3 Jun 2026 07:34:19 +0000 Subject: [PATCH 04/61] 8385312: Intermittent assert while running JCK test api/java_util/concurrent/Assorted/SynchronousQueue20.html Reviewed-by: coleenp, pchilanomate --- src/hotspot/share/runtime/continuation.cpp | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/hotspot/share/runtime/continuation.cpp b/src/hotspot/share/runtime/continuation.cpp index 26d865057e6..f8af2545c37 100644 --- a/src/hotspot/share/runtime/continuation.cpp +++ b/src/hotspot/share/runtime/continuation.cpp @@ -153,12 +153,6 @@ freeze_result Continuation::try_preempt(JavaThread* current, oop continuation) { return res; } -#ifndef PRODUCT -static jlong java_tid(JavaThread* thread) { - return java_lang_Thread::thread_id(thread->threadObj()); -} -#endif - ContinuationEntry* Continuation::get_continuation_entry_for_continuation(JavaThread* thread, oop continuation) { if (thread == nullptr || continuation == nullptr) { return nullptr; @@ -388,9 +382,9 @@ frame Continuation::continuation_bottom_sender(JavaThread* thread, const frame& ContinuationEntry* ce = get_continuation_entry_for_sp(thread, callee.sp()); assert(ce != nullptr, "callee.sp(): " INTPTR_FORMAT, p2i(callee.sp())); - log_develop_debug(continuations)("continuation_bottom_sender: [" JLONG_FORMAT "] [%d] callee: " INTPTR_FORMAT + log_develop_debug(continuations)("continuation_bottom_sender: [" UINT64_FORMAT "] [%d] callee: " INTPTR_FORMAT " sender_sp: " INTPTR_FORMAT, - java_tid(thread), thread->osthread()->thread_id(), p2i(callee.sp()), p2i(sender_sp)); + thread->monitor_owner_id(), thread->osthread()->thread_id(), p2i(callee.sp()), p2i(sender_sp)); frame entry = ce->to_frame(); if (callee.is_interpreted_frame()) { From ec1bffd9a51ae06479a261a6a106f44866212ae7 Mon Sep 17 00:00:00 2001 From: Aleksey Shipilev Date: Wed, 3 Jun 2026 07:36:14 +0000 Subject: [PATCH 05/61] 8385702: Improve polymorphic handling in JDK-8385648 Reviewed-by: mdoerr, sviswanathan, fyang --- .../cpu/aarch64/macroAssembler_aarch64.cpp | 31 +++++++++---------- src/hotspot/cpu/ppc/macroAssembler_ppc.cpp | 18 +++++------ .../cpu/riscv/macroAssembler_riscv.cpp | 30 +++++++++--------- src/hotspot/cpu/x86/macroAssembler_x86.cpp | 31 +++++++++---------- 4 files changed, 51 insertions(+), 59 deletions(-) diff --git a/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp b/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp index ac5bae22384..eb658ba4e30 100644 --- a/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp @@ -2147,7 +2147,7 @@ void MacroAssembler::profile_receiver_type(Register recv, Register mdp, int mdp_ Register offset = rscratch2; Label L_loop_search_receiver, L_loop_search_empty; - Label L_restart, L_found_recv, L_found_empty, L_polymorphic, L_count_update; + Label L_restart, L_found_recv, L_found_empty, L_count_update; // The code here recognizes three major cases: // A. Fastest: receiver found in the table @@ -2177,21 +2177,20 @@ void MacroAssembler::profile_receiver_type(Register recv, Register mdp, int mdp_ // if (receiver(i) == recv) goto found_recv(i); // } // - // // Fast: no receiver, but profile is full + // // Fast: no receiver, but profile is not full // for (i = 0; i < receiver_count(); i++) { // if (receiver(i) == null) goto found_null(i); // } - // goto polymorphic + // + // // Slow: profile is full, polymorphic case + // count++; + // return // // // Slow: try to install receiver // found_null(i): // CAS(&receiver(i), null, recv); // goto restart // - // polymorphic: - // count++; - // return - // // found_recv(i): // *receiver_count(i)++ // @@ -2208,7 +2207,7 @@ void MacroAssembler::profile_receiver_type(Register recv, Register mdp, int mdp_ sub(rscratch1, offset, end_receiver_offset); cbnz(rscratch1, L_loop_search_receiver); - // Fast: no receiver, but profile is full + // Fast: no receiver, but profile is not full mov(offset, base_receiver_offset); bind(L_loop_search_empty); ldr(rscratch1, Address(mdp, offset)); @@ -2216,9 +2215,13 @@ void MacroAssembler::profile_receiver_type(Register recv, Register mdp, int mdp_ add(offset, offset, receiver_step); sub(rscratch1, offset, end_receiver_offset); cbnz(rscratch1, L_loop_search_empty); - b(L_polymorphic); - // Slow: try to install receiver + // Slow: Receiver is not found and table is full. + // Increment polymorphic counter instead of receiver slot. + mov(offset, poly_count_offset); + b(L_count_update); + + // Slowest: try to install receiver bind(L_found_empty); // Atomically swing receiver slot: null -> recv. @@ -2237,17 +2240,11 @@ void MacroAssembler::profile_receiver_type(Register recv, Register mdp, int mdp_ // and just restart the search from the beginning. b(L_restart); - // Counter updates: - - // Increment polymorphic counter instead of receiver slot. - bind(L_polymorphic); - mov(offset, poly_count_offset); - b(L_count_update); - // Found a receiver, convert its slot offset to corresponding count offset. bind(L_found_recv); add(offset, offset, receiver_to_count_step); + // Finally, update the counter bind(L_count_update); increment(Address(mdp, offset), DataLayout::counter_increment); } diff --git a/src/hotspot/cpu/ppc/macroAssembler_ppc.cpp b/src/hotspot/cpu/ppc/macroAssembler_ppc.cpp index 0d6c272decb..b5bfcb0fced 100644 --- a/src/hotspot/cpu/ppc/macroAssembler_ppc.cpp +++ b/src/hotspot/cpu/ppc/macroAssembler_ppc.cpp @@ -4411,21 +4411,20 @@ void MacroAssembler::profile_receiver_type(Register recv, Register mdp, int mdp_ // if (receiver(i) == recv) goto found_recv(i); // } // - // // Fast: no receiver, but profile is full + // // Fast: no receiver, but profile is not full // for (i = 0; i < receiver_count(); i++) { // if (receiver(i) == null) goto found_null(i); // } - // goto polymorphic + // + // // Slow: profile is full, polymorphic case + // count++; + // return // // // Slow: try to install receiver // found_null(i): // CAS(&receiver(i), null, recv); // goto restart // - // polymorphic: - // count++; - // return - // // found_recv(i): // *receiver_count(i)++ // @@ -4466,11 +4465,12 @@ void MacroAssembler::profile_receiver_type(Register recv, Register mdp, int mdp_ addi(offset, offset, receiver_step); bdnz(L_loop_search_empty); - // Polymorphic: Increment polymorphic counter instead of receiver slot. + // Slow: Receiver is not found and table is full. + // Increment polymorphic counter instead of receiver slot. li(offset, poly_count_offset); b(L_count_update); - // Slow: try to install receiver + // Slowest: try to install receiver bind(L_found_empty); // Atomically swing receiver slot: null -> recv. @@ -4491,7 +4491,7 @@ void MacroAssembler::profile_receiver_type(Register recv, Register mdp, int mdp_ bind(L_found_recv); addi(offset, offset, receiver_to_count_step); - // Counter update + // Finally, update the counter bind(L_count_update); increment_mem64(mdp, offset, DataLayout::counter_increment, /* temp */ (count != noreg) ? count : recv); } diff --git a/src/hotspot/cpu/riscv/macroAssembler_riscv.cpp b/src/hotspot/cpu/riscv/macroAssembler_riscv.cpp index 02798d5204a..7b3e034906e 100644 --- a/src/hotspot/cpu/riscv/macroAssembler_riscv.cpp +++ b/src/hotspot/cpu/riscv/macroAssembler_riscv.cpp @@ -593,7 +593,7 @@ void MacroAssembler::profile_receiver_type(Register recv, Register mdp, int mdp_ Register offset = t1; Label L_loop_search_receiver, L_loop_search_empty; - Label L_restart, L_found_recv, L_found_empty, L_polymorphic, L_count_update; + Label L_restart, L_found_recv, L_found_empty, L_count_update; // The code here recognizes three major cases: // A. Fastest: receiver found in the table @@ -623,21 +623,20 @@ void MacroAssembler::profile_receiver_type(Register recv, Register mdp, int mdp_ // if (receiver(i) == recv) goto found_recv(i); // } // - // // Fast: no receiver, but profile is full + // // Fast: no receiver, but profile is not full // for (i = 0; i < receiver_count(); i++) { // if (receiver(i) == null) goto found_null(i); // } - // goto polymorphic + // + // // Slow: profile is full, polymorphic case + // count++; + // return // // // Slow: try to install receiver // found_null(i): // CAS(&receiver(i), null, recv); // goto restart // - // polymorphic: - // count++; - // return - // // found_recv(i): // *receiver_count(i)++ // @@ -654,7 +653,7 @@ void MacroAssembler::profile_receiver_type(Register recv, Register mdp, int mdp_ sub(t0, offset, end_receiver_offset); bnez(t0, L_loop_search_receiver); - // Fast: no receiver, but profile is full + // Fast: no receiver, but profile is not full mv(offset, base_receiver_offset); bind(L_loop_search_empty); add(t0, mdp, offset); @@ -663,9 +662,13 @@ void MacroAssembler::profile_receiver_type(Register recv, Register mdp, int mdp_ add(offset, offset, receiver_step); sub(t0, offset, end_receiver_offset); bnez(t0, L_loop_search_empty); - j(L_polymorphic); - // Slow: try to install receiver + // Slow: Receiver is not found and table is full. + // Increment polymorphic counter instead of receiver slot. + mv(offset, poly_count_offset); + j(L_count_update); + + // Slowest: try to install receiver bind(L_found_empty); // Atomically swing receiver slot: null -> recv. @@ -683,16 +686,11 @@ void MacroAssembler::profile_receiver_type(Register recv, Register mdp, int mdp_ // and just restart the search from the beginning. j(L_restart); - // Counter updates: - // Increment polymorphic counter instead of receiver slot. - bind(L_polymorphic); - mv(offset, poly_count_offset); - j(L_count_update); - // Found a receiver, convert its slot offset to corresponding count offset. bind(L_found_recv); add(offset, offset, receiver_to_count_step); + // Finally, update the counter bind(L_count_update); add(t1, mdp, offset); increment(Address(t1), DataLayout::counter_increment); diff --git a/src/hotspot/cpu/x86/macroAssembler_x86.cpp b/src/hotspot/cpu/x86/macroAssembler_x86.cpp index aa2195d0256..0ac0a8243d4 100644 --- a/src/hotspot/cpu/x86/macroAssembler_x86.cpp +++ b/src/hotspot/cpu/x86/macroAssembler_x86.cpp @@ -4901,7 +4901,7 @@ void MacroAssembler::profile_receiver_type(Register recv, Register mdp, int mdp_ Register offset = rscratch1; Label L_loop_search_receiver, L_loop_search_empty; - Label L_restart, L_found_recv, L_found_empty, L_polymorphic, L_count_update; + Label L_restart, L_found_recv, L_found_empty, L_count_update; // The code here recognizes three major cases: // A. Fastest: receiver found in the table @@ -4931,21 +4931,20 @@ void MacroAssembler::profile_receiver_type(Register recv, Register mdp, int mdp_ // if (receiver(i) == recv) goto found_recv(i); // } // - // // Fast: no receiver, but profile is full + // // Fast: no receiver, but profile is not full // for (i = 0; i < receiver_count(); i++) { // if (receiver(i) == null) goto found_null(i); // } - // goto polymorphic + // + // // Slow: profile is full, polymorphic case + // count++; + // return // // // Slow: try to install receiver // found_null(i): // CAS(&receiver(i), null, recv); // goto restart // - // polymorphic: - // count++; - // return - // // found_recv(i): // *receiver_count(i)++ // @@ -4961,7 +4960,7 @@ void MacroAssembler::profile_receiver_type(Register recv, Register mdp, int mdp_ cmpptr(offset, end_receiver_offset); jccb(Assembler::notEqual, L_loop_search_receiver); - // Fast: no receiver, but profile is full + // Fast: no receiver, but profile is not full movptr(offset, base_receiver_offset); bind(L_loop_search_empty); cmpptr(Address(mdp, offset, Address::times_ptr), NULL_WORD); @@ -4969,9 +4968,13 @@ void MacroAssembler::profile_receiver_type(Register recv, Register mdp, int mdp_ addptr(offset, receiver_step); cmpptr(offset, end_receiver_offset); jccb(Assembler::notEqual, L_loop_search_empty); - jmpb(L_polymorphic); - // Slow: try to install receiver + // Slow: Receiver is not found and table is full. + // Increment polymorphic counter instead of receiver slot. + movptr(offset, poly_count_offset); + jmpb(L_count_update); + + // Slowest: try to install receiver bind(L_found_empty); // Atomically swing receiver slot: null -> recv. @@ -5023,17 +5026,11 @@ void MacroAssembler::profile_receiver_type(Register recv, Register mdp, int mdp_ // and just restart the search from the beginning. jmpb(L_restart); - // Counter updates: - - // Increment polymorphic counter instead of receiver slot. - bind(L_polymorphic); - movptr(offset, poly_count_offset); - jmpb(L_count_update); - // Found a receiver, convert its slot offset to corresponding count offset. bind(L_found_recv); addptr(offset, receiver_to_count_step); + // Finally, update the counter bind(L_count_update); addptr(Address(mdp, offset, Address::times_ptr), DataLayout::counter_increment); } From 563befb8a1a19e574e7d43068b45783ba8cb7ba3 Mon Sep 17 00:00:00 2001 From: David Briemann Date: Wed, 3 Jun 2026 10:30:43 +0000 Subject: [PATCH 06/61] 8385633: PPC64: Shenandoah weak CAS fails after late barrier expansion Reviewed-by: mdoerr, shade --- .../shenandoahBarrierSetAssembler_ppc.cpp | 26 +-- .../shenandoahBarrierSetAssembler_ppc.hpp | 4 +- .../cpu/ppc/gc/shenandoah/shenandoah_ppc.ad | 149 ++++-------------- 3 files changed, 44 insertions(+), 135 deletions(-) diff --git a/src/hotspot/cpu/ppc/gc/shenandoah/shenandoahBarrierSetAssembler_ppc.cpp b/src/hotspot/cpu/ppc/gc/shenandoah/shenandoahBarrierSetAssembler_ppc.cpp index 9c079107e08..1f6ca4655de 100644 --- a/src/hotspot/cpu/ppc/gc/shenandoah/shenandoahBarrierSetAssembler_ppc.cpp +++ b/src/hotspot/cpu/ppc/gc/shenandoah/shenandoahBarrierSetAssembler_ppc.cpp @@ -1134,39 +1134,41 @@ void ShenandoahBarrierSetAssembler::store_c2(const MachNode* node, MacroAssemble ShenandoahBarrierStubC2::store_post(masm, node, Address(dst, disp), tmp1, tmp2); } -void ShenandoahBarrierSetAssembler::compare_and_set_c2(const MachNode* node, MacroAssembler* masm, Register res, Register addr, Register oldval, - Register newval, Register tmp1, Register tmp2, Register tmp3, bool exchange, bool narrow, bool weak, bool acquire) { +void ShenandoahBarrierSetAssembler::compare_and_set_c2(const MachNode* node, MacroAssembler* masm, Register res, Register addr, + Register oldval, Register newval, Register tmp1, Register tmp2, bool exchange, bool narrow, bool weak, bool acquire) { - ShenandoahBarrierStubC2::load_store_pre(masm, node, tmp1, addr, tmp2, tmp3, narrow); + ShenandoahBarrierStubC2::load_store_pre(masm, node, res, addr, tmp1, tmp2, narrow); - Register dest_current = exchange ? res : R0; - Register int_flag = exchange ? noreg : res; - int semantics = MacroAssembler::MemBarNone; + Register dest_current = exchange ? res : R0; + Label no_update; + int semantics = MacroAssembler::MemBarNone; if (acquire) { semantics = support_IRIW_for_not_multiple_copy_atomic_cpu ? MacroAssembler::MemBarAcq : MacroAssembler::MemBarFenceAfter; } + if (!exchange) { __ li(res, 0); } if (narrow) { - // CmpxchgX sets CR0 to cmpX(src1, src2) and Rres to 'true'/'false'. __ cmpxchgw(CR0, dest_current, oldval, newval, addr, semantics, MacroAssembler::cmpxchgx_hint_atomic_update(), - int_flag, nullptr, true, weak); + noreg, &no_update, true, weak); } else { - // CmpxchgX sets CR0 to cmpX(src1, src2) and Rres to 'true'/'false'. __ cmpxchgd(CR0, dest_current, oldval, newval, addr, semantics, MacroAssembler::cmpxchgx_hint_atomic_update(), - int_flag, nullptr, true, weak); + noreg, &no_update, true, weak); } + if (!exchange) { __ li(res, 1); } ShenandoahBarrierStubC2::load_store_post(masm, node, Address(addr, 0), tmp1, tmp2); + + __ bind(no_update); } -void ShenandoahBarrierSetAssembler::get_and_set_c2(const MachNode* node, MacroAssembler* masm, Register preval, Register newval, Register addr, Register tmp1, Register tmp2, Register tmp3) { +void ShenandoahBarrierSetAssembler::get_and_set_c2(const MachNode* node, MacroAssembler* masm, Register preval, Register newval, Register addr, Register tmp1, Register tmp2) { bool is_narrow = node->bottom_type()->isa_narrowoop(); - ShenandoahBarrierStubC2::load_store_pre(masm, node, tmp1, addr, tmp2, tmp3, is_narrow); + ShenandoahBarrierStubC2::load_store_pre(masm, node, preval, addr, tmp1, tmp2, is_narrow); if (is_narrow) { __ getandsetw(preval, newval, addr, MacroAssembler::cmpxchgx_hint_atomic_update()); diff --git a/src/hotspot/cpu/ppc/gc/shenandoah/shenandoahBarrierSetAssembler_ppc.hpp b/src/hotspot/cpu/ppc/gc/shenandoah/shenandoahBarrierSetAssembler_ppc.hpp index eda3aafdb2d..5c70a308691 100644 --- a/src/hotspot/cpu/ppc/gc/shenandoah/shenandoahBarrierSetAssembler_ppc.hpp +++ b/src/hotspot/cpu/ppc/gc/shenandoah/shenandoahBarrierSetAssembler_ppc.hpp @@ -140,10 +140,10 @@ public: Register dst, int disp, bool dst_narrow, Register src, bool src_narrow, Register tmp1, Register tmp2, Register tmp3); void compare_and_set_c2(const MachNode* node, MacroAssembler* masm, Register res, Register addr, Register oldval, - Register newval, Register tmp1, Register tmp2, Register tmp3, bool exchange, bool narrow, bool weak, bool acquire); + Register newval, Register tmp1, Register tmp2, bool exchange, bool narrow, bool weak, bool acquire); void get_and_set_c2(const MachNode* node, MacroAssembler* masm, - Register preval, Register newval, Register addr, Register tmp1, Register tmp2, Register tmp3); + Register preval, Register newval, Register addr, Register tmp1, Register tmp2); #endif // COMPILER2 }; diff --git a/src/hotspot/cpu/ppc/gc/shenandoah/shenandoah_ppc.ad b/src/hotspot/cpu/ppc/gc/shenandoah/shenandoah_ppc.ad index a75d1a03e4b..f2e5d4f3a27 100644 --- a/src/hotspot/cpu/ppc/gc/shenandoah/shenandoah_ppc.ad +++ b/src/hotspot/cpu/ppc/gc/shenandoah/shenandoah_ppc.ad @@ -1,6 +1,6 @@ // // Copyright (c) 2018, 2021, Red Hat, Inc. All rights reserved. -// Copyright (c) 2012, 2021 SAP SE. All rights reserved. +// Copyright (c) 2012, 2026 SAP SE. 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 @@ -201,10 +201,12 @@ instruct encodePAndStoreN_shenandoah(memory dst, iRegPsrc src, iRegPdstNoScratch // ---------------------- LOAD-STORES ----------------------------------- // -instruct compareAndSwapN_regP_regN_regN_shenandoah(iRegIdst res, iRegPdst mem_ptr, iRegNsrc src1, iRegNsrc src2, iRegPdstNoScratch tmp1, iRegPdstNoScratch tmp2, iRegPdstNoScratch tmp3, flagsRegCR0 cr0) %{ +// Strong CAS also handles WeakCompareAndSwap* on PPC, see JDK-8385633. +instruct compareAndSwapN_regP_regN_regN_shenandoah(iRegIdst res, iRegPdst mem_ptr, iRegNsrc src1, iRegNsrc src2, iRegPdstNoScratch tmp1, iRegPdstNoScratch tmp2, flagsRegCR0 cr0) %{ match(Set res (CompareAndSwapN mem_ptr (Binary src1 src2))); + match(Set res (WeakCompareAndSwapN mem_ptr (Binary src1 src2))); predicate(UseShenandoahGC && (n->as_LoadStore()->barrier_data() != 0) && !need_acquire_load_store(n)); - effect(TEMP_DEF res, TEMP tmp1, TEMP tmp2, TEMP tmp3, KILL cr0); // TEMP_DEF to avoid jump + effect(TEMP_DEF res, TEMP tmp1, TEMP tmp2, KILL cr0); // TEMP_DEF to avoid jump format %{ "CMPXCHGW $res, $mem_ptr, $src1, $src2; as bool" %} ins_encode %{ ShenandoahBarrierSet::assembler()->compare_and_set_c2(this, masm, @@ -214,7 +216,6 @@ instruct compareAndSwapN_regP_regN_regN_shenandoah(iRegIdst res, iRegPdst mem_pt $src2$$Register, $tmp1$$Register, $tmp2$$Register, - $tmp3$$Register, /* exchange */ false, /* is_narrow */ true, /* weak */ false, @@ -223,10 +224,11 @@ instruct compareAndSwapN_regP_regN_regN_shenandoah(iRegIdst res, iRegPdst mem_pt ins_pipe(pipe_class_default); %} -instruct compareAndSwapN_acq_regP_regN_regN_shenandoah(iRegIdst res, iRegPdst mem_ptr, iRegNsrc src1, iRegNsrc src2, iRegPdstNoScratch tmp1, iRegPdstNoScratch tmp2, iRegPdstNoScratch tmp3, flagsRegCR0 cr0) %{ +instruct compareAndSwapN_acq_regP_regN_regN_shenandoah(iRegIdst res, iRegPdst mem_ptr, iRegNsrc src1, iRegNsrc src2, iRegPdstNoScratch tmp1, iRegPdstNoScratch tmp2, flagsRegCR0 cr0) %{ match(Set res (CompareAndSwapN mem_ptr (Binary src1 src2))); + match(Set res (WeakCompareAndSwapN mem_ptr (Binary src1 src2))); predicate(UseShenandoahGC && (n->as_LoadStore()->barrier_data() != 0) && need_acquire_load_store(n)); - effect(TEMP_DEF res, TEMP tmp1, TEMP tmp2, TEMP tmp3, KILL cr0); // TEMP_DEF to avoid jump + effect(TEMP_DEF res, TEMP tmp1, TEMP tmp2, KILL cr0); // TEMP_DEF to avoid jump format %{ "CMPXCHGW $res, $mem_ptr, $src1, $src2; as bool" %} ins_encode %{ ShenandoahBarrierSet::assembler()->compare_and_set_c2(this, masm, @@ -236,7 +238,6 @@ instruct compareAndSwapN_acq_regP_regN_regN_shenandoah(iRegIdst res, iRegPdst me $src2$$Register, $tmp1$$Register, $tmp2$$Register, - $tmp3$$Register, /* exchange */ false, /* is_narrow */ true, /* weak */ false, @@ -245,9 +246,10 @@ instruct compareAndSwapN_acq_regP_regN_regN_shenandoah(iRegIdst res, iRegPdst me ins_pipe(pipe_class_default); %} -instruct compareAndSwapP_regP_regP_regP_shenandoah(iRegIdst res, iRegPdst mem_ptr, iRegPsrc src1, iRegPsrc src2, iRegPdstNoScratch tmp1, iRegPdstNoScratch tmp2, iRegPdstNoScratch tmp3, flagsRegCR0 cr0) %{ +instruct compareAndSwapP_regP_regP_regP_shenandoah(iRegIdst res, iRegPdst mem_ptr, iRegPsrc src1, iRegPsrc src2, iRegPdstNoScratch tmp1, iRegPdstNoScratch tmp2, flagsRegCR0 cr0) %{ match(Set res (CompareAndSwapP mem_ptr (Binary src1 src2))); - effect(TEMP_DEF res, TEMP tmp1, TEMP tmp2, TEMP tmp3, KILL cr0); // TEMP_DEF to avoid jump + match(Set res (WeakCompareAndSwapP mem_ptr (Binary src1 src2))); + effect(TEMP_DEF res, TEMP tmp1, TEMP tmp2, KILL cr0); // TEMP_DEF to avoid jump predicate(UseShenandoahGC && (n->as_LoadStore()->barrier_data() != 0) && !need_acquire_load_store(n)); format %{ "CMPXCHGD $res, $mem_ptr, $src1, $src2; as bool; ptr" %} ins_encode %{ @@ -258,7 +260,6 @@ instruct compareAndSwapP_regP_regP_regP_shenandoah(iRegIdst res, iRegPdst mem_pt $src2$$Register, $tmp1$$Register, $tmp2$$Register, - $tmp3$$Register, /* exchange */ false, /* is_narrow */ false, /* weak */ false, @@ -267,9 +268,10 @@ instruct compareAndSwapP_regP_regP_regP_shenandoah(iRegIdst res, iRegPdst mem_pt ins_pipe(pipe_class_default); %} -instruct compareAndSwapP_acq_regP_regP_regP_shenandoah(iRegIdst res, iRegPdst mem_ptr, iRegPsrc src1, iRegPsrc src2, iRegPdstNoScratch tmp1, iRegPdstNoScratch tmp2, iRegPdstNoScratch tmp3, flagsRegCR0 cr0) %{ +instruct compareAndSwapP_acq_regP_regP_regP_shenandoah(iRegIdst res, iRegPdst mem_ptr, iRegPsrc src1, iRegPsrc src2, iRegPdstNoScratch tmp1, iRegPdstNoScratch tmp2, flagsRegCR0 cr0) %{ match(Set res (CompareAndSwapP mem_ptr (Binary src1 src2))); - effect(TEMP_DEF res, TEMP tmp1, TEMP tmp2, TEMP tmp3, KILL cr0); // TEMP_DEF to avoid jump + match(Set res (WeakCompareAndSwapP mem_ptr (Binary src1 src2))); + effect(TEMP_DEF res, TEMP tmp1, TEMP tmp2, KILL cr0); // TEMP_DEF to avoid jump predicate(UseShenandoahGC && (n->as_LoadStore()->barrier_data() != 0) && need_acquire_load_store(n)); format %{ "CMPXCHGD $res, $mem_ptr, $src1, $src2; as bool; ptr" %} ins_encode %{ @@ -280,7 +282,6 @@ instruct compareAndSwapP_acq_regP_regP_regP_shenandoah(iRegIdst res, iRegPdst me $src2$$Register, $tmp1$$Register, $tmp2$$Register, - $tmp3$$Register, /* exchange */ false, /* is_narrow */ false, /* weak */ false, @@ -289,98 +290,10 @@ instruct compareAndSwapP_acq_regP_regP_regP_shenandoah(iRegIdst res, iRegPdst me ins_pipe(pipe_class_default); %} -instruct weakCompareAndSwapN_regP_regN_regN_shenandoah(iRegIdst res, iRegPdst mem_ptr, iRegNsrc src1, iRegNsrc src2, iRegPdstNoScratch tmp1, iRegPdstNoScratch tmp2, iRegPdstNoScratch tmp3, flagsRegCR0 cr0) %{ - match(Set res (WeakCompareAndSwapN mem_ptr (Binary src1 src2))); - predicate(UseShenandoahGC && (n->as_LoadStore()->barrier_data() != 0) && !need_acquire_load_store(n)); - effect(TEMP_DEF res, TEMP tmp1, TEMP tmp2, TEMP tmp3, KILL cr0); // TEMP_DEF to avoid jump - format %{ "weak CMPXCHGW acq $res, $mem_ptr, $src1, $src2; as bool" %} - ins_encode %{ - ShenandoahBarrierSet::assembler()->compare_and_set_c2(this, masm, - $res$$Register, - $mem_ptr$$Register, - $src1$$Register, - $src2$$Register, - $tmp1$$Register, - $tmp2$$Register, - $tmp3$$Register, - /* exchange */ false, - /* is_narrow */ true, - /* weak */ true, - /* acquire */ false); - %} - ins_pipe(pipe_class_default); -%} - -instruct weakCompareAndSwapN_acq_regP_regN_regN_shenandoah(iRegIdst res, iRegPdst mem_ptr, iRegNsrc src1, iRegNsrc src2, iRegPdstNoScratch tmp1, iRegPdstNoScratch tmp2, iRegPdstNoScratch tmp3, flagsRegCR0 cr0) %{ - match(Set res (WeakCompareAndSwapN mem_ptr (Binary src1 src2))); - predicate(UseShenandoahGC && (n->as_LoadStore()->barrier_data() != 0) && need_acquire_load_store(n)); - effect(TEMP_DEF res, TEMP tmp1, TEMP tmp2, TEMP tmp3, KILL cr0); // TEMP_DEF to avoid jump - format %{ "weak CMPXCHGW acq $res, $mem_ptr, $src1, $src2; as bool" %} - ins_encode %{ - ShenandoahBarrierSet::assembler()->compare_and_set_c2(this, masm, - $res$$Register, - $mem_ptr$$Register, - $src1$$Register, - $src2$$Register, - $tmp1$$Register, - $tmp2$$Register, - $tmp3$$Register, - /* exchange */ false, - /* is_narrow */ true, - /* weak */ true, - /* acquire */ true); - %} - ins_pipe(pipe_class_default); -%} - -instruct weakCompareAndSwapP_regP_regP_regP_shenandoah(iRegIdst res, iRegPdst mem_ptr, iRegPsrc src1, iRegPsrc src2, iRegPdstNoScratch tmp1, iRegPdstNoScratch tmp2, iRegPdstNoScratch tmp3, flagsRegCR0 cr0) %{ - match(Set res (WeakCompareAndSwapP mem_ptr (Binary src1 src2))); - predicate(UseShenandoahGC && (n->as_LoadStore()->barrier_data() != 0) && !need_acquire_load_store(n)); - effect(TEMP_DEF res, TEMP tmp1, TEMP tmp2, TEMP tmp3, KILL cr0); // TEMP_DEF to avoid jump - format %{ "weak CMPXCHGD $res, $mem_ptr, $src1, $src2; as bool; ptr" %} - ins_encode %{ - ShenandoahBarrierSet::assembler()->compare_and_set_c2(this, masm, - $res$$Register, - $mem_ptr$$Register, - $src1$$Register, - $src2$$Register, - $tmp1$$Register, - $tmp2$$Register, - $tmp3$$Register, - /* exchange */ false, - /* is_narrow */ false, - /* weak */ true, - /* acquire */ false); - %} - ins_pipe(pipe_class_default); -%} - -instruct weakCompareAndSwapP_acq_regP_regP_regP_shenandoah(iRegIdst res, iRegPdst mem_ptr, iRegPsrc src1, iRegPsrc src2, iRegPdstNoScratch tmp1, iRegPdstNoScratch tmp2, iRegPdstNoScratch tmp3, flagsRegCR0 cr0) %{ - match(Set res (WeakCompareAndSwapP mem_ptr (Binary src1 src2))); - predicate(UseShenandoahGC && (n->as_LoadStore()->barrier_data() != 0) && need_acquire_load_store(n)); - effect(TEMP_DEF res, TEMP tmp1, TEMP tmp2, TEMP tmp3, KILL cr0); // TEMP_DEF to avoid jump - format %{ "weak CMPXCHGD $res, $mem_ptr, $src1, $src2; as bool; ptr" %} - ins_encode %{ - ShenandoahBarrierSet::assembler()->compare_and_set_c2(this, masm, - $res$$Register, - $mem_ptr$$Register, - $src1$$Register, - $src2$$Register, - $tmp1$$Register, - $tmp2$$Register, - $tmp3$$Register, - /* exchange */ false, - /* is_narrow */ false, - /* weak */ true, - /* acquire */ true); - %} - ins_pipe(pipe_class_default); -%} - -instruct compareAndExchangeN_regP_regN_regN_shenandoah(iRegNdst res, iRegPdst mem_ptr, iRegNsrc src1, iRegNsrc src2, iRegPdstNoScratch tmp1, iRegPdstNoScratch tmp2, iRegPdstNoScratch tmp3, flagsRegCR0 cr0) %{ +instruct compareAndExchangeN_regP_regN_regN_shenandoah(iRegNdst res, iRegPdst mem_ptr, iRegNsrc src1, iRegNsrc src2, iRegPdstNoScratch tmp1, iRegPdstNoScratch tmp2, flagsRegCR0 cr0) %{ match(Set res (CompareAndExchangeN mem_ptr (Binary src1 src2))); predicate(UseShenandoahGC && (n->as_LoadStore()->barrier_data() != 0) && !need_acquire_load_store(n)); - effect(TEMP_DEF res, TEMP tmp1, TEMP tmp2, TEMP tmp3, KILL cr0); + effect(TEMP_DEF res, TEMP tmp1, TEMP tmp2, KILL cr0); format %{ "CMPXCHGW $res, $mem_ptr, $src1, $src2; as narrow oop" %} ins_encode %{ ShenandoahBarrierSet::assembler()->compare_and_set_c2(this, masm, @@ -390,7 +303,6 @@ instruct compareAndExchangeN_regP_regN_regN_shenandoah(iRegNdst res, iRegPdst me $src2$$Register, $tmp1$$Register, $tmp2$$Register, - $tmp3$$Register, /* exchange */ true, /* is_narrow */ true, /* weak */ false, @@ -399,10 +311,10 @@ instruct compareAndExchangeN_regP_regN_regN_shenandoah(iRegNdst res, iRegPdst me ins_pipe(pipe_class_default); %} -instruct compareAndExchangeN_acq_regP_regN_regN_shenandoah(iRegNdst res, iRegPdst mem_ptr, iRegNsrc src1, iRegNsrc src2, iRegPdstNoScratch tmp1, iRegPdstNoScratch tmp2, iRegPdstNoScratch tmp3, flagsRegCR0 cr0) %{ +instruct compareAndExchangeN_acq_regP_regN_regN_shenandoah(iRegNdst res, iRegPdst mem_ptr, iRegNsrc src1, iRegNsrc src2, iRegPdstNoScratch tmp1, iRegPdstNoScratch tmp2, flagsRegCR0 cr0) %{ match(Set res (CompareAndExchangeN mem_ptr (Binary src1 src2))); predicate(UseShenandoahGC && (n->as_LoadStore()->barrier_data() != 0) && need_acquire_load_store(n)); - effect(TEMP_DEF res, TEMP tmp1, TEMP tmp2, TEMP tmp3, KILL cr0); + effect(TEMP_DEF res, TEMP tmp1, TEMP tmp2, KILL cr0); format %{ "CMPXCHGW $res, $mem_ptr, $src1, $src2; as narrow oop" %} ins_encode %{ ShenandoahBarrierSet::assembler()->compare_and_set_c2(this, masm, @@ -412,7 +324,6 @@ instruct compareAndExchangeN_acq_regP_regN_regN_shenandoah(iRegNdst res, iRegPds $src2$$Register, $tmp1$$Register, $tmp2$$Register, - $tmp3$$Register, /* exchange */ true, /* is_narrow */ true, /* weak */ false, @@ -421,10 +332,10 @@ instruct compareAndExchangeN_acq_regP_regN_regN_shenandoah(iRegNdst res, iRegPds ins_pipe(pipe_class_default); %} -instruct compareAndExchangeP_regP_regP_regP_shenandoah(iRegPdst res, iRegPdst mem_ptr, iRegPsrc src1, iRegPsrc src2, iRegPdstNoScratch tmp1, iRegPdstNoScratch tmp2, iRegPdstNoScratch tmp3, flagsRegCR0 cr0) %{ +instruct compareAndExchangeP_regP_regP_regP_shenandoah(iRegPdst res, iRegPdst mem_ptr, iRegPsrc src1, iRegPsrc src2, iRegPdstNoScratch tmp1, iRegPdstNoScratch tmp2, flagsRegCR0 cr0) %{ match(Set res (CompareAndExchangeP mem_ptr (Binary src1 src2))); predicate(UseShenandoahGC && (n->as_LoadStore()->barrier_data() != 0) && !need_acquire_load_store(n)); - effect(TEMP_DEF res, TEMP tmp1, TEMP tmp2, TEMP tmp3, KILL cr0); + effect(TEMP_DEF res, TEMP tmp1, TEMP tmp2, KILL cr0); format %{ "CMPXCHGD $res, $mem_ptr, $src1, $src2; as ptr; ptr" %} ins_encode %{ ShenandoahBarrierSet::assembler()->compare_and_set_c2(this, masm, @@ -434,7 +345,6 @@ instruct compareAndExchangeP_regP_regP_regP_shenandoah(iRegPdst res, iRegPdst me $src2$$Register, $tmp1$$Register, $tmp2$$Register, - $tmp3$$Register, /* exchange */ true, /* is_narrow */ false, /* weak */ false, @@ -443,10 +353,10 @@ instruct compareAndExchangeP_regP_regP_regP_shenandoah(iRegPdst res, iRegPdst me ins_pipe(pipe_class_default); %} -instruct compareAndExchangeP_acq_regP_regP_regP_shenandoah(iRegPdst res, iRegPdst mem_ptr, iRegPsrc src1, iRegPsrc src2, iRegPdstNoScratch tmp1, iRegPdstNoScratch tmp2, iRegPdstNoScratch tmp3, flagsRegCR0 cr0) %{ +instruct compareAndExchangeP_acq_regP_regP_regP_shenandoah(iRegPdst res, iRegPdst mem_ptr, iRegPsrc src1, iRegPsrc src2, iRegPdstNoScratch tmp1, iRegPdstNoScratch tmp2, flagsRegCR0 cr0) %{ match(Set res (CompareAndExchangeP mem_ptr (Binary src1 src2))); predicate(UseShenandoahGC && (n->as_LoadStore()->barrier_data() != 0) && need_acquire_load_store(n)); - effect(TEMP_DEF res, TEMP tmp1, TEMP tmp2, TEMP tmp3, KILL cr0); + effect(TEMP_DEF res, TEMP tmp1, TEMP tmp2, KILL cr0); format %{ "CMPXCHGD $res, $mem_ptr, $src1, $src2; as ptr; ptr" %} ins_encode %{ ShenandoahBarrierSet::assembler()->compare_and_set_c2(this, masm, @@ -456,7 +366,6 @@ instruct compareAndExchangeP_acq_regP_regP_regP_shenandoah(iRegPdst res, iRegPds $src2$$Register, $tmp1$$Register, $tmp2$$Register, - $tmp3$$Register, /* exchange */ true, /* is_narrow */ false, /* weak */ false, @@ -465,10 +374,10 @@ instruct compareAndExchangeP_acq_regP_regP_regP_shenandoah(iRegPdst res, iRegPds ins_pipe(pipe_class_default); %} -instruct getAndSetP_shenandoah(iRegPdst res, iRegPdst mem_ptr, iRegPsrc src, iRegPdstNoScratch tmp1, iRegPdstNoScratch tmp2, iRegPdstNoScratch tmp3, flagsRegCR0 cr0) %{ +instruct getAndSetP_shenandoah(iRegPdst res, iRegPdst mem_ptr, iRegPsrc src, iRegPdstNoScratch tmp1, iRegPdstNoScratch tmp2, flagsRegCR0 cr0) %{ match(Set res (GetAndSetP mem_ptr src)); predicate(UseShenandoahGC && (n->as_LoadStore()->barrier_data() != 0)); - effect(TEMP_DEF res, TEMP tmp1, TEMP tmp2, TEMP tmp3, KILL cr0); + effect(TEMP_DEF res, TEMP tmp1, TEMP tmp2, KILL cr0); format %{ "GetAndSetP $res, $mem_ptr, $src" %} ins_encode %{ ShenandoahBarrierSet::assembler()->get_and_set_c2(this, masm, @@ -476,16 +385,15 @@ instruct getAndSetP_shenandoah(iRegPdst res, iRegPdst mem_ptr, iRegPsrc src, iRe $src$$Register, $mem_ptr$$Register, $tmp1$$Register, - $tmp2$$Register, - $tmp3$$Register); + $tmp2$$Register); %} ins_pipe(pipe_class_default); %} -instruct getAndSetN_shenandoah(iRegNdst res, iRegPdst mem_ptr, iRegNsrc src, iRegPdstNoScratch tmp1, iRegPdstNoScratch tmp2, iRegPdstNoScratch tmp3, flagsRegCR0 cr0) %{ +instruct getAndSetN_shenandoah(iRegNdst res, iRegPdst mem_ptr, iRegNsrc src, iRegPdstNoScratch tmp1, iRegPdstNoScratch tmp2, flagsRegCR0 cr0) %{ match(Set res (GetAndSetN mem_ptr src)); predicate(UseShenandoahGC && (n->as_LoadStore()->barrier_data() != 0)); - effect(TEMP_DEF res, TEMP tmp1, TEMP tmp2, TEMP tmp3, KILL cr0); + effect(TEMP_DEF res, TEMP tmp1, TEMP tmp2, KILL cr0); format %{ "GetAndSetN $res, $mem_ptr, $src" %} ins_encode %{ ShenandoahBarrierSet::assembler()->get_and_set_c2(this, masm, @@ -493,8 +401,7 @@ instruct getAndSetN_shenandoah(iRegNdst res, iRegPdst mem_ptr, iRegNsrc src, iRe $src$$Register, $mem_ptr$$Register, $tmp1$$Register, - $tmp2$$Register, - $tmp3$$Register); + $tmp2$$Register); %} ins_pipe(pipe_class_default); %} From 2352da05efa66e29a0f041290cb841b483ae72d8 Mon Sep 17 00:00:00 2001 From: Nizar Benalla Date: Wed, 3 Jun 2026 10:42:38 +0000 Subject: [PATCH 07/61] 8385836: Update comment in SourceVersion for language evolution history for changes in 27 Reviewed-by: darcy, iris --- .../share/classes/javax/lang/model/SourceVersion.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/java.compiler/share/classes/javax/lang/model/SourceVersion.java b/src/java.compiler/share/classes/javax/lang/model/SourceVersion.java index 2835143dc4e..a5d3a523434 100644 --- a/src/java.compiler/share/classes/javax/lang/model/SourceVersion.java +++ b/src/java.compiler/share/classes/javax/lang/model/SourceVersion.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2026, Oracle and/or its affiliates. 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 @@ -87,7 +87,8 @@ public enum SourceVersion { * third preview) * 26: no changes (primitive Types in Patterns, instanceof, and * switch in fourth preview) - * 27: tbd + * 27: no changes (primitive Types in Patterns, instanceof, and + * switch in fifth preview) */ /** From a29ef339f218217b017197900652c6b6759df77e Mon Sep 17 00:00:00 2001 From: Thomas Schatzl Date: Wed, 3 Jun 2026 10:43:10 +0000 Subject: [PATCH 08/61] 8382249: G1: Racy concurrent update of young gen size and revising young gen length Reviewed-by: iwalulya, aboldtch --- src/hotspot/share/gc/g1/g1Policy.cpp | 46 +++++++++++++++------ src/hotspot/share/gc/g1/g1Policy.hpp | 16 ++++--- src/hotspot/share/gc/g1/g1YoungGenSizer.cpp | 26 ++++++------ src/hotspot/share/gc/g1/g1YoungGenSizer.hpp | 11 ++--- 4 files changed, 64 insertions(+), 35 deletions(-) diff --git a/src/hotspot/share/gc/g1/g1Policy.cpp b/src/hotspot/share/gc/g1/g1Policy.cpp index f9e084248b9..71a482e2505 100644 --- a/src/hotspot/share/gc/g1/g1Policy.cpp +++ b/src/hotspot/share/gc/g1/g1Policy.cpp @@ -160,7 +160,7 @@ void G1Policy::record_new_heap_size(uint new_number_of_regions) { double reserve_regions_d = (double) new_number_of_regions * _reserve_factor; // We use ceiling so that if reserve_regions_d is > 0.0 (but // smaller than 1.0) we'll get 1. - _reserve_regions = (uint) ceil(reserve_regions_d); + _reserve_regions.store_relaxed((uint) ceil(reserve_regions_d)); _young_gen_sizer.heap_size_changed(new_number_of_regions); @@ -186,8 +186,22 @@ void G1Policy::update_young_length_bounds() { void G1Policy::update_young_length_bounds(size_t pending_cards, size_t card_rs_length, size_t code_root_rs_length) { uint old_young_list_target_length = young_list_target_length(); - uint new_young_list_desired_length = calculate_young_desired_length(pending_cards, card_rs_length, code_root_rs_length); - uint new_young_list_target_length = calculate_young_target_length(new_young_list_desired_length); + uint min_young_length_by_sizer = _young_gen_sizer.min_desired_young_length(); + uint max_young_length_by_sizer = _young_gen_sizer.max_desired_young_length(); + + if (max_young_length_by_sizer < min_young_length_by_sizer) { + // This can happen due to races with heap_size_changed() at mutator time. Do not update the young gen + // lengths. Will be updated on the next regular call anyway. + assert(!SafepointSynchronize::is_at_safepoint(), "must be"); + return; + } + + uint new_young_list_desired_length = calculate_young_desired_length(pending_cards, + card_rs_length, + code_root_rs_length, + min_young_length_by_sizer, + max_young_length_by_sizer); + uint new_young_list_target_length = calculate_young_target_length(new_young_list_desired_length, min_young_length_by_sizer); log_trace(gc, ergo, heap)("Young list length update: pending cards %zu card_rs_length %zu old target %u desired: %u target: %u", pending_cards, @@ -224,9 +238,9 @@ void G1Policy::update_young_length_bounds(size_t pending_cards, size_t card_rs_l // uint G1Policy::calculate_young_desired_length(size_t pending_cards, size_t card_rs_length, - size_t code_root_rs_length) const { - uint min_young_length_by_sizer = _young_gen_sizer.min_desired_young_length(); - uint max_young_length_by_sizer = _young_gen_sizer.max_desired_young_length(); + size_t code_root_rs_length, + uint min_young_length_by_sizer, + uint max_young_length_by_sizer) const { assert(min_young_length_by_sizer >= 1, "invariant"); assert(max_young_length_by_sizer >= min_young_length_by_sizer, "invariant"); @@ -302,7 +316,7 @@ uint G1Policy::calculate_young_desired_length(size_t pending_cards, // Limit the desired (wished) young length by current free regions. If the request // can be satisfied without using up reserve regions, do so, otherwise eat into // the reserve, giving away at most what the heap sizer allows. -uint G1Policy::calculate_young_target_length(uint desired_young_length) const { +uint G1Policy::calculate_young_target_length(uint desired_young_length, uint min_young_length_by_sizer) const { uint allocated_young_length = _g1h->young_regions_count(); uint receiving_additional_eden; @@ -319,8 +333,14 @@ uint G1Policy::calculate_young_target_length(uint desired_young_length) const { // do, we at most eat the sizer's minimum regions into the reserve or half the // reserve rounded up (if possible; this is an arbitrary value). - uint max_to_eat_into_reserve = MIN2(_young_gen_sizer.min_desired_young_length(), - (_reserve_regions + 1) / 2); + // The heap reserve needs to be snapshotted for consistent use in the following. + // It can be concurrently modified by the mutator as it expands the heap. It can + // only increase at that time, so this is a conservative snapshot. So at worst this + // method will return a too small young gen length in that case. + uint reserve_regions = _reserve_regions.load_relaxed(); + + uint max_to_eat_into_reserve = MIN2(min_young_length_by_sizer, + (reserve_regions + 1) / 2); log_trace(gc, ergo, heap)("Young target length: Common " "free regions at end of collection %u " @@ -329,14 +349,14 @@ uint G1Policy::calculate_young_target_length(uint desired_young_length) const { "max to eat into reserve %u", _free_regions_at_end_of_collection, desired_young_length, - _reserve_regions, + reserve_regions, max_to_eat_into_reserve); uint survivor_regions_count = _g1h->survivor_regions_count(); uint desired_eden_length = desired_young_length - survivor_regions_count; uint allocated_eden_length = allocated_young_length - survivor_regions_count; - if (_free_regions_at_end_of_collection <= _reserve_regions) { + if (_free_regions_at_end_of_collection <= reserve_regions) { // Fully eat (or already eating) into the reserve, hand back at most absolute_min_length regions. uint receiving_eden = MIN3(_free_regions_at_end_of_collection, desired_eden_length, @@ -351,9 +371,9 @@ uint G1Policy::calculate_young_target_length(uint desired_young_length) const { log_trace(gc, ergo, heap)("Young target length: Fully eat into reserve " "receiving eden %u receiving additional eden %u", receiving_eden, receiving_additional_eden); - } else if (_free_regions_at_end_of_collection < (desired_eden_length + _reserve_regions)) { + } else if (_free_regions_at_end_of_collection < (desired_eden_length + reserve_regions)) { // Partially eat into the reserve, at most max_to_eat_into_reserve regions. - uint free_outside_reserve = _free_regions_at_end_of_collection - _reserve_regions; + uint free_outside_reserve = _free_regions_at_end_of_collection - reserve_regions; assert(free_outside_reserve < desired_eden_length, "must be %u %u", free_outside_reserve, desired_eden_length); diff --git a/src/hotspot/share/gc/g1/g1Policy.hpp b/src/hotspot/share/gc/g1/g1Policy.hpp index 45952963168..0a472dd0527 100644 --- a/src/hotspot/share/gc/g1/g1Policy.hpp +++ b/src/hotspot/share/gc/g1/g1Policy.hpp @@ -91,9 +91,11 @@ class G1Policy: public CHeapObj { G1SurvRateGroup* _survivor_surv_rate_group; double _reserve_factor; - // This will be set when the heap is expanded - // for the first time during initialization. - uint _reserve_regions; + // The allocation reserve in number of regions that we try to keep free. + // G1 allocation of new regions for eden is restrained when allocating into that reserve. + // This intentionally slows down the allocation when the heap is close to full to allow + // concurrent marking to finish and hopefully avoid a Full GC. + Atomic _reserve_regions; G1YoungGenSizer _young_gen_sizer; @@ -224,9 +226,13 @@ private: // Calculate desired young length based on current situation without taking actually // available free regions into account. - uint calculate_young_desired_length(size_t pending_cards, size_t card_rs_length, size_t code_root_rs_length) const; + uint calculate_young_desired_length(size_t pending_cards, + size_t card_rs_length, + size_t code_root_rs_length, + uint min_young_length_by_sizer, + uint max_young_length_by_sizer) const; // Limit the given desired young length to available free regions. - uint calculate_young_target_length(uint desired_young_length) const; + uint calculate_young_target_length(uint desired_young_length, uint min_young_length_by_sizer) const; double predict_survivor_regions_evac_time() const; double predict_retained_regions_evac_time() const; diff --git a/src/hotspot/share/gc/g1/g1YoungGenSizer.cpp b/src/hotspot/share/gc/g1/g1YoungGenSizer.cpp index ffa573c68cc..60c79ec28df 100644 --- a/src/hotspot/share/gc/g1/g1YoungGenSizer.cpp +++ b/src/hotspot/share/gc/g1/g1YoungGenSizer.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2026, Oracle and/or its affiliates. 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 @@ -30,7 +30,7 @@ #include "runtime/globals_extension.hpp" G1YoungGenSizer::G1YoungGenSizer() : _sizer_kind(SizerDefaults), - _use_adaptive_sizing(true), _min_desired_young_length(0), _max_desired_young_length(0) { + _use_adaptive_sizing(true), _min_desired_young_length(), _max_desired_young_length(0) { precond(!FLAG_IS_ERGO(NewRatio)); precond(!FLAG_IS_ERGO(NewSize)); @@ -100,16 +100,16 @@ G1YoungGenSizer::G1YoungGenSizer() : _sizer_kind(SizerDefaults), } if (user_specified_NewSize) { - _min_desired_young_length = MAX2((uint)(NewSize / G1HeapRegion::GrainBytes), 1U); + _min_desired_young_length.store_relaxed(MAX2((uint)(NewSize / G1HeapRegion::GrainBytes), 1U)); } if (user_specified_MaxNewSize) { - _max_desired_young_length = MAX2((uint)(MaxNewSize / G1HeapRegion::GrainBytes), 1U); + _max_desired_young_length.store_relaxed(MAX2((uint)(MaxNewSize / G1HeapRegion::GrainBytes), 1U)); } if (user_specified_NewSize && user_specified_MaxNewSize) { _sizer_kind = SizerMaxAndNewSize; - _use_adaptive_sizing = _min_desired_young_length != _max_desired_young_length; + _use_adaptive_sizing = min_desired_young_length() != max_desired_young_length(); } else if (user_specified_NewSize) { _sizer_kind = SizerNewSizeOnly; } else { @@ -159,20 +159,22 @@ void G1YoungGenSizer::recalculate_min_max_young_length(uint number_of_heap_regio } void G1YoungGenSizer::adjust_max_new_size(uint number_of_heap_regions) { - // We need to pass the desired values because recalculation may not update these // values in some cases. - uint temp = _min_desired_young_length; - uint result = _max_desired_young_length; - recalculate_min_max_young_length(number_of_heap_regions, &temp, &result); + uint unused_new_min = min_desired_young_length(); + uint new_max = max_desired_young_length(); + recalculate_min_max_young_length(number_of_heap_regions, &unused_new_min, &new_max); - size_t max_young_size = result * G1HeapRegion::GrainBytes; + size_t max_young_size = new_max * G1HeapRegion::GrainBytes; if (max_young_size != MaxNewSize) { FLAG_SET_ERGO(MaxNewSize, max_young_size); } } void G1YoungGenSizer::heap_size_changed(uint new_number_of_heap_regions) { - recalculate_min_max_young_length(new_number_of_heap_regions, &_min_desired_young_length, - &_max_desired_young_length); + uint min = min_desired_young_length(); + uint max = max_desired_young_length(); + recalculate_min_max_young_length(new_number_of_heap_regions, &min, &max); + _min_desired_young_length.store_relaxed(min); + _max_desired_young_length.store_relaxed(max); } diff --git a/src/hotspot/share/gc/g1/g1YoungGenSizer.hpp b/src/hotspot/share/gc/g1/g1YoungGenSizer.hpp index 138989d4d94..c60c3c373a9 100644 --- a/src/hotspot/share/gc/g1/g1YoungGenSizer.hpp +++ b/src/hotspot/share/gc/g1/g1YoungGenSizer.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2026, Oracle and/or its affiliates. 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 @@ -25,6 +25,7 @@ #ifndef SHARE_GC_G1_G1YOUNGGENSIZER_HPP #define SHARE_GC_G1_G1YOUNGGENSIZER_HPP +#include "runtime/atomic.hpp" #include "utilities/globalDefinitions.hpp" // There are three command line options related to the young gen size: @@ -78,8 +79,8 @@ private: // true otherwise. bool _use_adaptive_sizing; - uint _min_desired_young_length; - uint _max_desired_young_length; + Atomic _min_desired_young_length; + Atomic _max_desired_young_length; uint calculate_default_min_length(uint new_number_of_heap_regions); uint calculate_default_max_length(uint new_number_of_heap_regions); @@ -96,10 +97,10 @@ public: virtual void heap_size_changed(uint new_number_of_heap_regions); uint min_desired_young_length() const { - return _min_desired_young_length; + return _min_desired_young_length.load_relaxed(); } uint max_desired_young_length() const { - return _max_desired_young_length; + return _max_desired_young_length.load_relaxed(); } bool use_adaptive_young_list_length() const { From bf14f24f76fee928f9911b07b0676731fc47417c Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Wed, 3 Jun 2026 12:28:18 +0000 Subject: [PATCH 09/61] 8385013: Startup regression ~30% / 7-10ms with noop Reviewed-by: liach --- .../jdk/internal/lang/LazyConstantImpl.java | 47 ++++++++++--------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/src/java.base/share/classes/jdk/internal/lang/LazyConstantImpl.java b/src/java.base/share/classes/jdk/internal/lang/LazyConstantImpl.java index e5a1dd62c11..2031789892c 100644 --- a/src/java.base/share/classes/jdk/internal/lang/LazyConstantImpl.java +++ b/src/java.base/share/classes/jdk/internal/lang/LazyConstantImpl.java @@ -27,6 +27,7 @@ package jdk.internal.lang; import jdk.internal.misc.Unsafe; import jdk.internal.vm.annotation.AOTSafeClassInitializer; +import jdk.internal.vm.annotation.DontInline; import jdk.internal.vm.annotation.ForceInline; import jdk.internal.vm.annotation.Stable; @@ -84,35 +85,35 @@ public final class LazyConstantImpl implements LazyConstant { return (t != null) ? t : getSlowPath(); } - @SuppressWarnings("unchecked") + @DontInline private T getSlowPath() { preventReentry(); synchronized (this) { T t = getAcquire(); if (t == null) { - switch (computingFunctionOrExceptionType) { - case Supplier computingFunction -> { - try { - @SuppressWarnings("unchecked") - final T newT = (T) computingFunction.get(); - t = newT; - Objects.requireNonNull(t); - setRelease(t); - // Allow the underlying supplier to be collected after - // a successful initialization - computingFunctionOrExceptionType = null; - } catch (Throwable ex) { - // Release the original computing function and replace it with - // an exception marker - final String exceptionType = ex.getClass().getName().intern(); - computingFunctionOrExceptionType = exceptionType; - throw unableToAccessConstant(exceptionType, ex); - } + final Object cf = computingFunctionOrExceptionType; + // Don't use pattern matching here in order to improve startup time. + if (cf instanceof Supplier computingFunction) { + try { + @SuppressWarnings("unchecked") + final T newT = (T) computingFunction.get(); + t = newT; + Objects.requireNonNull(t); + setRelease(t); + // Allow the underlying supplier to be collected after + // a successful initialization + computingFunctionOrExceptionType = null; + } catch (Throwable ex) { + // Release the original computing function and replace it with + // an exception marker + final String exceptionType = ex.getClass().getName().intern(); + computingFunctionOrExceptionType = exceptionType; + throw unableToAccessConstant(exceptionType, ex); } - case String exceptionType -> - throw unableToAccessConstant(exceptionType, null); - default -> - throw new InternalError("Cannot reach here"); + } else if (cf instanceof String exceptionType) { + throw unableToAccessConstant(exceptionType, null); + } else { + throw new InternalError("Cannot reach here"); } } return t; From 15726f9b6c93f4e1707a589901b42b376c9ca82e Mon Sep 17 00:00:00 2001 From: Kerem Kat Date: Wed, 3 Jun 2026 13:45:26 +0000 Subject: [PATCH 10/61] 8384182: Incorrect assertion eq4a_state in vectorization AlignmentSolver::solve Reviewed-by: chagedorn, snatarajan --- src/hotspot/share/opto/vectorization.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hotspot/share/opto/vectorization.cpp b/src/hotspot/share/opto/vectorization.cpp index 8e0ca927a16..96ac4d78dcd 100644 --- a/src/hotspot/share/opto/vectorization.cpp +++ b/src/hotspot/share/opto/vectorization.cpp @@ -1744,8 +1744,8 @@ AlignmentSolution* AlignmentSolver::solve() const { // And since abs(C_pre) < aw, the solutions of (4a, b, c) can now only be constrained or empty. // But since we already handled the empty case, the solutions are now all constrained. assert(eq4a_state == EQ4::State::CONSTRAINED && - eq4a_state == EQ4::State::CONSTRAINED && - eq4a_state == EQ4::State::CONSTRAINED, "all must be constrained now"); + eq4b_state == EQ4::State::CONSTRAINED && + eq4c_state == EQ4::State::CONSTRAINED, "all must be constrained now"); // And since they are all constrained, we must have: // From d3483a98d4fd0763e0e7d6b24737830b2f1b1934 Mon Sep 17 00:00:00 2001 From: Derek White Date: Wed, 3 Jun 2026 13:56:28 +0000 Subject: [PATCH 11/61] 8385831: Prevent APX instruction being generated on non-APX machines Reviewed-by: sviswanathan, shade --- src/hotspot/cpu/x86/x86.ad | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/hotspot/cpu/x86/x86.ad b/src/hotspot/cpu/x86/x86.ad index ab39692b44b..b3dd1a0812b 100644 --- a/src/hotspot/cpu/x86/x86.ad +++ b/src/hotspot/cpu/x86/x86.ad @@ -13959,6 +13959,7 @@ instruct orL_rReg_ndd(rRegL dst, rRegL src1, rRegL src2, rFlagsReg cr) // Use any_RegP to match R15 (TLS register) without spilling. instruct orL_rReg_castP2X(rRegL dst, any_RegP src, rFlagsReg cr) %{ + predicate(!UseAPX); match(Set dst (OrL dst (CastP2X src))); effect(KILL cr); flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag); @@ -13971,6 +13972,7 @@ instruct orL_rReg_castP2X(rRegL dst, any_RegP src, rFlagsReg cr) %{ %} instruct orL_rReg_castP2X_ndd(rRegL dst, any_RegP src1, any_RegP src2, rFlagsReg cr) %{ + predicate(UseAPX); match(Set dst (OrL src1 (CastP2X src2))); effect(KILL cr); flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag); From 85db081db4389cc0c1c75bf93fcd3db843ea1f31 Mon Sep 17 00:00:00 2001 From: Ivan Walulya Date: Wed, 3 Jun 2026 14:04:11 +0000 Subject: [PATCH 12/61] 8379846: G1: IHOP Allocation rate calculation too sensitive to outliers causing endless concurrent cycles Co-authored-by: Thomas Schatzl Reviewed-by: tschatzl, aboldtch --- src/hotspot/share/gc/g1/g1CollectedHeap.cpp | 4 +- .../share/gc/g1/g1ConcurrentCycleTracker.cpp | 131 +++ .../share/gc/g1/g1ConcurrentCycleTracker.hpp | 111 ++ .../g1ConcurrentStartToMixedTimeTracker.hpp | 89 -- src/hotspot/share/gc/g1/g1IHOPControl.cpp | 136 +-- src/hotspot/share/gc/g1/g1IHOPControl.hpp | 45 +- .../share/gc/g1/g1OldGenAllocationTracker.cpp | 51 +- .../share/gc/g1/g1OldGenAllocationTracker.hpp | 81 +- src/hotspot/share/gc/g1/g1Policy.cpp | 126 +-- src/hotspot/share/gc/g1/g1Policy.hpp | 17 +- src/hotspot/share/gc/g1/g1Trace.cpp | 43 +- src/hotspot/share/gc/g1/g1Trace.hpp | 21 +- .../gc/g1/g1YoungGCPostEvacuateTasks.cpp | 10 +- src/hotspot/share/jfr/metadata/metadata.xml | 16 +- .../gtest/gc/g1/test_g1IHOPControl.cpp | 961 ++++++++++++++---- .../jtreg/gc/g1/ihop/lib/IhopUtils.java | 6 +- .../jfr/event/metadata/TestEventMetadata.java | 4 +- 17 files changed, 1301 insertions(+), 551 deletions(-) create mode 100644 src/hotspot/share/gc/g1/g1ConcurrentCycleTracker.cpp create mode 100644 src/hotspot/share/gc/g1/g1ConcurrentCycleTracker.hpp delete mode 100644 src/hotspot/share/gc/g1/g1ConcurrentStartToMixedTimeTracker.hpp diff --git a/src/hotspot/share/gc/g1/g1CollectedHeap.cpp b/src/hotspot/share/gc/g1/g1CollectedHeap.cpp index d0e549c9b11..8ea880c820f 100644 --- a/src/hotspot/share/gc/g1/g1CollectedHeap.cpp +++ b/src/hotspot/share/gc/g1/g1CollectedHeap.cpp @@ -729,7 +729,7 @@ HeapWord* G1CollectedHeap::attempt_allocation_humongous(size_t word_size) { result = humongous_obj_allocate(word_size); if (result != nullptr) { policy()->old_gen_alloc_tracker()-> - add_allocated_humongous_bytes_since_last_gc(humongous_byte_size); + add_allocated_humongous_bytes(humongous_byte_size); return result; } @@ -2861,7 +2861,7 @@ void G1CollectedHeap::record_obj_copy_mem_stats() { G1ReservePercent); policy()->old_gen_alloc_tracker()-> - add_allocated_bytes_since_last_gc(total_old_allocated * HeapWordSize); + add_allocated_non_humongous_bytes(total_old_allocated * HeapWordSize); _gc_tracer_stw->report_evacuation_statistics(create_g1_evac_summary(&_survivor_evac_stats), create_g1_evac_summary(&_old_evac_stats)); diff --git a/src/hotspot/share/gc/g1/g1ConcurrentCycleTracker.cpp b/src/hotspot/share/gc/g1/g1ConcurrentCycleTracker.cpp new file mode 100644 index 00000000000..ab43a693a60 --- /dev/null +++ b/src/hotspot/share/gc/g1/g1ConcurrentCycleTracker.cpp @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#include "gc/g1/g1ConcurrentCycleTracker.hpp" +#include "gc/g1/g1OldGenAllocationTracker.hpp" +#include "utilities/checkedCast.hpp" +#include "utilities/debug.hpp" + +void G1ConcurrentCycleTracker::reset() { + _state = CycleState::Inactive; + _total_pause_time_s = 0.0; + _cycle_start_time_s = 0.0; + _cycle_end_time_s = 0.0; + + _humongous_bytes_at_start = 0; + _non_humongous_allocated_bytes = 0; + _peak_extra_humongous_occupancy_bytes = 0; +} + +void G1ConcurrentCycleTracker::update_allocation_stats(G1AllocationIntervalStats interval_stats) { + if (!is_active()) { + return; + } + + _non_humongous_allocated_bytes += interval_stats._non_humongous_allocated_bytes; + + intptr_t delta_before = checked_cast(interval_stats._total_humongous_before_bytes) - + checked_cast(_humongous_bytes_at_start); + + intptr_t delta_after = delta_before + + checked_cast(interval_stats._humongous_allocated_bytes); + + if (delta_after > 0) { + _peak_extra_humongous_occupancy_bytes = MAX2(_peak_extra_humongous_occupancy_bytes, checked_cast(delta_after)); + } +} + +G1ConcurrentCycleTracker::G1ConcurrentCycleTracker() +: _state(CycleState::Inactive), + _cycle_start_time_s(0.0), + _cycle_end_time_s(0.0), + _total_pause_time_s(0.0), + _humongous_bytes_at_start(0), + _non_humongous_allocated_bytes(0), + _peak_extra_humongous_occupancy_bytes(0) +{ } + +void G1ConcurrentCycleTracker::record_cycle_start(double cycle_start_time_s, size_t humongous_bytes_after_pause) { + assert(_state == CycleState::Inactive, "Concurrent start out of order."); + _cycle_start_time_s = cycle_start_time_s; + _humongous_bytes_at_start = humongous_bytes_after_pause; + _state = CycleState::Active; +} + +void G1ConcurrentCycleTracker::record_allocation_interval(Pause pause_type, + bool is_periodic_gc, + double pause_start_time_s, + double pause_end_time_s, + G1AllocationIntervalStats interval_stats) { + if (is_periodic_gc || pause_type == Pause::Full || pause_type == Pause::ConcurrentStartUndo) { + reset(); + return; + } + + if (pause_type == Pause::ConcurrentStartFull) { + record_cycle_start(pause_end_time_s, interval_stats._total_humongous_after_bytes); + return; + } + + if (!is_active()) { + return; + } + + update_allocation_stats(interval_stats); + + if (pause_type == Pause::Mixed) { + complete_cycle(pause_start_time_s); + return; + } + + assert(pause_type == Pause::Normal || + pause_type == Pause::PrepareMixed || + pause_type == Pause::Remark || + pause_type == Pause::Cleanup, + "Unhandled pause type"); + + add_pause(pause_end_time_s - pause_start_time_s); +} + +void G1ConcurrentCycleTracker::complete_cycle(double cycle_end_time_s) { + precond(is_active()); + + _cycle_end_time_s = cycle_end_time_s; + _state = CycleState::Complete; +} + +G1ConcurrentCycleStats G1ConcurrentCycleTracker::get_and_reset_cycle_stats() { + precond(has_completed_cycle()); + + double cycle_duration = (_cycle_end_time_s - _cycle_start_time_s - _total_pause_time_s); + + G1ConcurrentCycleStats stats{ + cycle_duration, + _non_humongous_allocated_bytes, + _peak_extra_humongous_occupancy_bytes + }; + + reset(); + return stats; +} diff --git a/src/hotspot/share/gc/g1/g1ConcurrentCycleTracker.hpp b/src/hotspot/share/gc/g1/g1ConcurrentCycleTracker.hpp new file mode 100644 index 00000000000..7d575e7abcd --- /dev/null +++ b/src/hotspot/share/gc/g1/g1ConcurrentCycleTracker.hpp @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2001, 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef SHARE_GC_G1_G1CONCURRENTCYCLETRACKER_HPP +#define SHARE_GC_G1_G1CONCURRENTCYCLETRACKER_HPP + +#include "gc/g1/g1CollectorState.hpp" +#include "utilities/globalDefinitions.hpp" + +struct G1AllocationIntervalStats; + +// The sampling interval for G1ConcurrentCycleTracker covers the concurrent cycle +// from the end of the Concurrent Start GC to start of the first Mixed GC. +struct G1ConcurrentCycleStats { + double _cycle_duration_s; + size_t _non_humongous_allocated_bytes; + size_t _peak_extra_humongous_occupancy_bytes; + + G1ConcurrentCycleStats(double cycle_duration_s, + size_t non_humongous_allocated_bytes, + size_t peak_extra_humongous_occupancy_bytes) + : _cycle_duration_s(cycle_duration_s), + _non_humongous_allocated_bytes(non_humongous_allocated_bytes), + _peak_extra_humongous_occupancy_bytes(peak_extra_humongous_occupancy_bytes) + { } +}; + +class G1ConcurrentCycleTracker { + using Pause = G1CollectorState::Pause; + + enum class CycleState { + Inactive, + Active, + Complete, + }; + + CycleState _state; + double _cycle_start_time_s; + double _cycle_end_time_s; + double _total_pause_time_s; + + // allocation accounting + size_t _humongous_bytes_at_start; + size_t _non_humongous_allocated_bytes; + size_t _peak_extra_humongous_occupancy_bytes; + + void reset(); + + bool is_active() const { + return _state == CycleState::Active; + } + void update_allocation_stats(G1AllocationIntervalStats interval_stats); + + void add_pause(double pause_duration_s) { + _total_pause_time_s += pause_duration_s; + } + + void record_cycle_start(double cycle_start_time_s, size_t humongous_bytes_after_pause); + + void complete_cycle(double cycle_end_time_s); + + public: + G1ConcurrentCycleTracker(); + + void record_allocation_interval(Pause pause_type, + bool is_periodic_gc, + double pause_start_time_s, + double pause_end_time_s, + G1AllocationIntervalStats interval_stats); + + void abort_cycle() { + reset(); + } + + bool has_completed_cycle() const { + return _state == CycleState::Complete; + } + + size_t non_humongous_allocated_bytes() const { + return _non_humongous_allocated_bytes; + } + + size_t peak_extra_humongous_occupancy_bytes() const { + return _peak_extra_humongous_occupancy_bytes; + } + + G1ConcurrentCycleStats get_and_reset_cycle_stats(); +}; + +#endif // SHARE_GC_G1_G1CONCURRENTCYCLETRACKER_HPP diff --git a/src/hotspot/share/gc/g1/g1ConcurrentStartToMixedTimeTracker.hpp b/src/hotspot/share/gc/g1/g1ConcurrentStartToMixedTimeTracker.hpp deleted file mode 100644 index f8bad4bdcd7..00000000000 --- a/src/hotspot/share/gc/g1/g1ConcurrentStartToMixedTimeTracker.hpp +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (c) 2001, 2026, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - * - */ - -#ifndef SHARE_GC_G1_G1CONCURRENTSTARTTOMIXEDTIMETRACKER_HPP -#define SHARE_GC_G1_G1CONCURRENTSTARTTOMIXEDTIMETRACKER_HPP - -#include "utilities/debug.hpp" -#include "utilities/globalDefinitions.hpp" - -// Used to track time from the end of concurrent start to the first mixed GC. -// After calling the concurrent start/mixed gc notifications, the result can be -// obtained in get_and_reset_last_marking_time() once, after which the tracking resets. -// Any pauses recorded by add_pause() will be subtracted from that results. -class G1ConcurrentStartToMixedTimeTracker { -private: - bool _active; - double _concurrent_start_end_time; - double _mixed_start_time; - double _total_pause_time; - - double wall_time() const { - return _mixed_start_time - _concurrent_start_end_time; - } -public: - G1ConcurrentStartToMixedTimeTracker() { reset(); } - - // Record concurrent start pause end, starting the time tracking. - void record_concurrent_start_end(double end_time) { - assert(!_active, "Concurrent start out of order."); - _concurrent_start_end_time = end_time; - _active = true; - } - - // Record the first mixed gc pause start, ending the time tracking. - void record_mixed_gc_start(double start_time) { - if (_active) { - _mixed_start_time = start_time; - _active = false; - } - } - - double get_and_reset_last_marking_time() { - assert(has_result(), "Do not have all measurements yet."); - double result = (_mixed_start_time - _concurrent_start_end_time) - _total_pause_time; - reset(); - return result; - } - - void reset() { - _active = false; - _total_pause_time = 0.0; - _concurrent_start_end_time = -1.0; - _mixed_start_time = -1.0; - } - - void add_pause(double time) { - if (_active) { - _total_pause_time += time; - } - } - - bool is_active() const { return _active; } - - // Returns whether we have a result that can be retrieved. - bool has_result() const { return _mixed_start_time > 0.0 && _concurrent_start_end_time > 0.0; } -}; - -#endif // SHARE_GC_G1_G1CONCURRENTSTARTTOMIXEDTIMETRACKER_HPP diff --git a/src/hotspot/share/gc/g1/g1IHOPControl.cpp b/src/hotspot/share/gc/g1/g1IHOPControl.cpp index 164486123f7..782cdadc13b 100644 --- a/src/hotspot/share/gc/g1/g1IHOPControl.cpp +++ b/src/hotspot/share/gc/g1/g1IHOPControl.cpp @@ -31,19 +31,14 @@ double G1IHOPControl::predict(const TruncatedSeq* seq) const { assert(_is_adaptive, "precondition"); assert(_predictor != nullptr, "precondition"); - - return _predictor->predict_zero_bounded(seq); + return _predictor->predict_zero_bounded(seq); } bool G1IHOPControl::have_enough_data_for_prediction() const { assert(_is_adaptive, "precondition"); return ((size_t)_marking_start_to_mixed_time_s.num() >= G1AdaptiveIHOPNumInitialSamples) && - ((size_t)_old_gen_alloc_rate.num() >= G1AdaptiveIHOPNumInitialSamples); -} - -double G1IHOPControl::last_marking_start_to_mixed_time_s() const { - return _marking_start_to_mixed_time_s.last(); + ((size_t)_old_non_humongous_alloc_rate.num() >= G1AdaptiveIHOPNumInitialSamples); } size_t G1IHOPControl::effective_target_occupancy() const { @@ -66,7 +61,6 @@ size_t G1IHOPControl::effective_target_occupancy() const { } G1IHOPControl::G1IHOPControl(double ihop_percent, - const G1OldGenAllocationTracker* old_gen_alloc_tracker, bool adaptive, const G1Predictions* predictor, size_t heap_reserve_percent, @@ -76,11 +70,10 @@ G1IHOPControl::G1IHOPControl(double ihop_percent, _target_occupancy(0), _heap_reserve_percent(heap_reserve_percent), _heap_waste_percent(heap_waste_percent), - _last_allocation_time_s(0.0), - _old_gen_alloc_tracker(old_gen_alloc_tracker), _predictor(predictor), _marking_start_to_mixed_time_s(10, 0.05), - _old_gen_alloc_rate(10, 0.05), + _old_non_humongous_alloc_rate(10, 0.05), + _peak_extra_humongous_occupancy_in_mark_cycle(10, 0.05), _expected_young_gen_at_first_mixed_gc(0) { assert(_initial_ihop_percent >= 0.0 && _initial_ihop_percent <= 100.0, "IHOP percent out of range: %.3f", ihop_percent); @@ -93,22 +86,28 @@ void G1IHOPControl::update_target_occupancy(size_t new_target_occupancy) { _target_occupancy = new_target_occupancy; } -void G1IHOPControl::report_statistics(G1NewTracer* new_tracer, size_t non_young_occupancy) { - print_log(non_young_occupancy); - send_trace_event(new_tracer, non_young_occupancy); +void G1IHOPControl::report_statistics(G1NewTracer* new_tracer, + size_t non_young_occupancy, + size_t non_humongous_allocation, + size_t peak_extra_humongous_occupancy) { + print_log(non_young_occupancy, non_humongous_allocation, peak_extra_humongous_occupancy); + send_trace_event(new_tracer, non_young_occupancy, + non_humongous_allocation, peak_extra_humongous_occupancy); } -void G1IHOPControl::update_allocation_info(double allocation_time_s, size_t expected_young_gen_size) { - assert(allocation_time_s > 0, "Invalid allocation time: %.3f", allocation_time_s); - _last_allocation_time_s = allocation_time_s; - double alloc_rate = _old_gen_alloc_tracker->last_period_old_gen_growth() / allocation_time_s; - _old_gen_alloc_rate.add(alloc_rate); +void G1IHOPControl::record_expected_young_gen_size(size_t expected_young_gen_size) { _expected_young_gen_at_first_mixed_gc = expected_young_gen_size; } -void G1IHOPControl::add_marking_start_to_mixed_length(double length_s) { - assert(length_s >= 0.0, "Invalid marking length: %.3f", length_s); - _marking_start_to_mixed_time_s.add(length_s); +void G1IHOPControl::record_concurrent_cycle(double marking_start_to_mixed_time_s, + size_t non_humongous_bytes, + size_t peak_extra_humongous_occupancy_bytes) { + assert(marking_start_to_mixed_time_s > 0.0, "Invalid concurrent cycle duration: %.3f", marking_start_to_mixed_time_s); + + double non_humongous_rate = non_humongous_bytes / marking_start_to_mixed_time_s; + _marking_start_to_mixed_time_s.add(marking_start_to_mixed_time_s); + _old_non_humongous_alloc_rate.add(non_humongous_rate); + _peak_extra_humongous_occupancy_in_mark_cycle.add(peak_extra_humongous_occupancy_bytes); } // Determine the old generation occupancy threshold at which to start @@ -121,80 +120,93 @@ size_t G1IHOPControl::old_gen_threshold_for_conc_mark_start() const { return (size_t)(_initial_ihop_percent * _target_occupancy / 100.0); } - // During the time between marking start and the first Mixed GC, - // additional memory will be consumed: - // - Old gen grows due to allocations: - // old_gen_alloc_bytes = old_gen_alloc_rate * marking_start_to_mixed_time - // - Young gen will occupy a certain size at the first Mixed GC: - // expected_young_gen_at_first_mixed_gc - double marking_start_to_mixed_time = predict(&_marking_start_to_mixed_time_s); - double old_gen_alloc_rate = predict(&_old_gen_alloc_rate); - size_t old_gen_alloc_bytes = (size_t)(marking_start_to_mixed_time * old_gen_alloc_rate); - - // Therefore, the total heap occupancy at the first Mixed GC is: - // current_old_gen + old_gen_growth + expected_young_gen_at_first_mixed_gc + // Between Concurrent Start GC and the first Mixed GC (i.e. concurrent cycle), + // we expect extra heap occupancy from three sources: + // - non-humongous allocations into the old-gen + // - peak extra humongous occupancy during the cycle, relative to the humongous occupancy + // at the end of the Concurrent Start GC. + // - we also wish to maintain the current desired young generation until the first Mixed-gc; + // promotions into the old gen should not shrink the young gen and degrade performance. // - // To ensure this does not exceed the target_heap_occupancy, we work - // backwards to compute the old gen occupancy at which marking must start: - // mark_start_threshold = target_heap_occupancy - - // (old_gen_growth + expected_young_gen_at_first_mixed_gc) + // We therefore start marking early enough such that: + // + // old_gen_at_concurrent_start + + // predicted_non_hum_old_growth + + // predicted_peak_extra_humongous_occupancy + + // expected_young_gen_at_first_mixed_gc + // + // stays below the effective target occupancy. + double marking_start_to_mixed_time = predict(&_marking_start_to_mixed_time_s); + double old_non_humongous_alloc_rate = predict(&_old_non_humongous_alloc_rate); + size_t old_non_humongous_alloc_bytes = (size_t)(marking_start_to_mixed_time * old_non_humongous_alloc_rate); - size_t predicted_needed = old_gen_alloc_bytes + _expected_young_gen_at_first_mixed_gc; + size_t predicted_peak_extra_humongous_occupancy = + predict(&_peak_extra_humongous_occupancy_in_mark_cycle); + + size_t reserve_for_young_regions = _expected_young_gen_at_first_mixed_gc; size_t target_heap_occupancy = effective_target_occupancy(); - return predicted_needed < target_heap_occupancy - ? target_heap_occupancy - predicted_needed - : 0; + size_t needed_for_concurrent_cycle = reserve_for_young_regions + + old_non_humongous_alloc_bytes + + predicted_peak_extra_humongous_occupancy; + + size_t threshold = needed_for_concurrent_cycle < target_heap_occupancy ? + target_heap_occupancy - needed_for_concurrent_cycle : 0; + return threshold; } -void G1IHOPControl::print_log(size_t non_young_occupancy) { +void G1IHOPControl::print_log(size_t non_young_occupancy, + size_t non_humongous_allocation, + size_t peak_extra_humongous_occupancy) { assert(_target_occupancy > 0, "Target occupancy still not updated yet."); size_t old_gen_mark_start_threshold = old_gen_threshold_for_conc_mark_start(); - log_debug(gc, ihop)("Basic information (value update), old-gen threshold: %zuB (%1.2f%%), target occupancy: %zuB, old-gen occupancy: %zuB (%1.2f%%), " - "recent old-gen allocation size: %zuB, recent allocation duration: %1.2fms, recent old-gen allocation rate: %1.2fB/s, recent marking phase length: %1.2fms", + log_debug(gc, ihop)("Basic information (value update), old-gen threshold: %zuB (%1.2f%%), target occupancy: %zuB, old-gen occupancy: %zuB (%1.2f%%)", old_gen_mark_start_threshold, percent_of(old_gen_mark_start_threshold, _target_occupancy), _target_occupancy, non_young_occupancy, - percent_of(non_young_occupancy, _target_occupancy), - _old_gen_alloc_tracker->last_period_old_gen_bytes(), - _last_allocation_time_s * 1000.0, - _last_allocation_time_s > 0.0 ? _old_gen_alloc_tracker->last_period_old_gen_bytes() / _last_allocation_time_s : 0.0, - last_marking_start_to_mixed_time_s() * 1000.0); + percent_of(non_young_occupancy, _target_occupancy)); - if (!_is_adaptive) { + if (!_is_adaptive || !have_enough_data_for_prediction()) { return; } size_t effective_target = effective_target_occupancy(); - log_debug(gc, ihop)("Adaptive IHOP information (value update), prediction active: %s, old-gen threshold: %zuB (%1.2f%%), internal target occupancy: %zuB, " - "old-gen occupancy: %zuB, additional buffer size: %zuB, predicted old-gen allocation rate: %1.2fB/s, " - "predicted marking phase length: %1.2fms", - BOOL_TO_STR(have_enough_data_for_prediction()), + log_debug(gc, ihop)("Adaptive IHOP information (value update), old-gen threshold: %zuB (%1.2f%%), internal target occupancy: %zuB, " + "old-gen occupancy: %zuB (%1.2f%%), additional buffer size: %zuB, " + "current non-humongous allocation: %zuB, current peak extra humongous occupancy: %zuB, " + "predicted old-gen non-humongous allocation rate: %1.2fB/s, predicted peak extra humongous occupancy: %1.2fB, " + "predicted concurrent cycle duration: %1.2fms", old_gen_mark_start_threshold, percent_of(old_gen_mark_start_threshold, effective_target), effective_target, non_young_occupancy, + percent_of(non_young_occupancy, effective_target), _expected_young_gen_at_first_mixed_gc, - predict(&_old_gen_alloc_rate), + non_humongous_allocation, peak_extra_humongous_occupancy, + predict(&_old_non_humongous_alloc_rate), + predict(&_peak_extra_humongous_occupancy_in_mark_cycle), predict(&_marking_start_to_mixed_time_s) * 1000.0); } -void G1IHOPControl::send_trace_event(G1NewTracer* tracer, size_t non_young_occupancy) { +void G1IHOPControl::send_trace_event(G1NewTracer* tracer, + size_t non_young_occupancy, + size_t non_humongous_allocation, + size_t peak_extra_humongous_occupancy) { assert(_target_occupancy > 0, "Target occupancy still not updated yet."); tracer->report_basic_ihop_statistics(old_gen_threshold_for_conc_mark_start(), _target_occupancy, - non_young_occupancy, - _old_gen_alloc_tracker->last_period_old_gen_bytes(), - _last_allocation_time_s, - last_marking_start_to_mixed_time_s()); + non_young_occupancy); if (_is_adaptive) { tracer->report_adaptive_ihop_statistics(old_gen_threshold_for_conc_mark_start(), effective_target_occupancy(), non_young_occupancy, _expected_young_gen_at_first_mixed_gc, - predict(&_old_gen_alloc_rate), + non_humongous_allocation, + peak_extra_humongous_occupancy, + predict(&_old_non_humongous_alloc_rate), + predict(&_peak_extra_humongous_occupancy_in_mark_cycle), predict(&_marking_start_to_mixed_time_s), have_enough_data_for_prediction()); } diff --git a/src/hotspot/share/gc/g1/g1IHOPControl.hpp b/src/hotspot/share/gc/g1/g1IHOPControl.hpp index 2836408978b..df92f31065f 100644 --- a/src/hotspot/share/gc/g1/g1IHOPControl.hpp +++ b/src/hotspot/share/gc/g1/g1IHOPControl.hpp @@ -25,7 +25,6 @@ #ifndef SHARE_GC_G1_G1IHOPCONTROL_HPP #define SHARE_GC_G1_G1IHOPCONTROL_HPP -#include "gc/g1/g1OldGenAllocationTracker.hpp" #include "memory/allocation.hpp" #include "utilities/numberSeq.hpp" @@ -53,16 +52,15 @@ class G1IHOPControl : public CHeapObj { // Percentage of free heap that should be considered as waste. const size_t _heap_waste_percent; - // Most recent complete mutator allocation period in seconds. - double _last_allocation_time_s; - const G1OldGenAllocationTracker* _old_gen_alloc_tracker; - const G1Predictions* _predictor; // Wall-clock time in seconds from marking start to the first mixed GC, // excluding GC Pause time. TruncatedSeq _marking_start_to_mixed_time_s; - // Old generation allocation rate in bytes per second. - TruncatedSeq _old_gen_alloc_rate; + // Track old-generation allocations during a concurrent cycle: end of the + // Concurrent Start to the first Mixed GC. + // These values are used only when G1UseAdaptiveIHOP is enabled. + TruncatedSeq _old_non_humongous_alloc_rate; + TruncatedSeq _peak_extra_humongous_occupancy_in_mark_cycle; // The most recent unrestrained size of the young gen. This is used as an additional // factor in the calculation of the threshold, as the threshold is based on @@ -77,19 +75,23 @@ class G1IHOPControl : public CHeapObj { double predict(const TruncatedSeq* seq) const; bool have_enough_data_for_prediction() const; - double last_marking_start_to_mixed_time_s() const; // The "effective" target occupancy the algorithm wants to keep until the start // of Mixed GCs. This is typically lower than the target occupancy, as the // algorithm needs to consider restrictions by the environment. size_t effective_target_occupancy() const; - void print_log(size_t non_young_occupancy); - void send_trace_event(G1NewTracer* tracer, size_t non_young_occupancy); + void print_log(size_t non_young_occupancy, + size_t non_humongous_allocation, + size_t peak_extra_humongous_occupancy); + + void send_trace_event(G1NewTracer* tracer, + size_t non_young_occupancy, + size_t non_humongous_allocation, + size_t peak_extra_humongous_occupancy); public: G1IHOPControl(double ihop_percent, - const G1OldGenAllocationTracker* old_gen_alloc_tracker, bool adaptive, const G1Predictions* predictor, size_t heap_reserve_percent, @@ -98,26 +100,23 @@ class G1IHOPControl : public CHeapObj { // Adjust target occupancy. void update_target_occupancy(size_t new_target_occupancy); - void update_target_after_marking_phase(); - - // Update allocation rate information and current expected young gen size for the - // first mixed gc needed for the predictor. Allocation rate is given as the - // separately passed in allocation increment and the time passed (mutator time) - // for the latest allocation increment here. Allocation size is the memory needed - // during the mutator before and the first mixed gc pause itself. + // Updates expected young gen size for the first mixed gc needed for the predictor. // Contents include young gen at that point, and the memory required for evacuating // the collection set in that first mixed gc (including waste caused by PLAB // allocation etc.). - void update_allocation_info(double allocation_time_s, size_t expected_young_gen_size); + void record_expected_young_gen_size(size_t expected_young_gen_size); - // Update the time spent in the mutator beginning from the end of concurrent start to - // the first mixed gc. - void add_marking_start_to_mixed_length(double length_s); + void record_concurrent_cycle(double marking_start_to_mixed_time_s, + size_t non_humongous_bytes, + size_t peak_extra_humongous_occupancy); // Get the current non-young occupancy at which concurrent marking should start. size_t old_gen_threshold_for_conc_mark_start() const; - void report_statistics(G1NewTracer* tracer, size_t non_young_occupancy); + void report_statistics(G1NewTracer* tracer, + size_t non_young_occupancy, + size_t non_humongous_allocation, + size_t peak_extra_humongous_occupancy); }; #endif // SHARE_GC_G1_G1IHOPCONTROL_HPP diff --git a/src/hotspot/share/gc/g1/g1OldGenAllocationTracker.cpp b/src/hotspot/share/gc/g1/g1OldGenAllocationTracker.cpp index ec3d39d7e50..7355a40c348 100644 --- a/src/hotspot/share/gc/g1/g1OldGenAllocationTracker.cpp +++ b/src/hotspot/share/gc/g1/g1OldGenAllocationTracker.cpp @@ -26,36 +26,43 @@ #include "logging/log.hpp" G1OldGenAllocationTracker::G1OldGenAllocationTracker() : - _last_period_old_gen_bytes(0), - _last_period_old_gen_growth(0), - _humongous_bytes_after_last_gc(0), - _allocated_bytes_since_last_gc(0), - _allocated_humongous_bytes_since_last_gc(0) { + _humongous_bytes_after_last_pause(0), + _allocated_non_humongous_bytes_since_last_pause(0), + _allocated_humongous_bytes_since_last_pause(0) { } -void G1OldGenAllocationTracker::reset_after_gc(size_t humongous_bytes_after_gc) { +G1AllocationIntervalStats G1OldGenAllocationTracker::end_allocation_interval(size_t humongous_bytes_after_pause) { // Calculate actual increase in old, taking eager reclaim into consideration. - size_t last_period_humongous_increase = 0; - if (humongous_bytes_after_gc > _humongous_bytes_after_last_gc) { - last_period_humongous_increase = humongous_bytes_after_gc - _humongous_bytes_after_last_gc; - assert(last_period_humongous_increase <= _allocated_humongous_bytes_since_last_gc, + size_t last_interval_humongous_increase = 0; + if (humongous_bytes_after_pause > _humongous_bytes_after_last_pause) { + last_interval_humongous_increase = humongous_bytes_after_pause - _humongous_bytes_after_last_pause; + assert(last_interval_humongous_increase <= _allocated_humongous_bytes_since_last_pause, "Increase larger than allocated %zu <= %zu", - last_period_humongous_increase, _allocated_humongous_bytes_since_last_gc); + last_interval_humongous_increase, _allocated_humongous_bytes_since_last_pause); } - _last_period_old_gen_growth = _allocated_bytes_since_last_gc + last_period_humongous_increase; - // Calculate and record needed values. - _last_period_old_gen_bytes = _allocated_bytes_since_last_gc + _allocated_humongous_bytes_since_last_gc; - _humongous_bytes_after_last_gc = humongous_bytes_after_gc; + size_t last_interval_old_gen_growth = _allocated_non_humongous_bytes_since_last_pause + + last_interval_humongous_increase; - log_debug(gc, alloc, stats)("Old generation allocation in the last mutator period, " + G1AllocationIntervalStats allocation_interval_stats{ + _allocated_non_humongous_bytes_since_last_pause, + _allocated_humongous_bytes_since_last_pause, + _humongous_bytes_after_last_pause, + humongous_bytes_after_pause + }; + + _humongous_bytes_after_last_pause = humongous_bytes_after_pause; + + log_debug(gc, alloc, stats)("Old generation allocation in the last allocation interval, " "old gen allocated: %zuB, humongous allocated: %zuB, " "old gen growth: %zuB.", - _allocated_bytes_since_last_gc, - _allocated_humongous_bytes_since_last_gc, - _last_period_old_gen_growth); + _allocated_non_humongous_bytes_since_last_pause, + _allocated_humongous_bytes_since_last_pause, + last_interval_old_gen_growth); - // Reset for next mutator period. - _allocated_bytes_since_last_gc = 0; - _allocated_humongous_bytes_since_last_gc = 0; + // Reset for the next interval. + _allocated_non_humongous_bytes_since_last_pause = 0; + _allocated_humongous_bytes_since_last_pause = 0; + + return allocation_interval_stats; } diff --git a/src/hotspot/share/gc/g1/g1OldGenAllocationTracker.hpp b/src/hotspot/share/gc/g1/g1OldGenAllocationTracker.hpp index aa5e3c6c942..218f65cf7c1 100644 --- a/src/hotspot/share/gc/g1/g1OldGenAllocationTracker.hpp +++ b/src/hotspot/share/gc/g1/g1OldGenAllocationTracker.hpp @@ -22,46 +22,71 @@ * */ -#ifndef SHARE_VM_GC_G1_G1OLDGENALLOCATIONTRACKER_HPP -#define SHARE_VM_GC_G1_G1OLDGENALLOCATIONTRACKER_HPP +#ifndef SHARE_GC_G1_G1OLDGENALLOCATIONTRACKER_HPP +#define SHARE_GC_G1_G1OLDGENALLOCATIONTRACKER_HPP -#include "gc/g1/g1HeapRegion.hpp" #include "memory/allocation.hpp" +// Allocation statistics for an allocation interval, i.e. the interval between two +// consecutive STW pauses. +// +// The allocation counters record allocations made during that interval. +// +// _total_humongous_before_bytes is the humongous occupancy after the previous +// pause (at the start of the allocation interval). +// +// _total_humongous_after_bytes is the humongous occupancy after the current +// pause (at the end of the allocation interval). +struct G1AllocationIntervalStats { + size_t _non_humongous_allocated_bytes; + size_t _humongous_allocated_bytes; + size_t _total_humongous_before_bytes; + size_t _total_humongous_after_bytes; + + G1AllocationIntervalStats(size_t non_humongous_allocated_bytes, + size_t humongous_allocated_bytes, + size_t total_humongous_before_bytes, + size_t total_humongous_after_bytes) + : _non_humongous_allocated_bytes(non_humongous_allocated_bytes), + _humongous_allocated_bytes(humongous_allocated_bytes), + _total_humongous_before_bytes(total_humongous_before_bytes), + _total_humongous_after_bytes(total_humongous_after_bytes) + { } + + void record_humongous_allocation(size_t humongous_allocation_bytes) { + _humongous_allocated_bytes += humongous_allocation_bytes; + _total_humongous_after_bytes += humongous_allocation_bytes; + } +}; + // Track allocation details in the old generation. class G1OldGenAllocationTracker : public CHeapObj { - // Total number of bytes allocated in the old generation at the end - // of the last gc. - size_t _last_period_old_gen_bytes; - // Total growth of the old geneneration since the last gc, - // taking eager-reclaim into consideration. - size_t _last_period_old_gen_growth; + // Total size of humongous objects after the last STW pause. + size_t _humongous_bytes_after_last_pause; - // Total size of humongous objects for last gc. - size_t _humongous_bytes_after_last_gc; - - // Non-humongous old generation allocations since the last gc. - size_t _allocated_bytes_since_last_gc; - // Humongous allocations during last mutator period. - size_t _allocated_humongous_bytes_since_last_gc; + // Non-humongous old generation allocations since the last STW pause. + size_t _allocated_non_humongous_bytes_since_last_pause; + // Humongous allocations during last allocation interval. + size_t _allocated_humongous_bytes_since_last_pause; public: G1OldGenAllocationTracker(); - void add_allocated_bytes_since_last_gc(size_t bytes) { _allocated_bytes_since_last_gc += bytes; } - void add_allocated_humongous_bytes_since_last_gc(size_t bytes) { _allocated_humongous_bytes_since_last_gc += bytes; } - - // Record a humongous allocation in a collection pause. This allocation - // is accounted to the previous mutator period. - void record_collection_pause_humongous_allocation(size_t bytes) { - _humongous_bytes_after_last_gc += bytes; + void add_allocated_non_humongous_bytes(size_t bytes) { + _allocated_non_humongous_bytes_since_last_pause += bytes; + } + void add_allocated_humongous_bytes(size_t bytes) { + _allocated_humongous_bytes_since_last_pause += bytes; } - size_t last_period_old_gen_bytes() const { return _last_period_old_gen_bytes; } - size_t last_period_old_gen_growth() const { return _last_period_old_gen_growth; }; + // Record a humongous allocation in a collection pause. This allocation + // is accounted to the previous allocation interval. + void record_collection_pause_humongous_allocation(size_t bytes) { + _humongous_bytes_after_last_pause += bytes; + } - // Calculates and resets stats after a collection. - void reset_after_gc(size_t humongous_bytes_after_gc); + // Calculate and reset allocation statistics after a pause. + G1AllocationIntervalStats end_allocation_interval(size_t humongous_bytes_after_pause); }; -#endif // SHARE_VM_GC_G1_G1OLDGENALLOCATIONTRACKER_HPP +#endif // SHARE_GC_G1_G1OLDGENALLOCATIONTRACKER_HPP diff --git a/src/hotspot/share/gc/g1/g1Policy.cpp b/src/hotspot/share/gc/g1/g1Policy.cpp index 71a482e2505..35211938065 100644 --- a/src/hotspot/share/gc/g1/g1Policy.cpp +++ b/src/hotspot/share/gc/g1/g1Policy.cpp @@ -55,8 +55,9 @@ G1Policy::G1Policy(STWGCTimer* gc_timer) : _analytics(new G1Analytics(&_predictor)), _remset_tracker(), _mmu_tracker(new G1MMUTracker(GCPauseIntervalMillis / 1000.0, MaxGCPauseMillis / 1000.0)), + _concurrent_cycle_tracker(), _old_gen_alloc_tracker(), - _ihop_control(create_ihop_control(&_old_gen_alloc_tracker, &_predictor)), + _ihop_control(create_ihop_control(&_predictor)), _policy_counters(new GCPolicyCounters("GarbageFirst", 1, 2)), _cur_pause_start_sec(0.0), _young_list_desired_length(0), @@ -68,7 +69,6 @@ G1Policy::G1Policy(STWGCTimer* gc_timer) : _young_gen_sizer(), _free_regions_at_end_of_collection(0), _pending_cards_from_gc(0), - _concurrent_start_to_mixed(), _collection_set(nullptr), _g1h(nullptr), _phase_times_timer(gc_timer), @@ -589,6 +589,7 @@ void G1Policy::record_full_collection_end(size_t allocation_word_size) { // Consider this like a collection pause for the purposes of allocation // since last pause. double end_sec = os::elapsedTime(); + double start_time_sec = cur_pause_start_sec(); // "Nuke" the heuristics that control the young/mixed GC // transitions and make sure we start with young GCs after the Full GC. @@ -602,9 +603,6 @@ void G1Policy::record_full_collection_end(size_t allocation_word_size) { _survivor_surv_rate_group->reset(); update_young_length_bounds(); - _old_gen_alloc_tracker.reset_after_gc(_g1h->humongous_regions_count() * G1HeapRegion::GrainBytes); - - double start_time_sec = cur_pause_start_sec(); record_pause(Pause::Full, start_time_sec, end_sec); } @@ -954,8 +952,6 @@ G1CollectorState G1Policy::record_young_collection_end(bool concurrent_operation phase_times()->sum_thread_work_items(G1GCPhaseTimes::MergePSS, G1GCPhaseTimes::MergePSSToYoungGenCards)); } - record_pause(collector_state()->gc_pause_type(concurrent_operation_is_full_mark), start_time_sec, end_time_sec); - if (collector_state()->is_in_prepare_mixed_gc()) { assert(!collector_state()->is_in_concurrent_start_gc(), "The young GC before mixed is not allowed to be concurrent start GC"); @@ -988,23 +984,27 @@ G1CollectorState G1Policy::record_young_collection_end(bool concurrent_operation _free_regions_at_end_of_collection = _g1h->num_free_regions(); - _old_gen_alloc_tracker.reset_after_gc(_g1h->humongous_regions_count() * G1HeapRegion::GrainBytes); + Pause this_pause = collector_state()->gc_pause_type(concurrent_operation_is_full_mark); + size_t humongous_allocation_bytes = G1CollectedHeap::is_humongous(allocation_word_size) ? + G1CollectedHeap::allocation_used_bytes(allocation_word_size) : 0; + + record_pause(this_pause, start_time_sec, end_time_sec, humongous_allocation_bytes); // Do not update dynamic IHOP due to G1 periodic collection as it is highly likely // that in this case we are not running in a "normal" operating mode. if (_g1h->gc_cause() != GCCause::_g1_periodic_collection) { update_young_length_bounds(); + // Take snapshots of these values here as update_ihop_prediction + // may complete the concurrent cycle and reset the values. + size_t non_humongous_allocation = _concurrent_cycle_tracker.non_humongous_allocated_bytes(); + size_t peak_extra_humongous_occupancy = _concurrent_cycle_tracker.peak_extra_humongous_occupancy_bytes(); + if (update_ihop_prediction(app_time_ms / 1000.0, is_young_only_pause)) { - _ihop_control->report_statistics(_g1h->gc_tracer_stw(), _g1h->non_young_occupancy_after_allocation(allocation_word_size)); + _ihop_control->report_statistics(_g1h->gc_tracer_stw(), + _g1h->non_young_occupancy_after_allocation(allocation_word_size), + non_humongous_allocation, + peak_extra_humongous_occupancy); } - } else { - // Any garbage collection triggered as periodic collection resets the time-to-mixed - // measurement. Periodic collection typically means that the application is "inactive", i.e. - // the marking threads may have received an uncharacteristic amount of cpu time - // for completing the marking, i.e. are faster than expected. - // This skews the predicted marking length towards smaller values which might cause - // the mark start being too late. - abort_time_to_mixed_tracking(); } // Note that _mmu_tracker->max_gc_time() returns the time in seconds. @@ -1032,10 +1032,8 @@ G1CollectorState G1Policy::record_young_collection_end(bool concurrent_operation return next_state; } -G1IHOPControl* G1Policy::create_ihop_control(const G1OldGenAllocationTracker* old_gen_alloc_tracker, - const G1Predictions* predictor) { +G1IHOPControl* G1Policy::create_ihop_control(const G1Predictions* predictor) { return new G1IHOPControl(G1IHOP, - old_gen_alloc_tracker, G1UseAdaptiveIHOP, predictor, G1ReservePercent, @@ -1052,29 +1050,31 @@ bool G1Policy::update_ihop_prediction(double mutator_time_s, double const min_valid_time = 1e-6; bool report = false; + if (!this_gc_was_young_only && _concurrent_cycle_tracker.has_completed_cycle()) { + G1ConcurrentCycleStats cycle_stats = _concurrent_cycle_tracker.get_and_reset_cycle_stats(); - if (!this_gc_was_young_only && _concurrent_start_to_mixed.has_result()) { - double marking_to_mixed_time = _concurrent_start_to_mixed.get_and_reset_last_marking_time(); - assert(marking_to_mixed_time > 0.0, - "Concurrent start to mixed time must be larger than zero but is %.3f", - marking_to_mixed_time); - if (marking_to_mixed_time > min_valid_time) { - _ihop_control->add_marking_start_to_mixed_length(marking_to_mixed_time); + double concurrent_cycle_duration_s = cycle_stats._cycle_duration_s; + assert(concurrent_cycle_duration_s > 0.0, + "Time for Concurrent Start GC to the first Mixed GC must be larger than zero but is %.3f", + concurrent_cycle_duration_s); + if (concurrent_cycle_duration_s > min_valid_time) { + _ihop_control->record_concurrent_cycle(concurrent_cycle_duration_s, + cycle_stats._non_humongous_allocated_bytes, + cycle_stats._peak_extra_humongous_occupancy_bytes); report = true; } } - // As an approximation for the young gc promotion rates during marking we use - // all of them. In many applications there are only a few if any young gcs during - // marking, which makes any prediction useless. This increases the accuracy of the - // prediction. + // The second clause prevents skewing the IHOP prediction with (typically) degenerate + // back-to-back young-gen-size samples. if (this_gc_was_young_only && mutator_time_s > min_valid_time) { // IHOP control wants to know the expected young gen length if it were not // restrained by the heap reserve. Using the actual length would make the // prediction too small and the limit the young gen every time we get to the // predicted target occupancy. size_t young_gen_size = young_list_desired_length() * G1HeapRegion::GrainBytes; - _ihop_control->update_allocation_info(mutator_time_s, young_gen_size); + + _ihop_control->record_expected_young_gen_size(young_gen_size); report = true; } @@ -1311,11 +1311,11 @@ void G1Policy::decide_on_concurrent_start_pause() { // Force concurrent start. collector_state()->set_in_concurrent_start_gc(); // We might have ended up coming here about to start a mixed phase with a collection set - // active. The following remark might change the change the "evacuation efficiency" of + // active. The following remark might change the "evacuation efficiency" of // the regions in this set, leading to failing asserts later. // Since the concurrent cycle will recreate the collection set anyway, simply drop it here. abandon_collection_set_candidates(); - abort_time_to_mixed_tracking(); + abort_concurrent_cycle_tracking(); log_debug(gc, ergo)("Initiate concurrent cycle (%s requested concurrent cycle)", requester_for_mixed_abort(cause)); } else { @@ -1355,7 +1355,7 @@ void G1Policy::record_concurrent_mark_cleanup_end(bool has_rebuilt_remembered_se } if (!mixed_gc_pending) { - abort_time_to_mixed_tracking(); + abort_concurrent_cycle_tracking(); log_debug(gc, ergo)("request young-only gcs (candidate old regions not available)"); } if (mixed_gc_pending) { @@ -1390,7 +1390,8 @@ void G1Policy::update_gc_pause_time_ratios(Pause gc_type, double start_time_sec, void G1Policy::record_pause(Pause gc_type, double start, - double end) { + double end, + size_t humongous_allocation_bytes) { // Manage the MMU tracker. For some reason it ignores Full GCs. if (gc_type != Pause::Full) { _mmu_tracker->add_pause(start, end); @@ -1398,49 +1399,26 @@ void G1Policy::record_pause(Pause gc_type, update_gc_pause_time_ratios(gc_type, start, end); - update_time_to_mixed_tracking(gc_type, start, end); + size_t humongous_bytes = _g1h->humongous_regions_count() * G1HeapRegion::GrainBytes; + G1AllocationIntervalStats alloc_interval_stats = _old_gen_alloc_tracker.end_allocation_interval(humongous_bytes); + bool is_periodic_gc = _g1h->gc_cause() == GCCause::_g1_periodic_collection; + + if (humongous_allocation_bytes > 0) { + // Record the humongous allocation that triggered the GC and attribute it to + // the ending allocation interval. We do this eagerly, before we know whether + // the post GC allocation succeeds, to keep the common case simple. In the + // rare case where this allocation fails, we over-account; this only + // affects the stored IHOP sample if the current GC is the first Mixed GC. + alloc_interval_stats.record_humongous_allocation(humongous_allocation_bytes); + } + _concurrent_cycle_tracker.record_allocation_interval(gc_type, is_periodic_gc, start, end, alloc_interval_stats); double elapsed_gc_cpu_time = _analytics->gc_cpu_time_ms(); _analytics->set_gc_cpu_time_at_pause_end_ms(elapsed_gc_cpu_time); } -void G1Policy::update_time_to_mixed_tracking(Pause gc_type, - double start, - double end) { - // Manage the mutator time tracking from concurrent start to first mixed gc. - switch (gc_type) { - case Pause::Full: - abort_time_to_mixed_tracking(); - break; - case Pause::Cleanup: - case Pause::Remark: - case Pause::Normal: - case Pause::PrepareMixed: - _concurrent_start_to_mixed.add_pause(end - start); - break; - case Pause::ConcurrentStartFull: - // Do not track time-to-mixed time for periodic collections as they are likely - // to be not representative to regular operation as the mutators are idle at - // that time. Also only track full concurrent mark cycles. - if (_g1h->gc_cause() != GCCause::_g1_periodic_collection) { - _concurrent_start_to_mixed.record_concurrent_start_end(end); - } - break; - case Pause::ConcurrentStartUndo: - assert(_g1h->gc_cause() == GCCause::_g1_humongous_allocation, - "GC cause must be humongous allocation but is %d", - _g1h->gc_cause()); - break; - case Pause::Mixed: - _concurrent_start_to_mixed.record_mixed_gc_start(start); - break; - default: - ShouldNotReachHere(); - } -} - -void G1Policy::abort_time_to_mixed_tracking() { - _concurrent_start_to_mixed.reset(); +void G1Policy::abort_concurrent_cycle_tracking() { + _concurrent_cycle_tracker.abort_cycle(); } bool G1Policy::next_gc_should_be_mixed() const { diff --git a/src/hotspot/share/gc/g1/g1Policy.hpp b/src/hotspot/share/gc/g1/g1Policy.hpp index 0a472dd0527..3cfd54c8c94 100644 --- a/src/hotspot/share/gc/g1/g1Policy.hpp +++ b/src/hotspot/share/gc/g1/g1Policy.hpp @@ -26,7 +26,7 @@ #define SHARE_GC_G1_G1POLICY_HPP #include "gc/g1/g1CollectorState.hpp" -#include "gc/g1/g1ConcurrentStartToMixedTimeTracker.hpp" +#include "gc/g1/g1ConcurrentCycleTracker.hpp" #include "gc/g1/g1GCPhaseTimes.hpp" #include "gc/g1/g1HeapRegionAttr.hpp" #include "gc/g1/g1MMUTracker.hpp" @@ -58,8 +58,7 @@ class STWGCTimer; class G1Policy: public CHeapObj { using Pause = G1CollectorState::Pause; - static G1IHOPControl* create_ihop_control(const G1OldGenAllocationTracker* old_gen_alloc_tracker, - const G1Predictions* predictor); + static G1IHOPControl* create_ihop_control(const G1Predictions* predictor); // Update the IHOP control with the necessary statistics. Returns true if there // has been a significant update to the prediction. bool update_ihop_prediction(double mutator_time_s, @@ -70,8 +69,9 @@ class G1Policy: public CHeapObj { G1RemSetTrackingPolicy _remset_tracker; G1MMUTracker* _mmu_tracker; + G1ConcurrentCycleTracker _concurrent_cycle_tracker; // Tracking the allocation in the old generation between - // two GCs. + // two pauses. G1OldGenAllocationTracker _old_gen_alloc_tracker; G1IHOPControl* _ihop_control; @@ -114,8 +114,6 @@ class G1Policy: public CHeapObj { // garbage collection or the most recent refinement sweep. size_t _to_collection_set_cards; - G1ConcurrentStartToMixedTimeTracker _concurrent_start_to_mixed; - bool should_update_surv_rate_group_predictors(); double pending_cards_processing_time() const; @@ -264,17 +262,16 @@ public: private: void abandon_collection_set_candidates(); - // Manage time-to-mixed tracking. - void update_time_to_mixed_tracking(Pause gc_type, double start, double end); // Record the given STW pause with the given start and end times (in s). void record_pause(Pause gc_type, double start, - double end); + double end, + size_t humongous_allocation_bytes = 0); void update_gc_pause_time_ratios(Pause gc_type, double start_sec, double end_sec); // Indicate that we aborted marking before doing any mixed GCs. - void abort_time_to_mixed_tracking(); + void abort_concurrent_cycle_tracking(); public: diff --git a/src/hotspot/share/gc/g1/g1Trace.cpp b/src/hotspot/share/gc/g1/g1Trace.cpp index d6eadda5d50..bcc0941dc62 100644 --- a/src/hotspot/share/gc/g1/g1Trace.cpp +++ b/src/hotspot/share/gc/g1/g1Trace.cpp @@ -97,31 +97,31 @@ void G1NewTracer::report_evacuation_statistics(const G1EvacSummary& young_summar } void G1NewTracer::report_basic_ihop_statistics(size_t threshold, - size_t target_ccupancy, - size_t non_young_occupancy, - size_t last_allocation_size, - double last_allocation_duration, - double last_marking_length) { + size_t target_occupancy, + size_t non_young_occupancy) { send_basic_ihop_statistics(threshold, - target_ccupancy, - non_young_occupancy, - last_allocation_size, - last_allocation_duration, - last_marking_length); + target_occupancy, + non_young_occupancy); } void G1NewTracer::report_adaptive_ihop_statistics(size_t threshold, size_t internal_target_occupancy, size_t current_occupancy, size_t additional_buffer_size, - double predicted_allocation_rate, + size_t non_humongous_allocation, + size_t peak_extra_humongous_occupancy, + double predicted_old_non_hum_alloc_rate, + size_t predicted_peak_extra_humongous_occupancy, double predicted_marking_length, bool prediction_active) { send_adaptive_ihop_statistics(threshold, internal_target_occupancy, current_occupancy, additional_buffer_size, - predicted_allocation_rate, + non_humongous_allocation, + peak_extra_humongous_occupancy, + predicted_old_non_hum_alloc_rate, + predicted_peak_extra_humongous_occupancy, predicted_marking_length, prediction_active); } @@ -206,10 +206,7 @@ void G1NewTracer::send_old_evacuation_statistics(const G1EvacSummary& summary) c void G1NewTracer::send_basic_ihop_statistics(size_t threshold, size_t target_occupancy, - size_t non_young_occupancy, - size_t last_allocation_size, - double last_allocation_duration, - double last_marking_length) { + size_t non_young_occupancy) { EventG1BasicIHOP evt; if (evt.should_commit()) { evt.set_gcId(GCId::current()); @@ -217,10 +214,6 @@ void G1NewTracer::send_basic_ihop_statistics(size_t threshold, evt.set_targetOccupancy(target_occupancy); evt.set_thresholdPercentage(target_occupancy > 0 ? ((double)threshold / target_occupancy) : 0.0); evt.set_currentOccupancy(non_young_occupancy); - evt.set_recentMutatorAllocationSize(last_allocation_size); - evt.set_recentMutatorDuration(last_allocation_duration * MILLIUNITS); - evt.set_recentAllocationRate(last_allocation_duration != 0.0 ? last_allocation_size / last_allocation_duration : 0.0); - evt.set_lastMarkingDuration(last_marking_length * MILLIUNITS); evt.commit(); } } @@ -229,7 +222,10 @@ void G1NewTracer::send_adaptive_ihop_statistics(size_t threshold, size_t internal_target_occupancy, size_t current_occupancy, size_t additional_buffer_size, - double predicted_allocation_rate, + size_t non_humongous_allocation, + size_t peak_extra_humongous_occupancy, + double predicted_old_non_hum_alloc_rate, + size_t predicted_peak_extra_humongous_occupancy, double predicted_marking_length, bool prediction_active) { EventG1AdaptiveIHOP evt; @@ -240,7 +236,10 @@ void G1NewTracer::send_adaptive_ihop_statistics(size_t threshold, evt.set_ihopTargetOccupancy(internal_target_occupancy); evt.set_currentOccupancy(current_occupancy); evt.set_additionalBufferSize(additional_buffer_size); - evt.set_predictedAllocationRate(predicted_allocation_rate); + evt.set_nonHumongousAllocation(non_humongous_allocation); + evt.set_peakExtraHumongousOccupancy(peak_extra_humongous_occupancy); + evt.set_predictedNonHumongousAllocation(predicted_old_non_hum_alloc_rate); + evt.set_predictedPeakExtraHumongousOccupancy(predicted_peak_extra_humongous_occupancy); evt.set_predictedMarkingDuration(predicted_marking_length * MILLIUNITS); evt.set_predictionActive(prediction_active); evt.commit(); diff --git a/src/hotspot/share/gc/g1/g1Trace.hpp b/src/hotspot/share/gc/g1/g1Trace.hpp index bfcc275d2ca..c5d099d5807 100644 --- a/src/hotspot/share/gc/g1/g1Trace.hpp +++ b/src/hotspot/share/gc/g1/g1Trace.hpp @@ -52,15 +52,15 @@ public: void report_basic_ihop_statistics(size_t threshold, size_t target_occupancy, - size_t current_occupancy, - size_t last_allocation_size, - double last_allocation_duration, - double last_marking_length); + size_t current_occupancy); void report_adaptive_ihop_statistics(size_t threshold, size_t internal_target_occupancy, size_t current_occupancy, size_t additional_buffer_size, - double predicted_allocation_rate, + size_t non_humongous_allocation, + size_t peak_extra_humongous_occupancy, + double predicted_old_gen_non_humongous_allocation_rate, + size_t predicted_peak_extra_humongous_occupancy, double predicted_marking_length, bool prediction_active); private: @@ -73,15 +73,16 @@ private: void send_basic_ihop_statistics(size_t threshold, size_t target_occupancy, - size_t non_young_occupancy, - size_t last_allocation_size, - double last_allocation_duration, - double last_marking_length); + size_t non_young_occupancy); + void send_adaptive_ihop_statistics(size_t threshold, size_t internal_target_occupancy, size_t non_young_occupancy, size_t additional_buffer_size, - double predicted_allocation_rate, + size_t non_humongous_allocation, + size_t peak_extra_humongous_occupancy, + double predicted_old_gen_non_humongous_allocation_rate, + size_t predicted_peak_extra_humongous_occupancy, double predicted_marking_length, bool prediction_active); }; diff --git a/src/hotspot/share/gc/g1/g1YoungGCPostEvacuateTasks.cpp b/src/hotspot/share/gc/g1/g1YoungGCPostEvacuateTasks.cpp index d0c843aa5d6..bf4a6cca81d 100644 --- a/src/hotspot/share/gc/g1/g1YoungGCPostEvacuateTasks.cpp +++ b/src/hotspot/share/gc/g1/g1YoungGCPostEvacuateTasks.cpp @@ -542,7 +542,7 @@ public: class FreeCSetStats { size_t _before_used_bytes; // Usage in regions successfully evacuate size_t _after_used_bytes; // Usage in regions failing evacuation - size_t _bytes_allocated_in_old_since_last_gc; // Size of young regions turned into old + size_t _bytes_allocated_in_old_since_last_pause; // Size of young regions turned into old size_t _failure_used_words; // Live size in failed regions size_t _failure_waste_words; // Wasted size in failed regions uint _regions_freed; // Number of regions freed @@ -551,7 +551,7 @@ public: FreeCSetStats() : _before_used_bytes(0), _after_used_bytes(0), - _bytes_allocated_in_old_since_last_gc(0), + _bytes_allocated_in_old_since_last_pause(0), _failure_used_words(0), _failure_waste_words(0), _regions_freed(0) { } @@ -560,7 +560,7 @@ public: assert(other != nullptr, "invariant"); _before_used_bytes += other->_before_used_bytes; _after_used_bytes += other->_after_used_bytes; - _bytes_allocated_in_old_since_last_gc += other->_bytes_allocated_in_old_since_last_gc; + _bytes_allocated_in_old_since_last_pause += other->_bytes_allocated_in_old_since_last_pause; _failure_used_words += other->_failure_used_words; _failure_waste_words += other->_failure_waste_words; _regions_freed += other->_regions_freed; @@ -575,7 +575,7 @@ public: g1h->alloc_buffer_stats(G1HeapRegionAttr::Old)->add_failure_used_and_waste(_failure_used_words, _failure_waste_words); G1Policy *policy = g1h->policy(); - policy->old_gen_alloc_tracker()->add_allocated_bytes_since_last_gc(_bytes_allocated_in_old_since_last_gc); + policy->old_gen_alloc_tracker()->add_allocated_non_humongous_bytes(_bytes_allocated_in_old_since_last_pause); policy->cset_regions_freed(); } @@ -592,7 +592,7 @@ public: // additional allocation: both the objects still in the region and the // ones already moved are accounted for elsewhere. if (r->is_young()) { - _bytes_allocated_in_old_since_last_gc += G1HeapRegion::GrainBytes; + _bytes_allocated_in_old_since_last_pause += G1HeapRegion::GrainBytes; } } diff --git a/src/hotspot/share/jfr/metadata/metadata.xml b/src/hotspot/share/jfr/metadata/metadata.xml index 09d9e0ccabf..22bd21c8ff4 100644 --- a/src/hotspot/share/jfr/metadata/metadata.xml +++ b/src/hotspot/share/jfr/metadata/metadata.xml @@ -1,7 +1,7 @@