| /* | |
| * 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{}; | |
| } |