blob: b3f96da1cce471750b2f677053ff75d1dfab0437 [file]
/*
* Copyright (c) 2014, 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/vmClasses.hpp"
#include "classfile/vmSymbols.hpp"
#include "gc/shared/gcLogPrecious.hpp"
#include "gc/shared/oopStorage.hpp"
#include "gc/shared/oopStorageSet.hpp"
#include "gc/shared/stringdedup/stringDedup.hpp"
#include "gc/shared/stringdedup/stringDedupConfig.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 "gc/shared/stringdedup/stringDedupThread.hpp"
#include "logging/log.hpp"
#include "memory/allocation.hpp"
#include "memory/iterator.hpp"
#include "oops/access.inline.hpp"
#include "oops/instanceKlass.hpp"
#include "oops/markWord.hpp"
#include "oops/oopsHierarchy.hpp"
#include "runtime/globals.hpp"
#include "runtime/mutexLocker.hpp"
#include "runtime/orderAccess.hpp"
#include "runtime/safepoint.hpp"
#include "utilities/debug.hpp"
#include "utilities/globalDefinitions.hpp"
bool StringDedup::_initialized = false;
bool StringDedup::_enabled = false;
StringDedup::Processor* StringDedup::_processor = nullptr;
StringDedup::Stat StringDedup::_cur_stat{};
StringDedup::Stat StringDedup::_total_stat{};
// Configuration for predicates used to decide whether to deduplicate.
// The initial values are suitable for deduplication being disabled.
const Klass* StringDedup::_string_klass_or_null = nullptr; // No klass will match.
static_assert(markWord::max_age < UINT_MAX, "assumption");
uint StringDedup::_enabled_age_threshold = UINT_MAX; // Age never equals max.
uint StringDedup::_enabled_age_limit = 0; // Age is never less than zero.
bool StringDedup::ergo_initialize() {
return Config::ergo_initialize();
}
void StringDedup::initialize() {
assert(!_initialized, "already initialized");
// Unconditionally create the oopstorage objects, to simplify usage
// elsewhere. OopStorageSet and clients don't support optional oopstorage
// objects.
Table::initialize_storage();
Processor::initialize_storage();
if (UseStringDeduplication) {
Config::initialize();
// Verify klass comparison with _string_klass_or_null is sufficient
// to determine whether dedup is enabled and the object is a String.
assert(vmClasses::String_klass()->is_final(), "precondition");
_string_klass_or_null = vmClasses::String_klass();
_enabled_age_threshold = Config::age_threshold();
_enabled_age_limit = Config::age_threshold();
Table::initialize();
Processor::initialize();
// Don't create the thread yet. JavaThreads need to be created later.
_enabled = true;
log_info_p(stringdedup, init)("String Deduplication is enabled");
}
_initialized = true;
}
void StringDedup::start() {
assert(is_enabled(), "precondition");
StringDedupThread::initialize();
}
void StringDedup::forbid_deduplication(oop java_string) {
assert(is_enabled(), "precondition");
if (java_lang_String::deduplication_forbidden(java_string)) {
// DCLP - we don't want a caller's access to the value array to float
// before the check; string dedup could change the value and another
// thread could set the flag, and this thread uses a stale value.
OrderAccess::acquire();
} else {
MutexLocker ml(StringDedupIntern_lock, Mutex::_no_safepoint_check_flag);
java_lang_String::set_deduplication_forbidden(java_string);
}
}
void StringDedup::notify_intern(oop java_string) {
assert(is_enabled(), "precondition");
// A String that is interned in the StringTable must not later have its
// underlying byte array changed, so mark it as not deduplicatable. But we
// can still add the byte array to the dedup table for sharing, so add the
// string to the pending requests. Triggering request processing is left
// to the next GC.
forbid_deduplication(java_string);
StorageUse* requests = Processor::storage_for_requests();
oop* ref = requests->storage()->allocate();
if (ref != nullptr) {
NativeAccess<ON_PHANTOM_OOP_REF>::oop_store(ref, java_string);
log_trace(stringdedup)("StringDedup::deduplicate");
}
requests->relinquish();
}
StringDedup::Requests::Requests() :
_storage_for_requests(nullptr), _buffer(nullptr), _index(0), _refill_failed(false)
{}
StringDedup::Requests::~Requests() {
flush();
}
bool StringDedup::Requests::refill_buffer() {
assert(_index == 0, "precondition");
// Treat out of memory failure as sticky; don't keep retrying.
if (_refill_failed) return false;
// Lazy initialization of the requests object. It can be common for
// many of the marking threads to not encounter any candidates.
const size_t buffer_size = OopStorage::bulk_allocate_limit;
if (_buffer == nullptr) {
// Lazily allocate a buffer to hold pre-allocated storage entries.
_buffer = NEW_C_HEAP_ARRAY_RETURN_NULL(oop*, buffer_size, mtStringDedup);
if (_buffer == nullptr) {
log_debug(stringdedup)("request failed to allocate buffer");
_refill_failed = true;
return false;
}
// Lazily obtain the storage object to use for requests.
assert(_storage_for_requests == nullptr, "invariant");
_storage_for_requests = Processor::storage_for_requests();
}
assert(_storage_for_requests != nullptr, "invariant");
// Bulk pre-allocate some storage entries to satisfy this and future
// requests. This amortizes the cost of allocating entries over
// multiple requests, and reduces contention on the storage object.
_index = _storage_for_requests->storage()->allocate(_buffer, buffer_size);
if (_index == 0) {
log_debug(stringdedup)("request failed to allocate oopstorage entries");
flush();
_refill_failed = true;
return false;
}
return true;
}
void StringDedup::Requests::add(oop java_string) {
assert(is_enabled(), "StringDedup not enabled");
if ((_index == 0) && !refill_buffer()) return;
// Store the string in the next pre-allocated storage entry.
oop* ref = _buffer[--_index];
NativeAccess<ON_PHANTOM_OOP_REF>::oop_store(ref, java_string);
log_trace(stringdedup)("request");
}
void StringDedup::Requests::flush() {
if (_buffer != nullptr) {
if (_index > 0) {
assert(_storage_for_requests != nullptr, "invariant");
_storage_for_requests->storage()->release(_buffer, _index);
}
FREE_C_HEAP_ARRAY(oop*, _buffer);
_buffer = nullptr;
}
if (_storage_for_requests != nullptr) {
_storage_for_requests->relinquish();
_storage_for_requests = nullptr;
}
_index = 0;
_refill_failed = false;
}
void StringDedup::verify() {
assert_at_safepoint();
if (is_enabled()) {
Table::verify();
}
}