blob: ecd9168cd65d37fdccbc97e36074338f6b901b39 [file] [log] [blame]
/*
* Copyright (c) 2021, 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 "utilities/macros.hpp"
#if INCLUDE_MANAGEMENT
#include "classfile/classLoaderDataGraph.inline.hpp"
#include "classfile/javaClasses.inline.hpp"
#include "classfile/symbolTable.hpp"
#include "memory/resourceArea.hpp"
#include "logging/log.hpp"
#include "oops/instanceKlass.inline.hpp"
#include "runtime/atomic.hpp"
#include "runtime/fieldDescriptor.inline.hpp"
#include "runtime/interfaceSupport.inline.hpp"
#include "runtime/javaThread.hpp"
#include "runtime/mutexLocker.hpp"
#include "runtime/synchronizer.hpp"
#include "services/finalizerService.hpp"
#include "utilities/concurrentHashTableTasks.inline.hpp"
#include "utilities/debug.hpp"
static const char* allocate(oop string) {
char* str = nullptr;
const typeArrayOop value = java_lang_String::value(string);
if (value != nullptr) {
const int length = java_lang_String::utf8_length(string, value);
str = NEW_C_HEAP_ARRAY(char, length + 1, mtServiceability);
java_lang_String::as_utf8_string(string, value, str, length + 1);
}
return str;
}
static int compute_field_offset(const Klass* klass, const char* field_name, const char* field_signature) {
assert(klass != nullptr, "invariant");
Symbol* const name = SymbolTable::new_symbol(field_name);
assert(name != nullptr, "invariant");
Symbol* const signature = SymbolTable::new_symbol(field_signature);
assert(signature != nullptr, "invariant");
assert(klass->is_instance_klass(), "invariant");
fieldDescriptor fd;
InstanceKlass::cast(klass)->find_field(name, signature, false, &fd);
return fd.offset();
}
static const char* location_no_frag_string(oop codesource) {
assert(codesource != nullptr, "invariant");
static int loc_no_frag_offset = compute_field_offset(codesource->klass(), "locationNoFragString", "Ljava/lang/String;");
oop string = codesource->obj_field(loc_no_frag_offset);
return string != nullptr ? allocate(string) : nullptr;
}
static oop codesource(oop pd) {
assert(pd != nullptr, "invariant");
static int codesource_offset = compute_field_offset(pd->klass(), "codesource", "Ljava/security/CodeSource;");
return pd->obj_field(codesource_offset);
}
static const char* get_codesource(const InstanceKlass* ik) {
assert(ik != nullptr, "invariant");
oop pd = java_lang_Class::protection_domain(ik->java_mirror());
if (pd == nullptr) {
return nullptr;
}
oop cs = codesource(pd);
return cs != nullptr ? location_no_frag_string(cs) : nullptr;
}
FinalizerEntry::FinalizerEntry(const InstanceKlass* ik) :
_ik(ik),
_codesource(get_codesource(ik)),
_objects_on_heap(0),
_total_finalizers_run(0) {}
FinalizerEntry::~FinalizerEntry() {
FREE_C_HEAP_ARRAY(char, _codesource);
}
const InstanceKlass* FinalizerEntry::klass() const {
return _ik;
}
const char* FinalizerEntry::codesource() const {
return _codesource;
}
uintptr_t FinalizerEntry::objects_on_heap() const {
return Atomic::load(&_objects_on_heap);
}
uintptr_t FinalizerEntry::total_finalizers_run() const {
return Atomic::load(&_total_finalizers_run);
}
void FinalizerEntry::on_register() {
Atomic::inc(&_objects_on_heap, memory_order_relaxed);
}
void FinalizerEntry::on_complete() {
Atomic::inc(&_total_finalizers_run, memory_order_relaxed);
Atomic::dec(&_objects_on_heap, memory_order_relaxed);
}
static inline uintx hash_function(const InstanceKlass* ik) {
assert(ik != nullptr, "invariant");
return primitive_hash(ik);
}
static inline uintx hash_function(const FinalizerEntry* fe) {
return hash_function(fe->klass());
}
class FinalizerEntryLookup : StackObj {
private:
const InstanceKlass* const _ik;
public:
FinalizerEntryLookup(const InstanceKlass* ik) : _ik(ik) {}
uintx get_hash() const { return hash_function(_ik); }
bool equals(FinalizerEntry** value) {
assert(value != nullptr, "invariant");
assert(*value != nullptr, "invariant");
return (*value)->klass() == _ik;
}
bool is_dead(FinalizerEntry** value) {
return false;
}
};
class FinalizerTableConfig : public AllStatic {
public:
typedef FinalizerEntry* Value; // value of the Node in the hashtable
static uintx get_hash(Value const& value, bool* is_dead) {
return hash_function(value);
}
static void* allocate_node(void* context, size_t size, Value const& value) {
return AllocateHeap(size, mtServiceability);
}
static void free_node(void* context, void* memory, Value const& value) {
FreeHeap(memory);
}
};
typedef ConcurrentHashTable<FinalizerTableConfig, mtServiceability> FinalizerHashtable;
static FinalizerHashtable* _table = nullptr;
static const size_t DEFAULT_TABLE_SIZE = 2048;
// 2^24 is max size, like StringTable.
static const size_t MAX_SIZE = 24;
static volatile bool _has_work = false;
class FinalizerEntryLookupResult {
private:
FinalizerEntry* _result;
public:
FinalizerEntryLookupResult() : _result(nullptr) {}
void operator()(FinalizerEntry* node) {
assert(node != nullptr, "invariant");
_result = node;
}
FinalizerEntry* result() const { return _result; }
};
class FinalizerEntryLookupGet {
private:
FinalizerEntry* _result;
public:
FinalizerEntryLookupGet() : _result(nullptr) {}
void operator()(FinalizerEntry** node) {
assert(node != nullptr, "invariant");
_result = *node;
}
FinalizerEntry* result() const { return _result; }
};
static inline void set_has_work(bool value) {
Atomic::store(&_has_work, value);
}
static inline bool has_work() {
return Atomic::load(&_has_work);
}
static void request_resize() {
if (!has_work()) {
MutexLocker ml(Service_lock, Mutex::_no_safepoint_check_flag);
if (!has_work()) {
set_has_work(true);
Service_lock->notify_all();
}
}
}
static FinalizerEntry* add_to_table_if_needed(const InstanceKlass* ik, Thread* thread) {
FinalizerEntryLookup lookup(ik);
FinalizerEntry* entry = nullptr;
bool grow_hint = false;
do {
// We have looked up the entry once, proceed with insertion.
entry = new FinalizerEntry(ik);
if (_table->insert(thread, lookup, entry, &grow_hint)) {
break;
}
// In case another thread did a concurrent add, return value already in the table.
// This could fail if the entry got deleted concurrently, so loop back until success.
FinalizerEntryLookupGet felg;
if (_table->get(thread, lookup, felg, &grow_hint)) {
entry = felg.result();
break;
}
} while (true);
if (grow_hint) {
request_resize();
}
assert(entry != nullptr, "invariant");
return entry;
}
static void do_table_concurrent_work(JavaThread* jt) {
if (!_table->is_max_size_reached()) {
FinalizerHashtable::GrowTask gt(_table);
if (!gt.prepare(jt)) {
return;
}
while (gt.do_task(jt)) {
gt.pause(jt);
{
ThreadBlockInVM tbivm(jt);
}
gt.cont(jt);
}
gt.done(jt);
}
set_has_work(false);
}
bool FinalizerService::has_work() {
return ::has_work();
}
void FinalizerService::do_concurrent_work(JavaThread* service_thread) {
assert(service_thread != nullptr, "invariant");
assert(has_work(), "invariant");
do_table_concurrent_work(service_thread);
}
void FinalizerService::init() {
assert(_table == nullptr, "invariant");
const size_t start_size_log_2 = ceil_log2(DEFAULT_TABLE_SIZE);
_table = new FinalizerHashtable(start_size_log_2, MAX_SIZE, FinalizerHashtable::DEFAULT_GROW_HINT);
}
static FinalizerEntry* lookup_entry(const InstanceKlass* ik, Thread* thread) {
FinalizerEntryLookup lookup(ik);
FinalizerEntryLookupGet felg;
_table->get(thread, lookup, felg);
return felg.result();
}
const FinalizerEntry* FinalizerService::lookup(const InstanceKlass* ik, Thread* thread) {
assert(ik != nullptr, "invariant");
assert(thread != nullptr, "invariant");
assert(ik->has_finalizer(), "invariant");
return lookup_entry(ik, thread);
}
// Add if not exist.
static FinalizerEntry* get_entry(const InstanceKlass* ik, Thread* thread) {
assert(ik != nullptr, "invariant");
assert(ik->has_finalizer(), "invariant");
FinalizerEntry* const entry = lookup_entry(ik, thread);
return entry != nullptr ? entry : add_to_table_if_needed(ik, thread);
}
static FinalizerEntry* get_entry(oop finalizee, Thread* thread) {
assert(finalizee != nullptr, "invariant");
assert(finalizee->is_instance(), "invariant");
return get_entry(InstanceKlass::cast(finalizee->klass()), thread);
}
static void log_registered(oop finalizee, Thread* thread) {
ResourceMark rm(thread);
const intptr_t identity_hash = ObjectSynchronizer::FastHashCode(thread, finalizee);
log_info(finalizer)("Registered object (" INTPTR_FORMAT ") of class %s as finalizable", identity_hash, finalizee->klass()->external_name());
}
void FinalizerService::on_register(oop finalizee, Thread* thread) {
FinalizerEntry* const fe = get_entry(finalizee, thread);
assert(fe != nullptr, "invariant");
fe->on_register();
if (log_is_enabled(Info, finalizer)) {
log_registered(finalizee, thread);
}
}
static void log_completed(oop finalizee, Thread* thread) {
ResourceMark rm(thread);
const intptr_t identity_hash = ObjectSynchronizer::FastHashCode(thread, finalizee);
log_info(finalizer)("Finalizer was run for object (" INTPTR_FORMAT ") of class %s", identity_hash, finalizee->klass()->external_name());
}
void FinalizerService::on_complete(oop finalizee, JavaThread* finalizer_thread) {
FinalizerEntry* const fe = get_entry(finalizee, finalizer_thread);
assert(fe != nullptr, "invariant");
fe->on_complete();
if (log_is_enabled(Info, finalizer)) {
log_completed(finalizee, finalizer_thread);
}
}
class FinalizerScan : public StackObj {
private:
FinalizerEntryClosure* _closure;
public:
FinalizerScan(FinalizerEntryClosure* closure) : _closure(closure) {}
bool operator()(FinalizerEntry** fe) {
return _closure->do_entry(*fe);
}
};
void FinalizerService::do_entries(FinalizerEntryClosure* closure, Thread* thread) {
assert(closure != nullptr, "invariant");
FinalizerScan scan(closure);
_table->do_scan(thread, scan);
}
static bool remove_entry(const InstanceKlass* ik) {
assert(ik != nullptr, "invariant");
FinalizerEntryLookup lookup(ik);
return _table->remove(Thread::current(), lookup);
}
static void on_unloading(Klass* klass) {
assert(klass != nullptr, "invariant");
if (!klass->is_instance_klass()) {
return;
}
const InstanceKlass* const ik = InstanceKlass::cast(klass);
if (ik->has_finalizer()) {
remove_entry(ik);
}
}
void FinalizerService::purge_unloaded() {
assert_locked_or_safepoint(ClassLoaderDataGraph_lock);
ClassLoaderDataGraph::classes_unloading_do(&on_unloading);
}
#endif // INCLUDE_MANAGEMENT