8305566: Change StringDedup thread to derive from JavaThread
Reviewed-by: stefank, cjplummer, tschatzl
This commit is contained in:
parent
f3c90f0445
commit
d3abfec8b7
src
hotspot/share
gc
shared/stringdedup
stringDedup.cppstringDedup.hppstringDedupProcessor.cppstringDedupProcessor.hppstringDedupStat.cppstringDedupStat.hppstringDedupTable.cppstringDedupTable.hppstringDedupThread.cppstringDedupThread.hpp
shenandoah
runtime
jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2014, 2021, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2014, 2023, 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
|
||||
@ -35,6 +35,7 @@
|
||||
#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"
|
||||
@ -43,7 +44,6 @@
|
||||
#include "oops/markWord.hpp"
|
||||
#include "oops/oopsHierarchy.hpp"
|
||||
#include "runtime/globals.hpp"
|
||||
#include "runtime/javaThread.hpp"
|
||||
#include "runtime/mutexLocker.hpp"
|
||||
#include "runtime/orderAccess.hpp"
|
||||
#include "runtime/safepoint.hpp"
|
||||
@ -57,9 +57,12 @@ StringDedup::Processor* StringDedup::_processor = nullptr;
|
||||
StringDedup::Stat StringDedup::_cur_stat{};
|
||||
StringDedup::Stat StringDedup::_total_stat{};
|
||||
|
||||
const Klass* StringDedup::_string_klass_or_null = nullptr;
|
||||
uint StringDedup::_enabled_age_threshold = 0;
|
||||
uint StringDedup::_enabled_age_limit = 0;
|
||||
// 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();
|
||||
@ -82,30 +85,16 @@ void StringDedup::initialize() {
|
||||
_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");
|
||||
} else {
|
||||
// No klass will ever match.
|
||||
_string_klass_or_null = nullptr;
|
||||
// Age can never equal UINT_MAX.
|
||||
static_assert(markWord::max_age < UINT_MAX, "assumption");
|
||||
_enabled_age_threshold = UINT_MAX;
|
||||
// Age can never be less than zero.
|
||||
_enabled_age_limit = 0;
|
||||
}
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
void StringDedup::stop() {
|
||||
void StringDedup::start() {
|
||||
assert(is_enabled(), "precondition");
|
||||
assert(_processor != nullptr, "invariant");
|
||||
_processor->stop();
|
||||
}
|
||||
|
||||
void StringDedup::threads_do(ThreadClosure* tc) {
|
||||
assert(is_enabled(), "precondition");
|
||||
assert(_processor != nullptr, "invariant");
|
||||
tc->do_thread(_processor);
|
||||
StringDedupThread::initialize();
|
||||
}
|
||||
|
||||
void StringDedup::forbid_deduplication(oop java_string) {
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2014, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2014, 2023, 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
|
||||
@ -93,6 +93,10 @@
|
||||
// but before weak reference processing, the GC should flush or delete all
|
||||
// of its Requests objects.
|
||||
//
|
||||
// The deduplication thread is a daemon JavaThread. No thread visitor is
|
||||
// needed, as it is handled via the normal JavaThread visiting mechanism.
|
||||
// Similarly, there is no need for a stop() function.
|
||||
//
|
||||
// For additional information on string deduplication, please see JEP 192,
|
||||
// https://openjdk.org/jeps/192
|
||||
|
||||
@ -102,6 +106,7 @@
|
||||
#include "utilities/globalDefinitions.hpp"
|
||||
|
||||
class Klass;
|
||||
class StringDedupThread;
|
||||
class ThreadClosure;
|
||||
|
||||
// The StringDedup class provides the API for the deduplication mechanism.
|
||||
@ -110,6 +115,8 @@ class ThreadClosure;
|
||||
// feature. Other functions in the StringDedup class are called where
|
||||
// needed, without requiring GC-specific code.
|
||||
class StringDedup : public AllStatic {
|
||||
friend class StringDedupThread;
|
||||
|
||||
class Config;
|
||||
class Processor;
|
||||
class Stat;
|
||||
@ -140,13 +147,9 @@ public:
|
||||
// Returns true if string deduplication is enabled.
|
||||
static bool is_enabled() { return _enabled; }
|
||||
|
||||
// Stop the deduplication processor thread.
|
||||
// Create and start the deduplication processor thread.
|
||||
// precondition: is_enabled()
|
||||
static void stop();
|
||||
|
||||
// Visit the deduplication processor thread.
|
||||
// precondition: is_enabled()
|
||||
static void threads_do(ThreadClosure* tc);
|
||||
static void start();
|
||||
|
||||
// Marks the String as not being subject to deduplication. This can be
|
||||
// used to prevent deduplication of Strings whose value array must remain
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2021, 2023, 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
|
||||
@ -33,21 +33,17 @@
|
||||
#include "gc/shared/stringdedup/stringDedupStat.hpp"
|
||||
#include "gc/shared/stringdedup/stringDedupStorageUse.hpp"
|
||||
#include "gc/shared/stringdedup/stringDedupTable.hpp"
|
||||
#include "gc/shared/suspendibleThreadSet.hpp"
|
||||
#include "logging/log.hpp"
|
||||
#include "memory/allocation.hpp"
|
||||
#include "memory/iterator.hpp"
|
||||
#include "oops/access.inline.hpp"
|
||||
#include "runtime/atomic.hpp"
|
||||
#include "runtime/interfaceSupport.inline.hpp"
|
||||
#include "runtime/mutexLocker.hpp"
|
||||
#include "utilities/debug.hpp"
|
||||
#include "utilities/globalCounter.hpp"
|
||||
#include "utilities/globalDefinitions.hpp"
|
||||
|
||||
StringDedup::Processor::Processor() : ConcurrentGCThread() {
|
||||
set_name("StringDedupProcessor");
|
||||
}
|
||||
|
||||
OopStorage* StringDedup::Processor::_storages[2] = {};
|
||||
|
||||
StringDedup::StorageUse* volatile StringDedup::Processor::_storage_for_requests = nullptr;
|
||||
@ -64,71 +60,63 @@ void StringDedup::Processor::initialize_storage() {
|
||||
_storage_for_processing = new StorageUse(_storages[1]);
|
||||
}
|
||||
|
||||
StringDedup::Processor::Processor() : _thread(nullptr) {}
|
||||
|
||||
void StringDedup::Processor::initialize() {
|
||||
_processor = new Processor();
|
||||
_processor->create_and_start();
|
||||
}
|
||||
|
||||
bool StringDedup::Processor::wait_for_requests() const {
|
||||
// Wait for the current request storage object to be non-empty. The
|
||||
// num-dead notification from the Table notifies the monitor.
|
||||
if (!should_terminate()) {
|
||||
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 (!should_terminate() &&
|
||||
(storage->allocation_count() == 0) &&
|
||||
while ((storage->allocation_count() == 0) &&
|
||||
!Table::is_dead_entry_removal_needed()) {
|
||||
ml.wait();
|
||||
}
|
||||
}
|
||||
// Swap the request and processing storage objects.
|
||||
if (!should_terminate()) {
|
||||
log_trace(stringdedup)("swapping request storages");
|
||||
_storage_for_processing = Atomic::xchg(&_storage_for_requests, _storage_for_processing);
|
||||
GlobalCounter::write_synchronize();
|
||||
}
|
||||
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.
|
||||
if (!should_terminate()) {
|
||||
{
|
||||
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() && !should_terminate()) {
|
||||
while (_storage_for_processing->is_used_acquire()) {
|
||||
ml.wait();
|
||||
}
|
||||
}
|
||||
return !should_terminate();
|
||||
}
|
||||
|
||||
StringDedup::StorageUse* StringDedup::Processor::storage_for_requests() {
|
||||
return StorageUse::obtain(&_storage_for_requests);
|
||||
}
|
||||
|
||||
bool StringDedup::Processor::yield_or_continue(SuspendibleThreadSetJoiner* joiner,
|
||||
Stat::Phase phase) const {
|
||||
if (joiner->should_yield()) {
|
||||
_cur_stat.block_phase(phase);
|
||||
joiner->yield();
|
||||
_cur_stat.unblock_phase();
|
||||
}
|
||||
return !should_terminate();
|
||||
void StringDedup::Processor::yield() const {
|
||||
assert(Thread::current() == _thread, "precondition");
|
||||
ThreadBlockInVM tbivm(_thread);
|
||||
}
|
||||
|
||||
void StringDedup::Processor::cleanup_table(SuspendibleThreadSetJoiner* joiner,
|
||||
bool grow_only,
|
||||
bool force) const {
|
||||
void StringDedup::Processor::cleanup_table(bool grow_only, bool force) const {
|
||||
if (Table::cleanup_start_if_needed(grow_only, force)) {
|
||||
Stat::Phase phase = Table::cleanup_phase();
|
||||
while (yield_or_continue(joiner, phase)) {
|
||||
if (!Table::cleanup_step()) break;
|
||||
}
|
||||
do {
|
||||
yield();
|
||||
} while (Table::cleanup_step());
|
||||
Table::cleanup_end();
|
||||
}
|
||||
}
|
||||
|
||||
class StringDedup::Processor::ProcessRequest final : public OopClosure {
|
||||
OopStorage* _storage;
|
||||
SuspendibleThreadSetJoiner* _joiner;
|
||||
size_t _release_index;
|
||||
oop* _bulk_release[OopStorage::bulk_allocate_limit];
|
||||
|
||||
@ -143,9 +131,8 @@ class StringDedup::Processor::ProcessRequest final : public OopClosure {
|
||||
}
|
||||
|
||||
public:
|
||||
ProcessRequest(OopStorage* storage, SuspendibleThreadSetJoiner* joiner) :
|
||||
ProcessRequest(OopStorage* storage) :
|
||||
_storage(storage),
|
||||
_joiner(joiner),
|
||||
_release_index(0),
|
||||
_bulk_release()
|
||||
{}
|
||||
@ -157,64 +144,52 @@ public:
|
||||
virtual void do_oop(narrowOop*) { ShouldNotReachHere(); }
|
||||
|
||||
virtual void do_oop(oop* ref) {
|
||||
if (_processor->yield_or_continue(_joiner, Stat::Phase::process)) {
|
||||
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(_joiner, true /* grow_only */, false /* force */);
|
||||
_cur_stat.report_process_resume();
|
||||
}
|
||||
_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(SuspendibleThreadSetJoiner* joiner) const {
|
||||
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(), joiner};
|
||||
ProcessRequest processor{_storage_for_processing->storage()};
|
||||
par_state.oops_do(&processor);
|
||||
_cur_stat.report_process_end();
|
||||
}
|
||||
|
||||
void StringDedup::Processor::run_service() {
|
||||
while (!should_terminate()) {
|
||||
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();
|
||||
if (!wait_for_requests()) {
|
||||
assert(should_terminate(), "invariant");
|
||||
break;
|
||||
}
|
||||
SuspendibleThreadSetJoiner sts_joiner{};
|
||||
if (should_terminate()) break;
|
||||
wait_for_requests();
|
||||
_cur_stat.report_idle_end();
|
||||
_cur_stat.report_concurrent_start();
|
||||
_cur_stat.report_process_start();
|
||||
process_requests(&sts_joiner);
|
||||
if (should_terminate()) break;
|
||||
_cur_stat.report_process_end();
|
||||
cleanup_table(&sts_joiner,
|
||||
false /* grow_only */,
|
||||
StringDeduplicationResizeALot /* force */);
|
||||
if (should_terminate()) break;
|
||||
_cur_stat.report_concurrent_end();
|
||||
_cur_stat.report_active_start();
|
||||
process_requests();
|
||||
cleanup_table(false /* grow_only */, StringDeduplicationResizeALot /* force */);
|
||||
_cur_stat.report_active_end();
|
||||
log_statistics();
|
||||
}
|
||||
}
|
||||
|
||||
void StringDedup::Processor::stop_service() {
|
||||
MonitorLocker ml(StringDedup_lock, Mutex::_no_safepoint_check_flag);
|
||||
ml.notify_all();
|
||||
}
|
||||
|
||||
void StringDedup::Processor::log_statistics() {
|
||||
_total_stat.add(&_cur_stat);
|
||||
Stat::log_summary(&_cur_stat, &_total_stat);
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2021, 2023, 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,26 +25,23 @@
|
||||
#ifndef SHARE_GC_SHARED_STRINGDEDUP_STRINGDEDUPPROCESSOR_HPP
|
||||
#define SHARE_GC_SHARED_STRINGDEDUP_STRINGDEDUPPROCESSOR_HPP
|
||||
|
||||
#include "gc/shared/concurrentGCThread.hpp"
|
||||
#include "gc/shared/stringdedup/stringDedup.hpp"
|
||||
#include "gc/shared/stringdedup/stringDedupStat.hpp"
|
||||
#include "memory/allocation.hpp"
|
||||
#include "utilities/macros.hpp"
|
||||
|
||||
class JavaThread;
|
||||
class OopStorage;
|
||||
class SuspendibleThreadSetJoiner;
|
||||
|
||||
// Thread class for string deduplication. There is only one instance of
|
||||
// this class. This thread processes deduplication requests. It also
|
||||
// manages the deduplication table, performing resize and cleanup operations
|
||||
// as needed. This includes managing the OopStorage objects used to hold
|
||||
// requests.
|
||||
// This class performs string deduplication. There is only one instance of
|
||||
// this class. It processes deduplication requests. It also manages the
|
||||
// deduplication table, performing resize and cleanup operations as needed.
|
||||
// This includes managing the OopStorage objects used to hold requests.
|
||||
//
|
||||
// This thread uses the SuspendibleThreadSet mechanism to take part in the
|
||||
// safepoint protocol. It checks for safepoints between processing requests
|
||||
// in order to minimize safepoint latency. The Table provides incremental
|
||||
// operations for resizing and for removing dead entries, so this thread can
|
||||
// perform safepoint checks between steps in those operations.
|
||||
class StringDedup::Processor : public ConcurrentGCThread {
|
||||
// Processing periodically checks for and yields at safepoints. Processing of
|
||||
// requests is performed in incremental chunks. The Table provides
|
||||
// incremental operations for resizing and for removing dead entries, so
|
||||
// safepoint checks can be performed between steps in those operations.
|
||||
class StringDedup::Processor : public CHeapObj<mtGC> {
|
||||
Processor();
|
||||
~Processor() = default;
|
||||
|
||||
@ -54,27 +51,32 @@ class StringDedup::Processor : public ConcurrentGCThread {
|
||||
static StorageUse* volatile _storage_for_requests;
|
||||
static StorageUse* _storage_for_processing;
|
||||
|
||||
// Returns !should_terminate();
|
||||
bool wait_for_requests() const;
|
||||
JavaThread* _thread;
|
||||
|
||||
// Yield if requested. Returns !should_terminate() after possible yield.
|
||||
bool yield_or_continue(SuspendibleThreadSetJoiner* joiner, Stat::Phase phase) const;
|
||||
// Wait until there are requests to be processed. The storage for requests
|
||||
// and storage for processing are swapped; the former requests storage
|
||||
// becomes the current processing storage, and vice versa.
|
||||
// precondition: the processing storage is empty.
|
||||
void wait_for_requests() const;
|
||||
|
||||
// Yield if requested.
|
||||
void yield() const;
|
||||
|
||||
class ProcessRequest;
|
||||
void process_requests(SuspendibleThreadSetJoiner* joiner) const;
|
||||
void cleanup_table(SuspendibleThreadSetJoiner* joiner, bool grow_only, bool force) const;
|
||||
void process_requests() const;
|
||||
void cleanup_table(bool grow_only, bool force) const;
|
||||
|
||||
void log_statistics();
|
||||
|
||||
protected:
|
||||
virtual void run_service();
|
||||
virtual void stop_service();
|
||||
|
||||
public:
|
||||
static void initialize();
|
||||
|
||||
static void initialize_storage();
|
||||
static StorageUse* storage_for_requests();
|
||||
|
||||
// Use thread as the deduplication thread.
|
||||
// precondition: thread == Thread::current()
|
||||
void run(JavaThread* thread);
|
||||
};
|
||||
|
||||
#endif // SHARE_GC_SHARED_STRINGDEDUP_STRINGDEDUPPROCESSOR_HPP
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2014, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2014, 2023, 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
|
||||
@ -40,21 +40,19 @@ StringDedup::Stat::Stat() :
|
||||
_skipped_dead(0),
|
||||
_skipped_incomplete(0),
|
||||
_skipped_shared(0),
|
||||
_concurrent(0),
|
||||
_active(0),
|
||||
_idle(0),
|
||||
_process(0),
|
||||
_resize_table(0),
|
||||
_cleanup_table(0),
|
||||
_block(0),
|
||||
_concurrent_start(),
|
||||
_concurrent_elapsed(),
|
||||
_active_start(),
|
||||
_active_elapsed(),
|
||||
_phase_start(),
|
||||
_idle_elapsed(),
|
||||
_process_elapsed(),
|
||||
_resize_table_elapsed(),
|
||||
_cleanup_table_elapsed(),
|
||||
_block_elapsed() {
|
||||
}
|
||||
_cleanup_table_elapsed()
|
||||
{}
|
||||
|
||||
void StringDedup::Stat::add(const Stat* const stat) {
|
||||
_inspected += stat->_inspected;
|
||||
@ -69,18 +67,16 @@ void StringDedup::Stat::add(const Stat* const stat) {
|
||||
_skipped_dead += stat->_skipped_dead;
|
||||
_skipped_incomplete += stat->_skipped_incomplete;
|
||||
_skipped_shared += stat->_skipped_shared;
|
||||
_concurrent += stat->_concurrent;
|
||||
_active += stat->_active;
|
||||
_idle += stat->_idle;
|
||||
_process += stat->_process;
|
||||
_resize_table += stat->_resize_table;
|
||||
_cleanup_table += stat->_cleanup_table;
|
||||
_block += stat->_block;
|
||||
_concurrent_elapsed += stat->_concurrent_elapsed;
|
||||
_active_elapsed += stat->_active_elapsed;
|
||||
_idle_elapsed += stat->_idle_elapsed;
|
||||
_process_elapsed += stat->_process_elapsed;
|
||||
_resize_table_elapsed += stat->_resize_table_elapsed;
|
||||
_cleanup_table_elapsed += stat->_cleanup_table_elapsed;
|
||||
_block_elapsed += stat->_block_elapsed;
|
||||
}
|
||||
|
||||
// Support for log output formatting
|
||||
@ -113,19 +109,19 @@ void StringDedup::Stat::log_summary(const Stat* last_stat, const Stat* total_sta
|
||||
last_stat->_deduped, STRDEDUP_BYTES_PARAM(last_stat->_deduped_bytes),
|
||||
total_deduped_bytes_percent,
|
||||
strdedup_elapsed_param_ms(last_stat->_process_elapsed),
|
||||
strdedup_elapsed_param_ms(last_stat->_concurrent_elapsed));
|
||||
strdedup_elapsed_param_ms(last_stat->_active_elapsed));
|
||||
}
|
||||
|
||||
void StringDedup::Stat::report_concurrent_start() {
|
||||
log_debug(stringdedup, phases, start)("Concurrent start");
|
||||
_concurrent_start = Ticks::now();
|
||||
_concurrent++;
|
||||
void StringDedup::Stat::report_active_start() {
|
||||
log_debug(stringdedup, phases, start)("Active start");
|
||||
_active_start = Ticks::now();
|
||||
_active++;
|
||||
}
|
||||
|
||||
void StringDedup::Stat::report_concurrent_end() {
|
||||
_concurrent_elapsed += (Ticks::now() - _concurrent_start);
|
||||
log_debug(stringdedup, phases)("Concurrent end: " STRDEDUP_ELAPSED_FORMAT_MS,
|
||||
strdedup_elapsed_param_ms(_concurrent_elapsed));
|
||||
void StringDedup::Stat::report_active_end() {
|
||||
_active_elapsed += (Ticks::now() - _active_start);
|
||||
log_debug(stringdedup, phases)("Active end: " STRDEDUP_ELAPSED_FORMAT_MS,
|
||||
strdedup_elapsed_param_ms(_active_elapsed));
|
||||
}
|
||||
|
||||
void StringDedup::Stat::report_phase_start(const char* phase) {
|
||||
@ -194,38 +190,13 @@ void StringDedup::Stat::report_cleanup_table_end() {
|
||||
report_phase_end("Cleanup Table", &_cleanup_table_elapsed);
|
||||
}
|
||||
|
||||
Tickspan* StringDedup::Stat::elapsed_for_phase(Phase phase) {
|
||||
switch (phase) {
|
||||
case Phase::process: return &_process_elapsed;
|
||||
case Phase::resize_table: return &_resize_table_elapsed;
|
||||
case Phase::cleanup_table: return &_cleanup_table_elapsed;
|
||||
}
|
||||
ShouldNotReachHere();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void StringDedup::Stat::block_phase(Phase phase) {
|
||||
Ticks now = Ticks::now();
|
||||
*elapsed_for_phase(phase) += now - _phase_start;
|
||||
_phase_start = now;
|
||||
_block++;
|
||||
}
|
||||
|
||||
void StringDedup::Stat::unblock_phase() {
|
||||
Ticks now = Ticks::now();
|
||||
_block_elapsed += now - _phase_start;
|
||||
_phase_start = now;
|
||||
}
|
||||
|
||||
void StringDedup::Stat::log_times(const char* prefix) const {
|
||||
log_debug(stringdedup)(
|
||||
" %s Process: %zu/" STRDEDUP_ELAPSED_FORMAT_MS
|
||||
", Idle: %zu/" STRDEDUP_ELAPSED_FORMAT_MS
|
||||
", Blocked: %zu/" STRDEDUP_ELAPSED_FORMAT_MS,
|
||||
", Idle: %zu/" STRDEDUP_ELAPSED_FORMAT_MS,
|
||||
prefix,
|
||||
_process, strdedup_elapsed_param_ms(_process_elapsed),
|
||||
_idle, strdedup_elapsed_param_ms(_idle_elapsed),
|
||||
_block, strdedup_elapsed_param_ms(_block_elapsed));
|
||||
_idle, strdedup_elapsed_param_ms(_idle_elapsed));
|
||||
if (_resize_table > 0) {
|
||||
log_debug(stringdedup)(
|
||||
" %s Resize Table: %zu/" STRDEDUP_ELAPSED_FORMAT_MS,
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2014, 2021, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2014, 2023, 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
|
||||
@ -34,14 +34,6 @@
|
||||
// Operation counters are updated when deduplicating a string.
|
||||
// Phase timing information is collected by the processing thread.
|
||||
class StringDedup::Stat {
|
||||
public:
|
||||
// Only phases that can be blocked, so excluding "idle".
|
||||
enum class Phase {
|
||||
process,
|
||||
resize_table,
|
||||
cleanup_table
|
||||
};
|
||||
|
||||
private:
|
||||
// Counters
|
||||
size_t _inspected;
|
||||
@ -58,26 +50,25 @@ private:
|
||||
size_t _skipped_shared;
|
||||
|
||||
// Phase counters for deduplication thread
|
||||
size_t _concurrent;
|
||||
size_t _active;
|
||||
size_t _idle;
|
||||
size_t _process;
|
||||
size_t _resize_table;
|
||||
size_t _cleanup_table;
|
||||
size_t _block;
|
||||
|
||||
// Time spent by the deduplication thread in different phases
|
||||
Ticks _concurrent_start;
|
||||
Tickspan _concurrent_elapsed;
|
||||
Ticks _active_start;
|
||||
Tickspan _active_elapsed;
|
||||
Ticks _phase_start;
|
||||
// These phases are disjoint, so share _phase_start.
|
||||
// Some of these overlap with active, hence need _active_start.
|
||||
Tickspan _idle_elapsed;
|
||||
Tickspan _process_elapsed;
|
||||
Tickspan _resize_table_elapsed;
|
||||
Tickspan _cleanup_table_elapsed;
|
||||
Tickspan _block_elapsed;
|
||||
|
||||
void report_phase_start(const char* phase);
|
||||
void report_phase_end(const char* phase, Tickspan* elapsed);
|
||||
Tickspan* elapsed_for_phase(Phase phase);
|
||||
|
||||
void log_times(const char* prefix) const;
|
||||
|
||||
@ -153,11 +144,8 @@ public:
|
||||
void report_cleanup_table_start(size_t entry_count, size_t dead_count);
|
||||
void report_cleanup_table_end();
|
||||
|
||||
void report_concurrent_start();
|
||||
void report_concurrent_end();
|
||||
|
||||
void block_phase(Phase phase);
|
||||
void unblock_phase();
|
||||
void report_active_start();
|
||||
void report_active_end();
|
||||
|
||||
void add(const Stat* const stat);
|
||||
void log_statistics(bool total) const;
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2014, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2014, 2023, 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
|
||||
@ -283,7 +283,6 @@ public:
|
||||
virtual bool step() = 0;
|
||||
virtual TableValue find(typeArrayOop obj, uint hash_code) const = 0;
|
||||
virtual void report_end() const = 0;
|
||||
virtual Stat::Phase phase() const = 0;
|
||||
virtual void verify() const = 0;
|
||||
};
|
||||
|
||||
@ -321,10 +320,6 @@ public:
|
||||
_cur_stat.report_resize_table_end();
|
||||
}
|
||||
|
||||
virtual Stat::Phase phase() const {
|
||||
return Stat::Phase::resize_table;
|
||||
}
|
||||
|
||||
virtual void verify() const;
|
||||
};
|
||||
|
||||
@ -389,10 +384,6 @@ public:
|
||||
_cur_stat.report_cleanup_table_end();
|
||||
}
|
||||
|
||||
virtual Stat::Phase phase() const {
|
||||
return Stat::Phase::cleanup_table;
|
||||
}
|
||||
|
||||
virtual void verify() const {} // Nothing to do here.
|
||||
};
|
||||
|
||||
@ -718,11 +709,6 @@ void StringDedup::Table::cleanup_end() {
|
||||
Atomic::store(&_dead_state, DeadState::wait2);
|
||||
}
|
||||
|
||||
StringDedup::Stat::Phase StringDedup::Table::cleanup_phase() {
|
||||
assert(_cleanup_state != nullptr, "precondition");
|
||||
return _cleanup_state->phase();
|
||||
}
|
||||
|
||||
void StringDedup::Table::verify() {
|
||||
size_t total_count = 0;
|
||||
for (size_t i = 0; i < _number_of_buckets; ++i) {
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2014, 2021, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2014, 2023, 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
|
||||
@ -138,10 +138,6 @@ public:
|
||||
// precondition: a cleanup is in progress.
|
||||
static void cleanup_end();
|
||||
|
||||
// Return the phase kind for the cleanup being performed.
|
||||
// precondition: a cleanup is in progress.
|
||||
static Stat::Phase cleanup_phase();
|
||||
|
||||
static void verify();
|
||||
static void log_statistics();
|
||||
};
|
||||
|
@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright (c) 2023, 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 "precompiled.hpp"
|
||||
#include "gc/shared/stringdedup/stringDedup.hpp"
|
||||
#include "gc/shared/stringdedup/stringDedupProcessor.hpp"
|
||||
#include "gc/shared/stringdedup/stringDedupThread.hpp"
|
||||
#include "runtime/handles.hpp"
|
||||
#include "runtime/os.hpp"
|
||||
#include "utilities/exceptions.hpp"
|
||||
|
||||
StringDedupThread::StringDedupThread() : JavaThread(thread_entry) {}
|
||||
|
||||
void StringDedupThread::initialize() {
|
||||
EXCEPTION_MARK;
|
||||
|
||||
const char* name = "StringDedupThread";
|
||||
Handle thread_oop = JavaThread::create_system_thread_object(name, CHECK);
|
||||
StringDedupThread* thread = new StringDedupThread();
|
||||
JavaThread::vm_exit_on_osthread_failure(thread);
|
||||
JavaThread::start_internal_daemon(THREAD, thread, thread_oop, NormPriority);
|
||||
}
|
||||
|
||||
void StringDedupThread::thread_entry(JavaThread* thread, TRAPS) {
|
||||
StringDedup::_processor->run(thread);
|
||||
}
|
||||
|
||||
bool StringDedupThread::is_hidden_from_external_view() const {
|
||||
return true;
|
||||
}
|
||||
|
@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright (c) 2023, 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_SHARED_STRINGDEDUP_STRINGDEDUPTHREAD_HPP
|
||||
#define SHARE_GC_SHARED_STRINGDEDUP_STRINGDEDUPTHREAD_HPP
|
||||
|
||||
#include "gc/shared/stringdedup/stringDedup.hpp"
|
||||
#include "runtime/javaThread.hpp"
|
||||
#include "utilities/exceptions.hpp"
|
||||
#include "utilities/macros.hpp"
|
||||
|
||||
// Thread class for string deduplication. There is only one instance of this
|
||||
// class. This class provides thread management. It uses the Processor
|
||||
// to perform most of the work.
|
||||
//
|
||||
// Unlike most of the classes in the stringdedup implementation, this class is
|
||||
// not an inner class of StringDedup. This is because we need a simple public
|
||||
// identifier for use by VMStructs.
|
||||
class StringDedupThread : public JavaThread {
|
||||
friend class VMStructs;
|
||||
|
||||
StringDedupThread();
|
||||
~StringDedupThread() = default;
|
||||
|
||||
NONCOPYABLE(StringDedupThread);
|
||||
|
||||
static void thread_entry(JavaThread* thread, TRAPS);
|
||||
|
||||
public:
|
||||
static void initialize();
|
||||
|
||||
bool is_hidden_from_external_view() const override;
|
||||
};
|
||||
|
||||
#endif // SHARE_GC_SHARED_STRINGDEDUP_STRINGDEDUPTHREAD_HPP
|
@ -58,7 +58,6 @@
|
||||
#include "gc/shenandoah/shenandoahParallelCleaning.inline.hpp"
|
||||
#include "gc/shenandoah/shenandoahReferenceProcessor.hpp"
|
||||
#include "gc/shenandoah/shenandoahRootProcessor.inline.hpp"
|
||||
#include "gc/shenandoah/shenandoahStringDedup.hpp"
|
||||
#include "gc/shenandoah/shenandoahSTWMark.hpp"
|
||||
#include "gc/shenandoah/shenandoahUtils.hpp"
|
||||
#include "gc/shenandoah/shenandoahVerifier.hpp"
|
||||
@ -1186,9 +1185,6 @@ void ShenandoahHeap::gc_threads_do(ThreadClosure* tcl) const {
|
||||
if (_safepoint_workers != nullptr) {
|
||||
_safepoint_workers->threads_do(tcl);
|
||||
}
|
||||
if (ShenandoahStringDedup::is_enabled()) {
|
||||
ShenandoahStringDedup::threads_do(tcl);
|
||||
}
|
||||
}
|
||||
|
||||
void ShenandoahHeap::print_tracing_info() const {
|
||||
@ -2193,13 +2189,13 @@ bool ShenandoahHeap::uncommit_bitmap_slice(ShenandoahHeapRegion *r) {
|
||||
}
|
||||
|
||||
void ShenandoahHeap::safepoint_synchronize_begin() {
|
||||
if (ShenandoahSuspendibleWorkers || UseStringDeduplication) {
|
||||
if (ShenandoahSuspendibleWorkers) {
|
||||
SuspendibleThreadSet::synchronize();
|
||||
}
|
||||
}
|
||||
|
||||
void ShenandoahHeap::safepoint_synchronize_end() {
|
||||
if (ShenandoahSuspendibleWorkers || UseStringDeduplication) {
|
||||
if (ShenandoahSuspendibleWorkers) {
|
||||
SuspendibleThreadSet::desynchronize();
|
||||
}
|
||||
}
|
||||
|
@ -479,11 +479,6 @@ void before_exit(JavaThread* thread, bool halt) {
|
||||
StatSampler::disengage();
|
||||
StatSampler::destroy();
|
||||
|
||||
// Shut down string deduplication if running.
|
||||
if (StringDedup::is_enabled()) {
|
||||
StringDedup::stop();
|
||||
}
|
||||
|
||||
// Stop concurrent GC threads
|
||||
Universe::heap()->stop();
|
||||
|
||||
|
@ -512,9 +512,7 @@ private:
|
||||
virtual bool is_Java_thread() const { return true; }
|
||||
virtual bool can_call_java() const { return true; }
|
||||
|
||||
virtual bool is_active_Java_thread() const {
|
||||
return on_thread_list() && !is_terminated();
|
||||
}
|
||||
virtual bool is_active_Java_thread() const;
|
||||
|
||||
// Thread oop. threadObj() can be null for initial JavaThread
|
||||
// (or for threads attached via JNI)
|
||||
|
@ -223,6 +223,10 @@ inline void JavaThread::set_terminated(TerminatedTypes t) {
|
||||
Atomic::release_store(&_terminated, t);
|
||||
}
|
||||
|
||||
inline bool JavaThread::is_active_Java_thread() const {
|
||||
return on_thread_list() && !is_terminated();
|
||||
}
|
||||
|
||||
// Allow tracking of class initialization monitor use
|
||||
inline void JavaThread::set_class_to_be_initialized(InstanceKlass* k) {
|
||||
assert((k == nullptr && _class_to_be_initialized != nullptr) ||
|
||||
|
@ -691,6 +691,11 @@ jint Threads::create_vm(JavaVMInitArgs* args, bool* canTryAgain) {
|
||||
}
|
||||
#endif
|
||||
|
||||
// Start string deduplication thread if requested.
|
||||
if (StringDedup::is_enabled()) {
|
||||
StringDedup::start();
|
||||
}
|
||||
|
||||
// Pre-initialize some JSR292 core classes to avoid deadlock during class loading.
|
||||
// It is done after compilers are initialized, because otherwise compilations of
|
||||
// signature polymorphic MH intrinsics can be missed
|
||||
@ -1267,9 +1272,6 @@ void Threads::print_on(outputStream* st, bool print_stacks,
|
||||
PrintOnClosure cl(st);
|
||||
cl.do_thread(VMThread::vm_thread());
|
||||
Universe::heap()->gc_threads_do(&cl);
|
||||
if (StringDedup::is_enabled()) {
|
||||
StringDedup::threads_do(&cl);
|
||||
}
|
||||
cl.do_thread(WatcherThread::watcher_thread());
|
||||
cl.do_thread(AsyncLogWriter::instance());
|
||||
|
||||
@ -1332,11 +1334,6 @@ void Threads::print_on_error(outputStream* st, Thread* current, char* buf,
|
||||
Universe::heap()->gc_threads_do(&print_closure);
|
||||
}
|
||||
|
||||
if (StringDedup::is_enabled()) {
|
||||
PrintOnErrorClosure print_closure(st, current, buf, buflen, &found_current);
|
||||
StringDedup::threads_do(&print_closure);
|
||||
}
|
||||
|
||||
if (!found_current) {
|
||||
st->cr();
|
||||
st->print("=>" PTR_FORMAT " (exited) ", p2i(current));
|
||||
|
@ -48,6 +48,7 @@
|
||||
#include "code/vmreg.hpp"
|
||||
#include "compiler/compileBroker.hpp"
|
||||
#include "compiler/oopMap.hpp"
|
||||
#include "gc/shared/stringdedup/stringDedupThread.hpp"
|
||||
#include "gc/shared/vmStructs_gc.hpp"
|
||||
#include "interpreter/bytecodes.hpp"
|
||||
#include "interpreter/interpreter.hpp"
|
||||
@ -1311,6 +1312,7 @@
|
||||
declare_type(ServiceThread, JavaThread) \
|
||||
declare_type(NotificationThread, JavaThread) \
|
||||
declare_type(CompilerThread, JavaThread) \
|
||||
declare_type(StringDedupThread, JavaThread) \
|
||||
declare_toplevel_type(OSThread) \
|
||||
declare_toplevel_type(JavaFrameAnchor) \
|
||||
\
|
||||
|
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (c) 2023, 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 sun.jvm.hotspot.runtime;
|
||||
|
||||
import sun.jvm.hotspot.debugger.Address;
|
||||
|
||||
public class StringDedupThread extends JavaThread {
|
||||
public StringDedupThread(Address addr) {
|
||||
super(addr);
|
||||
}
|
||||
|
||||
public boolean isJavaThread() { return false; }
|
||||
public boolean isHiddenFromExternalView() { return true; }
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2000, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2000, 2023, 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
|
||||
@ -156,6 +156,7 @@ public class Threads {
|
||||
virtualConstructor.addMapping("ServiceThread", ServiceThread.class);
|
||||
virtualConstructor.addMapping("MonitorDeflationThread", MonitorDeflationThread.class);
|
||||
virtualConstructor.addMapping("NotificationThread", NotificationThread.class);
|
||||
virtualConstructor.addMapping("StringDedupThread", StringDedupThread.class);
|
||||
}
|
||||
|
||||
public Threads() {
|
||||
|
Loading…
x
Reference in New Issue
Block a user