mirror of
https://github.com/openjdk/jdk.git
synced 2025-09-18 18:14:38 +02:00
8313678: SymbolTable can leak Symbols during cleanup
Reviewed-by: coleenp, dholmes, shade
This commit is contained in:
parent
f41c267f85
commit
4b2703ad39
11 changed files with 68 additions and 29 deletions
|
@ -229,11 +229,13 @@ public:
|
||||||
uintx get_hash() const {
|
uintx get_hash() const {
|
||||||
return _name->identity_hash();
|
return _name->identity_hash();
|
||||||
}
|
}
|
||||||
bool equals(DictionaryEntry** value, bool* is_dead) {
|
bool equals(DictionaryEntry** value) {
|
||||||
DictionaryEntry *entry = *value;
|
DictionaryEntry *entry = *value;
|
||||||
*is_dead = false;
|
|
||||||
return (entry->instance_klass()->name() == _name);
|
return (entry->instance_klass()->name() == _name);
|
||||||
}
|
}
|
||||||
|
bool is_dead(DictionaryEntry** value) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add a loaded class to the dictionary.
|
// Add a loaded class to the dictionary.
|
||||||
|
|
|
@ -176,11 +176,9 @@ class StringTableLookupJchar : StackObj {
|
||||||
uintx get_hash() const {
|
uintx get_hash() const {
|
||||||
return _hash;
|
return _hash;
|
||||||
}
|
}
|
||||||
bool equals(WeakHandle* value, bool* is_dead) {
|
bool equals(WeakHandle* value) {
|
||||||
oop val_oop = value->peek();
|
oop val_oop = value->peek();
|
||||||
if (val_oop == nullptr) {
|
if (val_oop == nullptr) {
|
||||||
// dead oop, mark this hash dead for cleaning
|
|
||||||
*is_dead = true;
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
bool equals = java_lang_String::equals(val_oop, _str, _len);
|
bool equals = java_lang_String::equals(val_oop, _str, _len);
|
||||||
|
@ -191,6 +189,10 @@ class StringTableLookupJchar : StackObj {
|
||||||
_found = Handle(_thread, value->resolve());
|
_found = Handle(_thread, value->resolve());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
bool is_dead(WeakHandle* value) {
|
||||||
|
oop val_oop = value->peek();
|
||||||
|
return val_oop == nullptr;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class StringTableLookupOop : public StackObj {
|
class StringTableLookupOop : public StackObj {
|
||||||
|
@ -208,11 +210,9 @@ class StringTableLookupOop : public StackObj {
|
||||||
return _hash;
|
return _hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool equals(WeakHandle* value, bool* is_dead) {
|
bool equals(WeakHandle* value) {
|
||||||
oop val_oop = value->peek();
|
oop val_oop = value->peek();
|
||||||
if (val_oop == nullptr) {
|
if (val_oop == nullptr) {
|
||||||
// dead oop, mark this hash dead for cleaning
|
|
||||||
*is_dead = true;
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
bool equals = java_lang_String::equals(_find(), val_oop);
|
bool equals = java_lang_String::equals(_find(), val_oop);
|
||||||
|
@ -223,6 +223,11 @@ class StringTableLookupOop : public StackObj {
|
||||||
_found = Handle(_thread, value->resolve());
|
_found = Handle(_thread, value->resolve());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool is_dead(WeakHandle* value) {
|
||||||
|
oop val_oop = value->peek();
|
||||||
|
return val_oop == nullptr;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
void StringTable::create_table() {
|
void StringTable::create_table() {
|
||||||
|
|
|
@ -374,7 +374,11 @@ public:
|
||||||
uintx get_hash() const {
|
uintx get_hash() const {
|
||||||
return _hash;
|
return _hash;
|
||||||
}
|
}
|
||||||
bool equals(Symbol* value, bool* is_dead) {
|
// Note: When equals() returns "true", the symbol's refcount is incremented. This is
|
||||||
|
// needed to ensure that the symbol is kept alive before equals() returns to the caller,
|
||||||
|
// so that another thread cannot clean the symbol up concurrently. The caller is
|
||||||
|
// responsible for decrementing the refcount, when the symbol is no longer needed.
|
||||||
|
bool equals(Symbol* value) {
|
||||||
assert(value != nullptr, "expected valid value");
|
assert(value != nullptr, "expected valid value");
|
||||||
Symbol *sym = value;
|
Symbol *sym = value;
|
||||||
if (sym->equals(_str, _len)) {
|
if (sym->equals(_str, _len)) {
|
||||||
|
@ -383,14 +387,15 @@ public:
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
assert(sym->refcount() == 0, "expected dead symbol");
|
assert(sym->refcount() == 0, "expected dead symbol");
|
||||||
*is_dead = true;
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
*is_dead = (sym->refcount() == 0);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
bool is_dead(Symbol* value) {
|
||||||
|
return value->refcount() == 0;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class SymbolTableGet : public StackObj {
|
class SymbolTableGet : public StackObj {
|
||||||
|
|
|
@ -258,10 +258,13 @@ class G1CardSetHashTable : public CHeapObj<mtGCCardSet> {
|
||||||
|
|
||||||
uintx get_hash() const { return G1CardSetHashTable::get_hash(_region_idx); }
|
uintx get_hash() const { return G1CardSetHashTable::get_hash(_region_idx); }
|
||||||
|
|
||||||
bool equals(G1CardSetHashTableValue* value, bool* is_dead) {
|
bool equals(G1CardSetHashTableValue* value) {
|
||||||
*is_dead = false;
|
|
||||||
return value->_region_idx == _region_idx;
|
return value->_region_idx == _region_idx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool is_dead(G1CardSetHashTableValue*) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class G1CardSetHashTableFound : public StackObj {
|
class G1CardSetHashTableFound : public StackObj {
|
||||||
|
|
|
@ -126,11 +126,9 @@ class ResolvedMethodTableLookup : StackObj {
|
||||||
uintx get_hash() const {
|
uintx get_hash() const {
|
||||||
return _hash;
|
return _hash;
|
||||||
}
|
}
|
||||||
bool equals(WeakHandle* value, bool* is_dead) {
|
bool equals(WeakHandle* value) {
|
||||||
oop val_oop = value->peek();
|
oop val_oop = value->peek();
|
||||||
if (val_oop == nullptr) {
|
if (val_oop == nullptr) {
|
||||||
// dead oop, mark this hash dead for cleaning
|
|
||||||
*is_dead = true;
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
bool equals = _method == java_lang_invoke_ResolvedMethodName::vmtarget(val_oop);
|
bool equals = _method == java_lang_invoke_ResolvedMethodName::vmtarget(val_oop);
|
||||||
|
@ -141,6 +139,10 @@ class ResolvedMethodTableLookup : StackObj {
|
||||||
_found = Handle(_thread, value->resolve());
|
_found = Handle(_thread, value->resolve());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
bool is_dead(WeakHandle* value) {
|
||||||
|
oop val_oop = value->peek();
|
||||||
|
return val_oop == nullptr;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -137,11 +137,14 @@ class FinalizerEntryLookup : StackObj {
|
||||||
public:
|
public:
|
||||||
FinalizerEntryLookup(const InstanceKlass* ik) : _ik(ik) {}
|
FinalizerEntryLookup(const InstanceKlass* ik) : _ik(ik) {}
|
||||||
uintx get_hash() const { return hash_function(_ik); }
|
uintx get_hash() const { return hash_function(_ik); }
|
||||||
bool equals(FinalizerEntry** value, bool* is_dead) {
|
bool equals(FinalizerEntry** value) {
|
||||||
assert(value != nullptr, "invariant");
|
assert(value != nullptr, "invariant");
|
||||||
assert(*value != nullptr, "invariant");
|
assert(*value != nullptr, "invariant");
|
||||||
return (*value)->klass() == _ik;
|
return (*value)->klass() == _ik;
|
||||||
}
|
}
|
||||||
|
bool is_dead(FinalizerEntry** value) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class FinalizerTableConfig : public AllStatic {
|
class FinalizerTableConfig : public AllStatic {
|
||||||
|
|
|
@ -187,13 +187,16 @@ public:
|
||||||
uintx get_hash() const {
|
uintx get_hash() const {
|
||||||
return _hash;
|
return _hash;
|
||||||
}
|
}
|
||||||
bool equals(ThreadIdTableEntry** value, bool* is_dead) {
|
bool equals(ThreadIdTableEntry** value) {
|
||||||
bool equals = primitive_equals(_tid, (*value)->tid());
|
bool equals = primitive_equals(_tid, (*value)->tid());
|
||||||
if (!equals) {
|
if (!equals) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
bool is_dead(ThreadIdTableEntry** value) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class ThreadGet : public StackObj {
|
class ThreadGet : public StackObj {
|
||||||
|
|
|
@ -455,9 +455,8 @@ inline bool ConcurrentHashTable<CONFIG, F>::
|
||||||
assert(bucket->is_locked(), "Must be locked.");
|
assert(bucket->is_locked(), "Must be locked.");
|
||||||
Node* const volatile * rem_n_prev = bucket->first_ptr();
|
Node* const volatile * rem_n_prev = bucket->first_ptr();
|
||||||
Node* rem_n = bucket->first();
|
Node* rem_n = bucket->first();
|
||||||
bool have_dead = false;
|
|
||||||
while (rem_n != nullptr) {
|
while (rem_n != nullptr) {
|
||||||
if (lookup_f.equals(rem_n->value(), &have_dead)) {
|
if (lookup_f.equals(rem_n->value())) {
|
||||||
bucket->release_assign_node_ptr(rem_n_prev, rem_n->next());
|
bucket->release_assign_node_ptr(rem_n_prev, rem_n->next());
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
|
@ -546,9 +545,7 @@ inline void ConcurrentHashTable<CONFIG, F>::
|
||||||
Node* const volatile * rem_n_prev = bucket->first_ptr();
|
Node* const volatile * rem_n_prev = bucket->first_ptr();
|
||||||
Node* rem_n = bucket->first();
|
Node* rem_n = bucket->first();
|
||||||
while (rem_n != nullptr) {
|
while (rem_n != nullptr) {
|
||||||
bool is_dead = false;
|
if (lookup_f.is_dead(rem_n->value())) {
|
||||||
lookup_f.equals(rem_n->value(), &is_dead);
|
|
||||||
if (is_dead) {
|
|
||||||
ndel[dels++] = rem_n;
|
ndel[dels++] = rem_n;
|
||||||
Node* next_node = rem_n->next();
|
Node* next_node = rem_n->next();
|
||||||
bucket->release_assign_node_ptr(rem_n_prev, next_node);
|
bucket->release_assign_node_ptr(rem_n_prev, next_node);
|
||||||
|
@ -626,12 +623,11 @@ ConcurrentHashTable<CONFIG, F>::
|
||||||
size_t loop_count = 0;
|
size_t loop_count = 0;
|
||||||
Node* node = bucket->first();
|
Node* node = bucket->first();
|
||||||
while (node != nullptr) {
|
while (node != nullptr) {
|
||||||
bool is_dead = false;
|
|
||||||
++loop_count;
|
++loop_count;
|
||||||
if (lookup_f.equals(node->value(), &is_dead)) {
|
if (lookup_f.equals(node->value())) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (is_dead && !(*have_dead)) {
|
if (!(*have_dead) && lookup_f.is_dead(node->value())) {
|
||||||
*have_dead = true;
|
*have_dead = true;
|
||||||
}
|
}
|
||||||
node = node->next();
|
node = node->next();
|
||||||
|
|
|
@ -125,3 +125,17 @@ TEST_VM_FATAL_ERROR_MSG(SymbolTable, test_symbol_underflow, ".*refcount has gone
|
||||||
my_symbol->decrement_refcount();
|
my_symbol->decrement_refcount();
|
||||||
my_symbol->increment_refcount(); // Should crash even in PRODUCT mode
|
my_symbol->increment_refcount(); // Should crash even in PRODUCT mode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_VM(SymbolTable, test_cleanup_leak) {
|
||||||
|
// Check that dead entry cleanup doesn't increment refcount of live entry in same bucket.
|
||||||
|
|
||||||
|
// Create symbol and release ref, marking it available for cleanup.
|
||||||
|
Symbol* entry1 = SymbolTable::new_symbol("hash_collision_123");
|
||||||
|
entry1->decrement_refcount();
|
||||||
|
|
||||||
|
// Create a new symbol in the same bucket, which will notice the dead entry and trigger cleanup.
|
||||||
|
// Note: relies on SymbolTable's use of String::hashCode which collides for these two values.
|
||||||
|
Symbol* entry2 = SymbolTable::new_symbol("hash_collision_397476851");
|
||||||
|
|
||||||
|
ASSERT_EQ(entry2->refcount(), 1) << "Symbol refcount just created is 1";
|
||||||
|
}
|
||||||
|
|
|
@ -107,9 +107,12 @@ struct SimpleTestLookup {
|
||||||
uintx get_hash() {
|
uintx get_hash() {
|
||||||
return Pointer::get_hash(_val, NULL);
|
return Pointer::get_hash(_val, NULL);
|
||||||
}
|
}
|
||||||
bool equals(const uintptr_t* value, bool* is_dead) {
|
bool equals(const uintptr_t* value) {
|
||||||
return _val == *value;
|
return _val == *value;
|
||||||
}
|
}
|
||||||
|
bool is_dead(const uintptr_t* value) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ValueGet {
|
struct ValueGet {
|
||||||
|
@ -561,9 +564,12 @@ struct TestLookup {
|
||||||
uintx get_hash() {
|
uintx get_hash() {
|
||||||
return TestInterface::get_hash(_val, NULL);
|
return TestInterface::get_hash(_val, NULL);
|
||||||
}
|
}
|
||||||
bool equals(const uintptr_t* value, bool* is_dead) {
|
bool equals(const uintptr_t* value) {
|
||||||
return _val == *value;
|
return _val == *value;
|
||||||
}
|
}
|
||||||
|
bool is_dead(const uintptr_t* value) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
static uintptr_t cht_get_copy(TestTable* cht, Thread* thr, TestLookup tl) {
|
static uintptr_t cht_get_copy(TestTable* cht, Thread* thr, TestLookup tl) {
|
||||||
|
|
|
@ -90,7 +90,7 @@ public class DynamicSharedSymbols extends DynamicArchiveTestBase {
|
||||||
ProcessBuilder pb = new ProcessBuilder();
|
ProcessBuilder pb = new ProcessBuilder();
|
||||||
pb.command(new String[] {JDKToolFinder.getJDKTool("jcmd"), Long.toString(pid), "VM.symboltable", "-verbose"});
|
pb.command(new String[] {JDKToolFinder.getJDKTool("jcmd"), Long.toString(pid), "VM.symboltable", "-verbose"});
|
||||||
OutputAnalyzer output = CDSTestUtils.executeAndLog(pb, "jcmd-symboltable");
|
OutputAnalyzer output = CDSTestUtils.executeAndLog(pb, "jcmd-symboltable");
|
||||||
output.shouldContain("17 3: jdk/test/lib/apps\n");
|
output.shouldContain("17 2: jdk/test/lib/apps\n");
|
||||||
output.shouldContain("Dynamic shared symbols:\n");
|
output.shouldContain("Dynamic shared symbols:\n");
|
||||||
output.shouldContain("5 65535: Hello\n");
|
output.shouldContain("5 65535: Hello\n");
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue