|
|
045ef6 |
# HG changeset patch
|
|
|
045ef6 |
# User tschatzl
|
|
|
045ef6 |
# Date 1494843615 -7200
|
|
|
045ef6 |
# Mon May 15 12:20:15 2017 +0200
|
|
|
045ef6 |
# Node ID 3d07e14d65bc223dbfe94be9224e4aa8c6e63762
|
|
|
045ef6 |
# Parent 2fee74c5547889d9698a2636e0a5170f9e66fb9c
|
|
|
045ef6 |
8180048, PR3411, RH1449870: Interned string and symbol table leak memory during parallel unlinking
|
|
|
045ef6 |
Summary: Make appending found dead BasicHashtableEntrys to the free list atomic.
|
|
|
045ef6 |
Reviewed-by: ehelin, shade
|
|
|
045ef6 |
|
|
|
045ef6 |
diff --git a/src/share/vm/classfile/symbolTable.cpp b/src/share/vm/classfile/symbolTable.cpp
|
|
|
045ef6 |
--- openjdk/hotspot/src/share/vm/classfile/symbolTable.cpp
|
|
|
045ef6 |
+++ openjdk/hotspot/src/share/vm/classfile/symbolTable.cpp
|
|
|
045ef6 |
@@ -1,5 +1,5 @@
|
|
|
045ef6 |
/*
|
|
|
045ef6 |
- * Copyright (c) 1997, 2014, Oracle and/or its affiliates. All rights reserved.
|
|
|
045ef6 |
+ * Copyright (c) 1997, 2017, Oracle and/or its affiliates. All rights reserved.
|
|
|
045ef6 |
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
|
|
045ef6 |
*
|
|
|
045ef6 |
* This code is free software; you can redistribute it and/or modify it
|
|
|
045ef6 |
@@ -96,7 +96,7 @@
|
|
|
045ef6 |
int SymbolTable::_symbols_counted = 0;
|
|
|
045ef6 |
volatile int SymbolTable::_parallel_claimed_idx = 0;
|
|
|
045ef6 |
|
|
|
045ef6 |
-void SymbolTable::buckets_unlink(int start_idx, int end_idx, int* processed, int* removed, size_t* memory_total) {
|
|
|
045ef6 |
+void SymbolTable::buckets_unlink(int start_idx, int end_idx, BucketUnlinkContext* context, size_t* memory_total) {
|
|
|
045ef6 |
for (int i = start_idx; i < end_idx; ++i) {
|
|
|
045ef6 |
HashtableEntry<Symbol*, mtSymbol>** p = the_table()->bucket_addr(i);
|
|
|
045ef6 |
HashtableEntry<Symbol*, mtSymbol>* entry = the_table()->bucket(i);
|
|
|
045ef6 |
@@ -110,15 +110,14 @@
|
|
|
045ef6 |
}
|
|
|
045ef6 |
Symbol* s = entry->literal();
|
|
|
045ef6 |
(*memory_total) += s->size();
|
|
|
045ef6 |
- (*processed)++;
|
|
|
045ef6 |
+ context->_num_processed++;
|
|
|
045ef6 |
assert(s != NULL, "just checking");
|
|
|
045ef6 |
// If reference count is zero, remove.
|
|
|
045ef6 |
if (s->refcount() == 0) {
|
|
|
045ef6 |
assert(!entry->is_shared(), "shared entries should be kept live");
|
|
|
045ef6 |
delete s;
|
|
|
045ef6 |
- (*removed)++;
|
|
|
045ef6 |
*p = entry->next();
|
|
|
045ef6 |
- the_table()->free_entry(entry);
|
|
|
045ef6 |
+ context->free_entry(entry);
|
|
|
045ef6 |
} else {
|
|
|
045ef6 |
p = entry->next_addr();
|
|
|
045ef6 |
}
|
|
|
045ef6 |
@@ -132,9 +131,14 @@
|
|
|
045ef6 |
// This is done late during GC.
|
|
|
045ef6 |
void SymbolTable::unlink(int* processed, int* removed) {
|
|
|
045ef6 |
size_t memory_total = 0;
|
|
|
045ef6 |
- buckets_unlink(0, the_table()->table_size(), processed, removed, &memory_total);
|
|
|
045ef6 |
- _symbols_removed += *removed;
|
|
|
045ef6 |
- _symbols_counted += *processed;
|
|
|
045ef6 |
+ BucketUnlinkContext context;
|
|
|
045ef6 |
+ buckets_unlink(0, the_table()->table_size(), &context, &memory_total);
|
|
|
045ef6 |
+ _the_table->bulk_free_entries(&context);
|
|
|
045ef6 |
+ *processed = context._num_processed;
|
|
|
045ef6 |
+ *removed = context._num_removed;
|
|
|
045ef6 |
+
|
|
|
045ef6 |
+ _symbols_removed = context._num_removed;
|
|
|
045ef6 |
+ _symbols_counted = context._num_processed;
|
|
|
045ef6 |
// Exclude printing for normal PrintGCDetails because people parse
|
|
|
045ef6 |
// this output.
|
|
|
045ef6 |
if (PrintGCDetails && Verbose && WizardMode) {
|
|
|
045ef6 |
@@ -148,6 +152,7 @@
|
|
|
045ef6 |
|
|
|
045ef6 |
size_t memory_total = 0;
|
|
|
045ef6 |
|
|
|
045ef6 |
+ BucketUnlinkContext context;
|
|
|
045ef6 |
for (;;) {
|
|
|
045ef6 |
// Grab next set of buckets to scan
|
|
|
045ef6 |
int start_idx = Atomic::add(ClaimChunkSize, &_parallel_claimed_idx) - ClaimChunkSize;
|
|
|
045ef6 |
@@ -157,10 +162,15 @@
|
|
|
045ef6 |
}
|
|
|
045ef6 |
|
|
|
045ef6 |
int end_idx = MIN2(limit, start_idx + ClaimChunkSize);
|
|
|
045ef6 |
- buckets_unlink(start_idx, end_idx, processed, removed, &memory_total);
|
|
|
045ef6 |
+ buckets_unlink(start_idx, end_idx, &context, &memory_total);
|
|
|
045ef6 |
}
|
|
|
045ef6 |
- Atomic::add(*processed, &_symbols_counted);
|
|
|
045ef6 |
- Atomic::add(*removed, &_symbols_removed);
|
|
|
045ef6 |
+
|
|
|
045ef6 |
+ _the_table->bulk_free_entries(&context);
|
|
|
045ef6 |
+ *processed = context._num_processed;
|
|
|
045ef6 |
+ *removed = context._num_removed;
|
|
|
045ef6 |
+
|
|
|
045ef6 |
+ Atomic::add(context._num_processed, &_symbols_counted);
|
|
|
045ef6 |
+ Atomic::add(context._num_removed, &_symbols_removed);
|
|
|
045ef6 |
// Exclude printing for normal PrintGCDetails because people parse
|
|
|
045ef6 |
// this output.
|
|
|
045ef6 |
if (PrintGCDetails && Verbose && WizardMode) {
|
|
|
045ef6 |
@@ -811,7 +821,11 @@
|
|
|
045ef6 |
}
|
|
|
045ef6 |
|
|
|
045ef6 |
void StringTable::unlink_or_oops_do(BoolObjectClosure* is_alive, OopClosure* f, int* processed, int* removed) {
|
|
|
045ef6 |
- buckets_unlink_or_oops_do(is_alive, f, 0, the_table()->table_size(), processed, removed);
|
|
|
045ef6 |
+ BucketUnlinkContext context;
|
|
|
045ef6 |
+ buckets_unlink_or_oops_do(is_alive, f, 0, the_table()->table_size(), &context);
|
|
|
045ef6 |
+ _the_table->bulk_free_entries(&context);
|
|
|
045ef6 |
+ *processed = context._num_processed;
|
|
|
045ef6 |
+ *removed = context._num_removed;
|
|
|
045ef6 |
}
|
|
|
045ef6 |
|
|
|
045ef6 |
void StringTable::possibly_parallel_unlink_or_oops_do(BoolObjectClosure* is_alive, OopClosure* f, int* processed, int* removed) {
|
|
|
045ef6 |
@@ -820,6 +834,7 @@
|
|
|
045ef6 |
assert(SafepointSynchronize::is_at_safepoint(), "must be at safepoint");
|
|
|
045ef6 |
const int limit = the_table()->table_size();
|
|
|
045ef6 |
|
|
|
045ef6 |
+ BucketUnlinkContext context;
|
|
|
045ef6 |
for (;;) {
|
|
|
045ef6 |
// Grab next set of buckets to scan
|
|
|
045ef6 |
int start_idx = Atomic::add(ClaimChunkSize, &_parallel_claimed_idx) - ClaimChunkSize;
|
|
|
045ef6 |
@@ -829,8 +844,11 @@
|
|
|
045ef6 |
}
|
|
|
045ef6 |
|
|
|
045ef6 |
int end_idx = MIN2(limit, start_idx + ClaimChunkSize);
|
|
|
045ef6 |
- buckets_unlink_or_oops_do(is_alive, f, start_idx, end_idx, processed, removed);
|
|
|
045ef6 |
+ buckets_unlink_or_oops_do(is_alive, f, start_idx, end_idx, &context);
|
|
|
045ef6 |
}
|
|
|
045ef6 |
+ _the_table->bulk_free_entries(&context);
|
|
|
045ef6 |
+ *processed = context._num_processed;
|
|
|
045ef6 |
+ *removed = context._num_removed;
|
|
|
045ef6 |
}
|
|
|
045ef6 |
|
|
|
045ef6 |
void StringTable::buckets_oops_do(OopClosure* f, int start_idx, int end_idx) {
|
|
|
045ef6 |
@@ -856,7 +874,7 @@
|
|
|
045ef6 |
}
|
|
|
045ef6 |
}
|
|
|
045ef6 |
|
|
|
045ef6 |
-void StringTable::buckets_unlink_or_oops_do(BoolObjectClosure* is_alive, OopClosure* f, int start_idx, int end_idx, int* processed, int* removed) {
|
|
|
045ef6 |
+void StringTable::buckets_unlink_or_oops_do(BoolObjectClosure* is_alive, OopClosure* f, int start_idx, int end_idx, BucketUnlinkContext* context) {
|
|
|
045ef6 |
const int limit = the_table()->table_size();
|
|
|
045ef6 |
|
|
|
045ef6 |
assert(0 <= start_idx && start_idx <= limit,
|
|
|
045ef6 |
@@ -880,10 +898,9 @@
|
|
|
045ef6 |
p = entry->next_addr();
|
|
|
045ef6 |
} else {
|
|
|
045ef6 |
*p = entry->next();
|
|
|
045ef6 |
- the_table()->free_entry(entry);
|
|
|
045ef6 |
- (*removed)++;
|
|
|
045ef6 |
+ context->free_entry(entry);
|
|
|
045ef6 |
}
|
|
|
045ef6 |
- (*processed)++;
|
|
|
045ef6 |
+ context->_num_processed++;
|
|
|
045ef6 |
entry = *p;
|
|
|
045ef6 |
}
|
|
|
045ef6 |
}
|
|
|
045ef6 |
diff --git a/src/share/vm/classfile/symbolTable.hpp b/src/share/vm/classfile/symbolTable.hpp
|
|
|
045ef6 |
--- openjdk/hotspot/src/share/vm/classfile/symbolTable.hpp
|
|
|
045ef6 |
+++ openjdk/hotspot/src/share/vm/classfile/symbolTable.hpp
|
|
|
045ef6 |
@@ -1,5 +1,5 @@
|
|
|
045ef6 |
/*
|
|
|
045ef6 |
- * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
|
|
|
045ef6 |
+ * Copyright (c) 1997, 2017, Oracle and/or its affiliates. All rights reserved.
|
|
|
045ef6 |
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
|
|
045ef6 |
*
|
|
|
045ef6 |
* This code is free software; you can redistribute it and/or modify it
|
|
|
045ef6 |
@@ -124,8 +124,11 @@
|
|
|
045ef6 |
|
|
|
045ef6 |
static volatile int _parallel_claimed_idx;
|
|
|
045ef6 |
|
|
|
045ef6 |
- // Release any dead symbols
|
|
|
045ef6 |
- static void buckets_unlink(int start_idx, int end_idx, int* processed, int* removed, size_t* memory_total);
|
|
|
045ef6 |
+ typedef SymbolTable::BucketUnlinkContext BucketUnlinkContext;
|
|
|
045ef6 |
+ // Release any dead symbols. Unlinked bucket entries are collected in the given
|
|
|
045ef6 |
+ // context to be freed later.
|
|
|
045ef6 |
+ // This allows multiple threads to work on the table at once.
|
|
|
045ef6 |
+ static void buckets_unlink(int start_idx, int end_idx, BucketUnlinkContext* context, size_t* memory_total);
|
|
|
045ef6 |
public:
|
|
|
045ef6 |
enum {
|
|
|
045ef6 |
symbol_alloc_batch_size = 8,
|
|
|
045ef6 |
@@ -274,9 +277,13 @@
|
|
|
045ef6 |
// Apply the give oop closure to the entries to the buckets
|
|
|
045ef6 |
// in the range [start_idx, end_idx).
|
|
|
045ef6 |
static void buckets_oops_do(OopClosure* f, int start_idx, int end_idx);
|
|
|
045ef6 |
+
|
|
|
045ef6 |
+ typedef StringTable::BucketUnlinkContext BucketUnlinkContext;
|
|
|
045ef6 |
// Unlink or apply the give oop closure to the entries to the buckets
|
|
|
045ef6 |
- // in the range [start_idx, end_idx).
|
|
|
045ef6 |
- static void buckets_unlink_or_oops_do(BoolObjectClosure* is_alive, OopClosure* f, int start_idx, int end_idx, int* processed, int* removed);
|
|
|
045ef6 |
+ // in the range [start_idx, end_idx). Unlinked bucket entries are collected in the given
|
|
|
045ef6 |
+ // context to be freed later.
|
|
|
045ef6 |
+ // This allows multiple threads to work on the table at once.
|
|
|
045ef6 |
+ static void buckets_unlink_or_oops_do(BoolObjectClosure* is_alive, OopClosure* f, int start_idx, int end_idx, BucketUnlinkContext* context);
|
|
|
045ef6 |
|
|
|
045ef6 |
StringTable() : RehashableHashtable<oop, mtSymbol>((int)StringTableSize,
|
|
|
045ef6 |
sizeof (HashtableEntry<oop, mtSymbol>)) {}
|
|
|
045ef6 |
diff --git a/src/share/vm/runtime/vmStructs.cpp b/src/share/vm/runtime/vmStructs.cpp
|
|
|
045ef6 |
--- openjdk/hotspot/src/share/vm/runtime/vmStructs.cpp
|
|
|
045ef6 |
+++ openjdk/hotspot/src/share/vm/runtime/vmStructs.cpp
|
|
|
045ef6 |
@@ -712,7 +712,7 @@
|
|
|
045ef6 |
\
|
|
|
045ef6 |
nonstatic_field(BasicHashtable<mtInternal>, _table_size, int) \
|
|
|
045ef6 |
nonstatic_field(BasicHashtable<mtInternal>, _buckets, HashtableBucket<mtInternal>*) \
|
|
|
045ef6 |
- nonstatic_field(BasicHashtable<mtInternal>, _free_list, BasicHashtableEntry<mtInternal>*) \
|
|
|
045ef6 |
+ volatile_nonstatic_field(BasicHashtable<mtInternal>, _free_list, BasicHashtableEntry<mtInternal>*) \
|
|
|
045ef6 |
nonstatic_field(BasicHashtable<mtInternal>, _first_free_entry, char*) \
|
|
|
045ef6 |
nonstatic_field(BasicHashtable<mtInternal>, _end_block, char*) \
|
|
|
045ef6 |
nonstatic_field(BasicHashtable<mtInternal>, _entry_size, int) \
|
|
|
045ef6 |
diff --git a/src/share/vm/utilities/hashtable.cpp b/src/share/vm/utilities/hashtable.cpp
|
|
|
045ef6 |
--- openjdk/hotspot/src/share/vm/utilities/hashtable.cpp
|
|
|
045ef6 |
+++ openjdk/hotspot/src/share/vm/utilities/hashtable.cpp
|
|
|
045ef6 |
@@ -1,5 +1,5 @@
|
|
|
045ef6 |
/*
|
|
|
045ef6 |
- * Copyright (c) 2003, 2014, Oracle and/or its affiliates. All rights reserved.
|
|
|
045ef6 |
+ * Copyright (c) 2003, 2017, Oracle and/or its affiliates. All rights reserved.
|
|
|
045ef6 |
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
|
|
045ef6 |
*
|
|
|
045ef6 |
* This code is free software; you can redistribute it and/or modify it
|
|
|
045ef6 |
@@ -172,6 +172,35 @@
|
|
|
045ef6 |
}
|
|
|
045ef6 |
}
|
|
|
045ef6 |
|
|
|
045ef6 |
+template <MEMFLAGS F> void BasicHashtable<F>::BucketUnlinkContext::free_entry(BasicHashtableEntry<F>* entry) {
|
|
|
045ef6 |
+ entry->set_next(_removed_head);
|
|
|
045ef6 |
+ _removed_head = entry;
|
|
|
045ef6 |
+ if (_removed_tail == NULL) {
|
|
|
045ef6 |
+ _removed_tail = entry;
|
|
|
045ef6 |
+ }
|
|
|
045ef6 |
+ _num_removed++;
|
|
|
045ef6 |
+}
|
|
|
045ef6 |
+
|
|
|
045ef6 |
+template <MEMFLAGS F> void BasicHashtable<F>::bulk_free_entries(BucketUnlinkContext* context) {
|
|
|
045ef6 |
+ if (context->_num_removed == 0) {
|
|
|
045ef6 |
+ assert(context->_removed_head == NULL && context->_removed_tail == NULL,
|
|
|
045ef6 |
+ err_msg("Zero entries in the unlink context, but elements linked from " PTR_FORMAT " to " PTR_FORMAT,
|
|
|
045ef6 |
+ p2i(context->_removed_head), p2i(context->_removed_tail)));
|
|
|
045ef6 |
+ return;
|
|
|
045ef6 |
+ }
|
|
|
045ef6 |
+
|
|
|
045ef6 |
+ // MT-safe add of the list of BasicHashTableEntrys from the context to the free list.
|
|
|
045ef6 |
+ BasicHashtableEntry<F>* current = _free_list;
|
|
|
045ef6 |
+ while (true) {
|
|
|
045ef6 |
+ context->_removed_tail->set_next(current);
|
|
|
045ef6 |
+ BasicHashtableEntry<F>* old = (BasicHashtableEntry<F>*)Atomic::cmpxchg_ptr(context->_removed_head, &_free_list, current);
|
|
|
045ef6 |
+ if (old == current) {
|
|
|
045ef6 |
+ break;
|
|
|
045ef6 |
+ }
|
|
|
045ef6 |
+ current = old;
|
|
|
045ef6 |
+ }
|
|
|
045ef6 |
+ Atomic::add(-context->_num_removed, &_number_of_entries);
|
|
|
045ef6 |
+}
|
|
|
045ef6 |
|
|
|
045ef6 |
// Copy the table to the shared space.
|
|
|
045ef6 |
|
|
|
045ef6 |
diff --git a/src/share/vm/utilities/hashtable.hpp b/src/share/vm/utilities/hashtable.hpp
|
|
|
045ef6 |
--- openjdk/hotspot/src/share/vm/utilities/hashtable.hpp
|
|
|
045ef6 |
+++ openjdk/hotspot/src/share/vm/utilities/hashtable.hpp
|
|
|
045ef6 |
@@ -164,11 +164,11 @@
|
|
|
045ef6 |
// Instance variables
|
|
|
045ef6 |
int _table_size;
|
|
|
045ef6 |
HashtableBucket<F>* _buckets;
|
|
|
045ef6 |
- BasicHashtableEntry<F>* _free_list;
|
|
|
045ef6 |
+ BasicHashtableEntry<F>* volatile _free_list;
|
|
|
045ef6 |
char* _first_free_entry;
|
|
|
045ef6 |
char* _end_block;
|
|
|
045ef6 |
int _entry_size;
|
|
|
045ef6 |
- int _number_of_entries;
|
|
|
045ef6 |
+ volatile int _number_of_entries;
|
|
|
045ef6 |
|
|
|
045ef6 |
protected:
|
|
|
045ef6 |
|
|
|
045ef6 |
@@ -215,6 +215,24 @@
|
|
|
045ef6 |
// Free the buckets in this hashtable
|
|
|
045ef6 |
void free_buckets();
|
|
|
045ef6 |
|
|
|
045ef6 |
+ // Helper data structure containing context for the bucket entry unlink process,
|
|
|
045ef6 |
+ // storing the unlinked buckets in a linked list.
|
|
|
045ef6 |
+ // Also avoids the need to pass around these four members as parameters everywhere.
|
|
|
045ef6 |
+ struct BucketUnlinkContext {
|
|
|
045ef6 |
+ int _num_processed;
|
|
|
045ef6 |
+ int _num_removed;
|
|
|
045ef6 |
+ // Head and tail pointers for the linked list of removed entries.
|
|
|
045ef6 |
+ BasicHashtableEntry<F>* _removed_head;
|
|
|
045ef6 |
+ BasicHashtableEntry<F>* _removed_tail;
|
|
|
045ef6 |
+
|
|
|
045ef6 |
+ BucketUnlinkContext() : _num_processed(0), _num_removed(0), _removed_head(NULL), _removed_tail(NULL) {
|
|
|
045ef6 |
+ }
|
|
|
045ef6 |
+
|
|
|
045ef6 |
+ void free_entry(BasicHashtableEntry<F>* entry);
|
|
|
045ef6 |
+ };
|
|
|
045ef6 |
+ // Add of bucket entries linked together in the given context to the global free list. This method
|
|
|
045ef6 |
+ // is mt-safe wrt. to other calls of this method.
|
|
|
045ef6 |
+ void bulk_free_entries(BucketUnlinkContext* context);
|
|
|
045ef6 |
public:
|
|
|
045ef6 |
int table_size() { return _table_size; }
|
|
|
045ef6 |
void set_entry(int index, BasicHashtableEntry<F>* entry);
|