| /* |
| * Copyright (c) 2021, 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. |
| * |
| */ |
| |
| #include "classfile/javaClasses.inline.hpp" |
| #include "classfile/stringTable.hpp" |
| #include "gc/shared/oopStorage.hpp" |
| #include "gc/shared/oopStorageParState.inline.hpp" |
| #include "gc/shared/oopStorageSet.hpp" |
| #include "gc/shared/stringdedup/stringDedup.hpp" |
| #include "gc/shared/stringdedup/stringDedupProcessor.hpp" |
| #include "gc/shared/stringdedup/stringDedupStat.hpp" |
| #include "gc/shared/stringdedup/stringDedupStorageUse.hpp" |
| #include "gc/shared/stringdedup/stringDedupTable.hpp" |
| #include "logging/log.hpp" |
| #include "memory/iterator.hpp" |
| #include "nmt/memTag.hpp" |
| #include "oops/access.inline.hpp" |
| #include "runtime/atomic.hpp" |
| #include "runtime/cpuTimeCounters.hpp" |
| #include "runtime/interfaceSupport.inline.hpp" |
| #include "runtime/mutexLocker.hpp" |
| #include "utilities/debug.hpp" |
| #include "utilities/globalCounter.hpp" |
| #include "utilities/globalDefinitions.hpp" |
| |
| OopStorage* StringDedup::Processor::_storages[2] = {}; |
| |
| StringDedup::StorageUse* volatile StringDedup::Processor::_storage_for_requests = nullptr; |
| StringDedup::StorageUse* StringDedup::Processor::_storage_for_processing = nullptr; |
| |
| void StringDedup::Processor::initialize_storage() { |
| assert(_storages[0] == nullptr, "storage already created"); |
| assert(_storages[1] == nullptr, "storage already created"); |
| assert(_storage_for_requests == nullptr, "storage already created"); |
| assert(_storage_for_processing == nullptr, "storage already created"); |
| _storages[0] = OopStorageSet::create_weak("StringDedup Requests0 Weak", mtStringDedup); |
| _storages[1] = OopStorageSet::create_weak("StringDedup Requests1 Weak", mtStringDedup); |
| _storage_for_requests = new StorageUse(_storages[0]); |
| _storage_for_processing = new StorageUse(_storages[1]); |
| } |
| |
| StringDedup::Processor::Processor() : _thread(nullptr) {} |
| |
| void StringDedup::Processor::initialize() { |
| _processor = new Processor(); |
| CPUTimeCounters::create_counter(CPUTimeGroups::CPUTimeType::conc_dedup); |
| } |
| |
| void StringDedup::Processor::wait_for_requests() const { |
| assert(Thread::current() == _thread, "precondition"); |
| // Wait for the current request storage object to be non-empty, or for the |
| // table to need cleanup. The num-dead notification from the Table notifies |
| // the monitor. |
| { |
| ThreadBlockInVM tbivm(_thread); |
| MonitorLocker ml(StringDedup_lock, Mutex::_no_safepoint_check_flag); |
| OopStorage* storage = Atomic::load(&_storage_for_requests)->storage(); |
| while ((storage->allocation_count() == 0) && |
| !Table::is_dead_entry_removal_needed()) { |
| ml.wait(); |
| } |
| } |
| // Swap the request and processing storage objects. |
| log_trace(stringdedup)("swapping request storages"); |
| _storage_for_processing = Atomic::xchg(&_storage_for_requests, _storage_for_processing); |
| GlobalCounter::write_synchronize(); |
| // Wait for the now current processing storage object to no longer be used |
| // by an in-progress GC. Again here, the num-dead notification from the |
| // Table notifies the monitor. |
| { |
| log_trace(stringdedup)("waiting for storage to process"); |
| ThreadBlockInVM tbivm(_thread); |
| MonitorLocker ml(StringDedup_lock, Mutex::_no_safepoint_check_flag); |
| while (_storage_for_processing->is_used_acquire()) { |
| ml.wait(); |
| } |
| } |
| } |
| |
| StringDedup::StorageUse* StringDedup::Processor::storage_for_requests() { |
| return StorageUse::obtain(&_storage_for_requests); |
| } |
| |
| void StringDedup::Processor::yield() const { |
| assert(Thread::current() == _thread, "precondition"); |
| ThreadBlockInVM tbivm(_thread); |
| } |
| |
| void StringDedup::Processor::cleanup_table(bool grow_only, bool force) const { |
| if (Table::cleanup_start_if_needed(grow_only, force)) { |
| do { |
| yield(); |
| } while (Table::cleanup_step()); |
| Table::cleanup_end(); |
| } |
| } |
| |
| class StringDedup::Processor::ProcessRequest final : public OopClosure { |
| OopStorage* _storage; |
| size_t _release_index; |
| oop* _bulk_release[OopStorage::bulk_allocate_limit]; |
| |
| void release_ref(oop* ref) { |
| assert(_release_index < ARRAY_SIZE(_bulk_release), "invariant"); |
| NativeAccess<ON_PHANTOM_OOP_REF>::oop_store(ref, nullptr); |
| _bulk_release[_release_index++] = ref; |
| if (_release_index == ARRAY_SIZE(_bulk_release)) { |
| _storage->release(_bulk_release, _release_index); |
| _release_index = 0; |
| } |
| } |
| |
| public: |
| ProcessRequest(OopStorage* storage) : |
| _storage(storage), |
| _release_index(0), |
| _bulk_release() |
| {} |
| |
| ~ProcessRequest() { |
| _storage->release(_bulk_release, _release_index); |
| } |
| |
| virtual void do_oop(narrowOop*) { ShouldNotReachHere(); } |
| |
| virtual void do_oop(oop* ref) { |
| _processor->yield(); |
| oop java_string = NativeAccess<ON_PHANTOM_OOP_REF>::oop_load(ref); |
| release_ref(ref); |
| // Dedup java_string, after checking for various reasons to skip it. |
| if (java_string == nullptr) { |
| // String became unreachable before we got a chance to process it. |
| _cur_stat.inc_skipped_dead(); |
| } else if (java_lang_String::value(java_string) == nullptr) { |
| // Request during String construction, before its value array has |
| // been initialized. |
| _cur_stat.inc_skipped_incomplete(); |
| } else { |
| Table::deduplicate(java_string); |
| if (Table::is_grow_needed()) { |
| _cur_stat.report_process_pause(); |
| _processor->cleanup_table(true /* grow_only */, false /* force */); |
| _cur_stat.report_process_resume(); |
| } |
| } |
| } |
| }; |
| |
| void StringDedup::Processor::process_requests() const { |
| _cur_stat.report_process_start(); |
| OopStorage::ParState<true, false> par_state{_storage_for_processing->storage(), 1}; |
| ProcessRequest processor{_storage_for_processing->storage()}; |
| par_state.oops_do(&processor); |
| _cur_stat.report_process_end(); |
| } |
| |
| void StringDedup::Processor::run(JavaThread* thread) { |
| assert(thread == Thread::current(), "precondition"); |
| _thread = thread; |
| log_debug(stringdedup)("Starting string deduplication thread"); |
| while (true) { |
| _cur_stat.report_idle_start(); |
| wait_for_requests(); |
| _cur_stat.report_idle_end(); |
| _cur_stat.report_active_start(); |
| process_requests(); |
| cleanup_table(false /* grow_only */, StringDeduplicationResizeALot /* force */); |
| _cur_stat.report_active_end(); |
| log_statistics(); |
| if (UsePerfData && os::is_thread_cpu_time_supported()) { |
| ThreadTotalCPUTimeClosure tttc(CPUTimeGroups::CPUTimeType::conc_dedup); |
| tttc.do_thread(thread); |
| } |
| } |
| } |
| |
| void StringDedup::Processor::log_statistics() { |
| _total_stat.add(&_cur_stat); |
| Stat::log_summary(&_cur_stat, &_total_stat); |
| if (log_is_enabled(Debug, stringdedup)) { |
| _cur_stat.log_statistics(false); |
| _total_stat.log_statistics(true); |
| Table::log_statistics(); |
| } |
| _cur_stat = Stat{}; |
| } |