8210986: Add OopStorage cleanup to ServiceThread

Service thread performs cleanup when notified.

Reviewed-by: coleenp, rehn
This commit is contained in:
Kim Barrett 2018-11-05 18:27:14 -05:00
parent a1029a4729
commit 1e9d3b5358
6 changed files with 248 additions and 165 deletions

@ -31,6 +31,7 @@
#include "runtime/atomic.hpp"
#include "runtime/globals.hpp"
#include "runtime/handles.inline.hpp"
#include "runtime/interfaceSupport.inline.hpp"
#include "runtime/mutex.hpp"
#include "runtime/mutexLocker.hpp"
#include "runtime/orderAccess.hpp"
@ -254,15 +255,15 @@ uintx OopStorage::Block::bitmask_for_entry(const oop* ptr) const {
return bitmask_for_index(get_index(ptr));
}
// A block is deletable if
// (1) It is empty.
// (2) There is not a release() operation currently operating on it.
// (3) It is not in the deferred updates list.
// The order of tests is important for proper interaction between release()
// and concurrent deletion.
bool OopStorage::Block::is_deletable() const {
return (OrderAccess::load_acquire(&_allocated_bitmask) == 0) &&
(OrderAccess::load_acquire(&_release_refcount) == 0) &&
// An empty block is not yet deletable if either:
// (1) There is a release() operation currently operating on it.
// (2) It is in the deferred updates list.
// For interaction with release(), these must follow the empty check,
// and the order of these checks is important.
bool OopStorage::Block::is_safe_to_delete() const {
assert(is_empty(), "precondition");
OrderAccess::loadload();
return (OrderAccess::load_acquire(&_release_refcount) == 0) &&
(OrderAccess::load_acquire(&_deferred_updates_next) == NULL);
}
@ -373,7 +374,7 @@ OopStorage::Block::block_for_ptr(const OopStorage* owner, const oop* ptr) {
// kept at the end of the _allocation_list, to make it easy for empty block
// deletion to find them.
//
// allocate(), and delete_empty_blocks_concurrent() lock the
// allocate(), and delete_empty_blocks() lock the
// _allocation_mutex while performing any list and array modifications.
//
// allocate() and release() update a block's _allocated_bitmask using CAS
@ -386,7 +387,10 @@ OopStorage::Block::block_for_ptr(const OopStorage* owner, const oop* ptr) {
// removed from the _allocation_list so it won't be considered by future
// allocations until some entries in it are released.
//
// release() is performed lock-free. release() first looks up the block for
// release() is performed lock-free. (Note: This means it can't notify the
// service thread of pending cleanup work. It must be lock-free because
// it is called in all kinds of contexts where even quite low ranked locks
// may be held.) release() first looks up the block for
// the entry, using address alignment to find the enclosing block (thereby
// avoiding iteration over the _active_array). Once the block has been
// determined, its _allocated_bitmask needs to be updated, and its position in
@ -400,7 +404,7 @@ OopStorage::Block::block_for_ptr(const OopStorage* owner, const oop* ptr) {
// locking the _allocation_mutex. To keep the release() operation lock-free,
// rather than updating the _allocation_list itself, it instead performs a
// lock-free push of the block onto the _deferred_updates list. Entries on
// that list are processed by allocate() and delete_empty_blocks_XXX(), while
// that list are processed by allocate() and delete_empty_blocks(), while
// they already hold the necessary lock. That processing makes the block's
// list state consistent with its current _allocated_bitmask. The block is
// added to the _allocation_list if not already present and the bitmask is not
@ -409,54 +413,17 @@ OopStorage::Block::block_for_ptr(const OopStorage* owner, const oop* ptr) {
oop* OopStorage::allocate() {
MutexLockerEx ml(_allocation_mutex, Mutex::_no_safepoint_check_flag);
// Do some deferred update processing every time we allocate.
// Continue processing deferred updates if _allocation_list is empty,
// in the hope that we'll get a block from that, rather than
// allocating a new block.
while (reduce_deferred_updates() && (_allocation_list.head() == NULL)) {}
// Use the first block in _allocation_list for the allocation.
Block* block = _allocation_list.head();
if (block == NULL) {
// No available blocks; make a new one, and add to storage.
{
MutexUnlockerEx mul(_allocation_mutex, Mutex::_no_safepoint_check_flag);
block = Block::new_block(this);
}
if (block == NULL) {
while (_allocation_list.head() == NULL) {
if (!reduce_deferred_updates()) {
// Failed to make new block, no other thread made a block
// available while the mutex was released, and didn't get
// one from a deferred update either, so return failure.
log_debug(oopstorage, blocks)("%s: failed block allocation", name());
return NULL;
}
}
} else {
// Add new block to storage.
log_debug(oopstorage, blocks)("%s: new block " PTR_FORMAT, name(), p2i(block));
// Add new block to the _active_array, growing if needed.
if (!_active_array->push(block)) {
if (expand_active_array()) {
guarantee(_active_array->push(block), "push failed after expansion");
} else {
log_debug(oopstorage, blocks)("%s: failed active array expand", name());
Block::delete_block(*block);
return NULL;
}
}
// Add to end of _allocation_list. The mutex release allowed
// other threads to add blocks to the _allocation_list. We prefer
// to allocate from non-empty blocks, to allow empty blocks to
// be deleted.
_allocation_list.push_back(*block);
}
block = _allocation_list.head();
// Note: Without this we might never perform cleanup. As it is,
// cleanup is only requested here, when completing a concurrent
// iteration, or when someone entirely else wakes up the service
// thread, which isn't ideal. But we can't notify in release().
if (reduce_deferred_updates()) {
notify_needs_cleanup();
}
// Allocate from first block.
assert(block != NULL, "invariant");
Block* block = block_for_allocation();
if (block == NULL) return NULL; // Block allocation failed.
assert(!block->is_full(), "invariant");
if (block->is_empty()) {
// Transitioning from empty to not empty.
@ -476,6 +443,62 @@ oop* OopStorage::allocate() {
return result;
}
bool OopStorage::try_add_block() {
assert_lock_strong(_allocation_mutex);
Block* block;
{
MutexUnlockerEx ul(_allocation_mutex, Mutex::_no_safepoint_check_flag);
block = Block::new_block(this);
}
if (block == NULL) return false;
// Add new block to the _active_array, growing if needed.
if (!_active_array->push(block)) {
if (expand_active_array()) {
guarantee(_active_array->push(block), "push failed after expansion");
} else {
log_debug(oopstorage, blocks)("%s: failed active array expand", name());
Block::delete_block(*block);
return false;
}
}
// Add to end of _allocation_list. The mutex release allowed other
// threads to add blocks to the _allocation_list. We prefer to
// allocate from non-empty blocks, to allow empty blocks to be
// deleted. But we don't bother notifying about the empty block
// because we're (probably) about to allocate an entry from it.
_allocation_list.push_back(*block);
log_debug(oopstorage, blocks)("%s: new block " PTR_FORMAT, name(), p2i(block));
return true;
}
OopStorage::Block* OopStorage::block_for_allocation() {
assert_lock_strong(_allocation_mutex);
while (true) {
// Use the first block in _allocation_list for the allocation.
Block* block = _allocation_list.head();
if (block != NULL) {
return block;
} else if (reduce_deferred_updates()) {
MutexUnlockerEx ul(_allocation_mutex, Mutex::_no_safepoint_check_flag);
notify_needs_cleanup();
} else if (try_add_block()) {
block = _allocation_list.head();
assert(block != NULL, "invariant");
return block;
} else if (reduce_deferred_updates()) { // Once more before failure.
MutexUnlockerEx ul(_allocation_mutex, Mutex::_no_safepoint_check_flag);
notify_needs_cleanup();
} else {
// Attempt to add a block failed, no other thread added a block,
// and no deferred updated added a block, then allocation failed.
log_debug(oopstorage, blocks)("%s: failed block allocation", name());
return NULL;
}
}
}
// Create a new, larger, active array with the same content as the
// current array, and then replace, relinquishing the old array.
// Return true if the array was successfully expanded, false to
@ -572,7 +595,7 @@ static void log_release_transitions(uintx releasing,
}
}
void OopStorage::Block::release_entries(uintx releasing, Block* volatile* deferred_list) {
void OopStorage::Block::release_entries(uintx releasing, OopStorage* owner) {
assert(releasing != 0, "preconditon");
// Prevent empty block deletion when transitioning to empty.
Atomic::inc(&_release_refcount);
@ -591,8 +614,8 @@ void OopStorage::Block::release_entries(uintx releasing, Block* volatile* deferr
// (updated bitmask is empty or old bitmask was full), atomically push
// this block onto the deferred updates list. Some future call to
// reduce_deferred_updates will make any needed changes related to this
// block and _allocation_list. This deferral avoids list updates and the
// associated locking here.
// block and _allocation_list. This deferral avoids _allocation_list
// updates and the associated locking here.
if ((releasing == old_allocated) || is_full_bitmask(old_allocated)) {
// Log transitions. Both transitions are possible in a single update.
if (log_is_enabled(Debug, oopstorage, blocks)) {
@ -605,13 +628,14 @@ void OopStorage::Block::release_entries(uintx releasing, Block* volatile* deferr
// anything further.
if (Atomic::replace_if_null(this, &_deferred_updates_next)) {
// Successfully claimed. Push, with self-loop for end-of-list.
Block* head = *deferred_list;
Block* head = owner->_deferred_updates;
while (true) {
_deferred_updates_next = (head == NULL) ? this : head;
Block* fetched = Atomic::cmpxchg(this, deferred_list, head);
Block* fetched = Atomic::cmpxchg(this, &owner->_deferred_updates, head);
if (fetched == head) break; // Successful update.
head = fetched; // Retry with updated head.
}
owner->record_needs_cleanup();
log_debug(oopstorage, blocks)("%s: deferred update " PTR_FORMAT,
_owner->name(), p2i(this));
}
@ -622,7 +646,7 @@ void OopStorage::Block::release_entries(uintx releasing, Block* volatile* deferr
// Process one available deferred update. Returns true if one was processed.
bool OopStorage::reduce_deferred_updates() {
assert_locked_or_safepoint(_allocation_mutex);
assert_lock_strong(_allocation_mutex);
// Atomically pop a block off the list, if any available.
// No ABA issue because this is only called by one thread at a time.
// The atomicity is wrto pushes by release().
@ -641,7 +665,7 @@ bool OopStorage::reduce_deferred_updates() {
// ordering with release(). Without this, we may be processing a stale
// bitmask state here while blocking a release() operation from recording
// the deferred update needed for its bitmask change.
OrderAccess::storeload();
OrderAccess::fence();
// Process popped block.
uintx allocated = block->allocated_bitmask();
@ -660,6 +684,7 @@ bool OopStorage::reduce_deferred_updates() {
if (is_empty_bitmask(allocated)) {
_allocation_list.unlink(*block);
_allocation_list.push_back(*block);
notify_needs_cleanup();
}
log_debug(oopstorage, blocks)("%s: processed deferred update " PTR_FORMAT,
@ -677,7 +702,7 @@ void OopStorage::release(const oop* ptr) {
Block* block = find_block_or_null(ptr);
assert(block != NULL, "%s: invalid release " PTR_FORMAT, name(), p2i(ptr));
log_trace(oopstorage, ref)("%s: released " PTR_FORMAT, name(), p2i(ptr));
block->release_entries(block->bitmask_for_entry(ptr), &_deferred_updates);
block->release_entries(block->bitmask_for_entry(ptr), this);
Atomic::dec(&_allocation_count);
}
@ -704,7 +729,7 @@ void OopStorage::release(const oop* const* ptrs, size_t size) {
++count;
}
// Release the contiguous entries that are in block.
block->release_entries(releasing, &_deferred_updates);
block->release_entries(releasing, this);
Atomic::sub(count, &_allocation_count);
}
}
@ -715,6 +740,11 @@ const char* dup_name(const char* name) {
return dup;
}
// Possible values for OopStorage::_needs_cleanup.
const uint needs_cleanup_none = 0; // No cleanup needed.
const uint needs_cleanup_marked = 1; // Requested, but no notification made.
const uint needs_cleanup_notified = 2; // Requested and Service thread notified.
const size_t initial_active_array_size = 8;
OopStorage::OopStorage(const char* name,
@ -727,11 +757,14 @@ OopStorage::OopStorage(const char* name,
_allocation_mutex(allocation_mutex),
_active_mutex(active_mutex),
_allocation_count(0),
_concurrent_iteration_count(0)
_concurrent_iteration_count(0),
_needs_cleanup(needs_cleanup_none)
{
_active_array->increment_refcount();
assert(_active_mutex->rank() < _allocation_mutex->rank(),
"%s: active_mutex must have lower rank than allocation_mutex", _name);
assert(Service_lock->rank() < _active_mutex->rank(),
"%s: active_mutex must have higher rank than Service_lock", _name);
assert(_active_mutex->_safepoint_check_required != Mutex::_safepoint_check_always,
"%s: active mutex requires safepoint check", _name);
assert(_allocation_mutex->_safepoint_check_required != Mutex::_safepoint_check_always,
@ -763,56 +796,82 @@ OopStorage::~OopStorage() {
FREE_C_HEAP_ARRAY(char, _name);
}
void OopStorage::delete_empty_blocks_safepoint() {
assert_at_safepoint();
// Process any pending release updates, which may make more empty
// blocks available for deletion.
while (reduce_deferred_updates()) {}
// Don't interfere with a concurrent iteration.
if (_concurrent_iteration_count > 0) return;
// Delete empty (and otherwise deletable) blocks from end of _allocation_list.
for (Block* block = _allocation_list.tail();
(block != NULL) && block->is_deletable();
block = _allocation_list.tail()) {
_active_array->remove(block);
_allocation_list.unlink(*block);
delete_empty_block(*block);
// Called by service thread to check for pending work.
bool OopStorage::needs_delete_empty_blocks() const {
return Atomic::load(&_needs_cleanup) != needs_cleanup_none;
}
// Record that cleanup is needed, without notifying the Service thread.
// Used by release(), where we can't lock even Service_lock.
void OopStorage::record_needs_cleanup() {
Atomic::cmpxchg(needs_cleanup_marked, &_needs_cleanup, needs_cleanup_none);
}
// Record that cleanup is needed, and notify the Service thread.
void OopStorage::notify_needs_cleanup() {
// Avoid re-notification if already notified.
const uint notified = needs_cleanup_notified;
if (Atomic::xchg(notified, &_needs_cleanup) != notified) {
MonitorLockerEx ml(Service_lock, Monitor::_no_safepoint_check_flag);
ml.notify_all();
}
}
void OopStorage::delete_empty_blocks_concurrent() {
bool OopStorage::delete_empty_blocks() {
MutexLockerEx ml(_allocation_mutex, Mutex::_no_safepoint_check_flag);
// Other threads could be adding to the empty block count while we
// release the mutex across the block deletions. Set an upper bound
// on how many blocks we'll try to release, so other threads can't
// cause an unbounded stay in this function.
// Clear the request before processing.
Atomic::store(needs_cleanup_none, &_needs_cleanup);
OrderAccess::fence();
// Other threads could be adding to the empty block count or the
// deferred update list while we're working. Set an upper bound on
// how many updates we'll process and blocks we'll try to release,
// so other threads can't cause an unbounded stay in this function.
size_t limit = block_count();
if (limit == 0) return false; // Empty storage; nothing at all to do.
for (size_t i = 0; i < limit; ++i) {
// Additional updates might become available while we dropped the
// lock. But limit number processed to limit lock duration.
reduce_deferred_updates();
// Process deferred updates, which might make empty blocks available.
// Continue checking once deletion starts, since additional updates
// might become available while we're working.
if (reduce_deferred_updates()) {
// Be safepoint-polite while looping.
MutexUnlockerEx ul(_allocation_mutex, Mutex::_no_safepoint_check_flag);
ThreadBlockInVM tbiv(JavaThread::current());
} else {
Block* block = _allocation_list.tail();
if ((block == NULL) || !block->is_empty()) {
return false;
} else if (!block->is_safe_to_delete()) {
// Look for other work while waiting for block to be deletable.
break;
}
Block* block = _allocation_list.tail();
if ((block == NULL) || !block->is_deletable()) {
// No block to delete, so done. There could be more pending
// deferred updates that could give us more work to do; deal with
// that in some later call, to limit lock duration here.
return;
// Try to delete the block. First, try to remove from _active_array.
{
MutexLockerEx aml(_active_mutex, Mutex::_no_safepoint_check_flag);
// Don't interfere with an active concurrent iteration.
// Instead, give up immediately. There is more work to do,
// but don't re-notify, to avoid useless spinning of the
// service thread. Instead, iteration completion notifies.
if (_concurrent_iteration_count > 0) return true;
_active_array->remove(block);
}
// Remove block from _allocation_list and delete it.
_allocation_list.unlink(*block);
// Be safepoint-polite while deleting and looping.
MutexUnlockerEx ul(_allocation_mutex, Mutex::_no_safepoint_check_flag);
delete_empty_block(*block);
ThreadBlockInVM tbiv(JavaThread::current());
}
{
MutexLockerEx aml(_active_mutex, Mutex::_no_safepoint_check_flag);
// Don't interfere with a concurrent iteration.
if (_concurrent_iteration_count > 0) return;
_active_array->remove(block);
}
// Remove block from _allocation_list and delete it.
_allocation_list.unlink(*block);
// Release mutex while deleting block.
MutexUnlockerEx ul(_allocation_mutex, Mutex::_no_safepoint_check_flag);
delete_empty_block(*block);
}
// Exceeded work limit or can't delete last block. This will
// cause the service thread to loop, giving other subtasks an
// opportunity to run too. There's no need for a notification,
// because we are part of the service thread (unless gtesting).
record_needs_cleanup();
return true;
}
OopStorage::EntryStatus OopStorage::allocation_status(const oop* ptr) const {
@ -886,6 +945,10 @@ OopStorage::BasicParState::BasicParState(const OopStorage* storage,
OopStorage::BasicParState::~BasicParState() {
_storage->relinquish_block_array(_active_array);
update_concurrent_iteration_count(-1);
if (_concurrent) {
// We may have deferred some work.
const_cast<OopStorage*>(_storage)->notify_needs_cleanup();
}
}
void OopStorage::BasicParState::update_concurrent_iteration_count(int value) {

@ -151,11 +151,19 @@ public:
// Other clients must use serial iteration.
template<bool concurrent, bool is_const> class ParState;
// Block cleanup functions are for the exclusive use of the GC.
// Both stop deleting if there is an in-progress concurrent iteration.
// Concurrent deletion locks both the _allocation_mutex and the _active_mutex.
void delete_empty_blocks_safepoint();
void delete_empty_blocks_concurrent();
// Service thread cleanup support.
// Stops deleting if there is an in-progress concurrent iteration.
// Locks both the _allocation_mutex and the _active_mutex, and may
// safepoint. Deletion may be throttled, with only some available
// work performed, in order to allow other Service thread subtasks
// to run. Returns true if there may be more work to do, false if
// nothing to do.
bool delete_empty_blocks();
// Service thread cleanup support.
// Called by the service thread (while holding Service_lock) to test
// whether a call to delete_empty_blocks should be made.
bool needs_delete_empty_blocks() const;
// Debugging and logging support.
const char* name() const;
@ -208,7 +216,9 @@ private:
const char* _name;
ActiveArray* _active_array;
AllocationList _allocation_list;
AIX_ONLY(public:) // xlC 12 on AIX doesn't implement C++ DR45.
Block* volatile _deferred_updates;
AIX_ONLY(private:)
Mutex* _allocation_mutex;
Mutex* _active_mutex;
@ -222,9 +232,18 @@ private:
// mutable because this gets set even for const iteration.
mutable int _concurrent_iteration_count;
volatile uint _needs_cleanup;
bool try_add_block();
Block* block_for_allocation();
Block* find_block_or_null(const oop* ptr) const;
void delete_empty_block(const Block& block);
bool reduce_deferred_updates();
void notify_needs_cleanup();
AIX_ONLY(public:) // xlC 12 on AIX doesn't implement C++ DR45.
void record_needs_cleanup();
AIX_ONLY(private:)
// Managing _active_array.
bool expand_active_array();

@ -173,7 +173,8 @@ public:
bool is_full() const;
bool is_empty() const;
uintx allocated_bitmask() const;
bool is_deletable() const;
bool is_safe_to_delete() const;
Block* deferred_updates_next() const;
void set_deferred_updates_next(Block* new_next);
@ -191,7 +192,7 @@ public:
static Block* new_block(const OopStorage* owner);
static void delete_block(const Block& block);
void release_entries(uintx releasing, Block* volatile* deferred_list);
void release_entries(uintx releasing, OopStorage* owner);
template<typename F> bool iterate(F f);
template<typename F> bool iterate(F f) const;

@ -45,7 +45,7 @@
// locked. This prevents concurrent iteration and empty block deletion from
// interfering with with each other.
//
// Both allocate() and delete_empty_blocks_concurrent() lock the
// Both allocate() and delete_empty_blocks() lock the
// _allocation_mutex while performing their respective list and array
// manipulations, preventing them from interfering with each other.
//
@ -71,11 +71,6 @@
// iteration. To help with this, allocate() and release() have an invariant
// that an entry's value must be NULL when it is not in use.
//
// An in-progress delete_empty_blocks_concurrent() operation can contend with
// the start of a concurrent iteration over the _active_mutex. Since both are
// under GC control, that potential contention can be eliminated by never
// scheduling both operations to run at the same time.
//
// ParState<concurrent, is_const>
// concurrent must be true if iteration may be concurrent with the
// mutators.

@ -29,6 +29,7 @@
#include "classfile/systemDictionary.hpp"
#include "runtime/interfaceSupport.inline.hpp"
#include "runtime/javaCalls.hpp"
#include "runtime/jniHandles.hpp"
#include "runtime/serviceThread.hpp"
#include "runtime/mutexLocker.hpp"
#include "runtime/os.hpp"
@ -80,7 +81,39 @@ void ServiceThread::initialize() {
}
}
static bool needs_oopstorage_cleanup(OopStorage* const* storages,
bool* needs_cleanup,
size_t size) {
bool any_needs_cleanup = false;
for (size_t i = 0; i < size; ++i) {
assert(!needs_cleanup[i], "precondition");
if (storages[i]->needs_delete_empty_blocks()) {
needs_cleanup[i] = true;
any_needs_cleanup = true;
}
}
return any_needs_cleanup;
}
static void cleanup_oopstorages(OopStorage* const* storages,
const bool* needs_cleanup,
size_t size) {
for (size_t i = 0; i < size; ++i) {
if (needs_cleanup[i]) {
storages[i]->delete_empty_blocks();
}
}
}
void ServiceThread::service_thread_entry(JavaThread* jt, TRAPS) {
OopStorage* const oopstorages[] = {
JNIHandles::global_handles(),
JNIHandles::weak_global_handles(),
StringTable::weak_storage(),
SystemDictionary::vm_weak_oop_storage()
};
const size_t oopstorage_count = ARRAY_SIZE(oopstorages);
while (true) {
bool sensors_changed = false;
bool has_jvmti_events = false;
@ -90,6 +123,8 @@ void ServiceThread::service_thread_entry(JavaThread* jt, TRAPS) {
bool symboltable_work = false;
bool resolved_method_table_work = false;
bool protection_domain_table_work = false;
bool oopstorage_work = false;
bool oopstorages_cleanup[oopstorage_count] = {}; // Zero (false) initialize.
JvmtiDeferredEvent jvmti_event;
{
// Need state transition ThreadBlockInVM so that this thread
@ -102,7 +137,7 @@ void ServiceThread::service_thread_entry(JavaThread* jt, TRAPS) {
ThreadBlockInVM tbivm(jt);
MutexLockerEx ml(Service_lock, Mutex::_no_safepoint_check_flag);
MonitorLockerEx ml(Service_lock, Mutex::_no_safepoint_check_flag);
// Process all available work on each (outer) iteration, rather than
// only the first recognized bit of work, to avoid frequently true early
// tests from potentially starving later work. Hence the use of
@ -114,10 +149,14 @@ void ServiceThread::service_thread_entry(JavaThread* jt, TRAPS) {
(stringtable_work = StringTable::has_work()) |
(symboltable_work = SymbolTable::has_work()) |
(resolved_method_table_work = ResolvedMethodTable::has_work()) |
(protection_domain_table_work = SystemDictionary::pd_cache_table()->has_work()))
(protection_domain_table_work = SystemDictionary::pd_cache_table()->has_work()) |
(oopstorage_work = needs_oopstorage_cleanup(oopstorages,
oopstorages_cleanup,
oopstorage_count)))
== 0) {
// Wait until notified that there is some work to do.
Service_lock->wait(Mutex::_no_safepoint_check_flag);
ml.wait(Mutex::_no_safepoint_check_flag);
}
if (has_jvmti_events) {
@ -156,6 +195,10 @@ void ServiceThread::service_thread_entry(JavaThread* jt, TRAPS) {
if (protection_domain_table_work) {
SystemDictionary::pd_cache_table()->unlink();
}
if (oopstorage_work) {
cleanup_oopstorages(oopstorages, oopstorages_cleanup, oopstorage_count);
}
}
}

@ -216,8 +216,6 @@ public:
static const size_t _max_entries = 1000;
oop* _entries[_max_entries];
class VM_DeleteBlocksAtSafepoint;
};
OopStorageTestWithAllocation::OopStorageTestWithAllocation() {
@ -230,19 +228,6 @@ OopStorageTestWithAllocation::OopStorageTestWithAllocation() {
const size_t OopStorageTestWithAllocation::_max_entries;
class OopStorageTestWithAllocation::VM_DeleteBlocksAtSafepoint
: public VM_GTestExecuteAtSafepoint {
public:
VM_DeleteBlocksAtSafepoint(OopStorage* storage) : _storage(storage) {}
void doit() {
_storage->delete_empty_blocks_safepoint();
}
private:
OopStorage* _storage;
};
static bool is_allocation_list_sorted(const OopStorage& storage) {
// The allocation_list isn't strictly sorted. Rather, all empty
// blocks are segregated to the end of the list.
@ -1027,7 +1012,7 @@ TEST_VM_F(OopStorageTestParIteration, par_state_concurrent_const_oops_do) {
vstate.check();
}
TEST_VM_F(OopStorageTestWithAllocation, delete_empty_blocks_safepoint) {
TEST_VM_F(OopStorageTestWithAllocation, delete_empty_blocks) {
size_t initial_active_size = active_count(_storage);
EXPECT_EQ(initial_active_size, _storage.block_count());
ASSERT_LE(3u, initial_active_size); // Need at least 3 blocks for test
@ -1040,37 +1025,15 @@ TEST_VM_F(OopStorageTestWithAllocation, delete_empty_blocks_safepoint) {
EXPECT_EQ(initial_active_size, active_count(_storage));
EXPECT_EQ(initial_active_size, _storage.block_count());
EXPECT_EQ(3u, empty_block_count(_storage));
{
ThreadInVMfromNative invm(JavaThread::current());
VM_DeleteBlocksAtSafepoint op(&_storage);
VMThread::execute(&op);
while (_storage.delete_empty_blocks()) {}
}
EXPECT_EQ(0u, empty_block_count(_storage));
EXPECT_EQ(initial_active_size - 3, active_count(_storage));
EXPECT_EQ(initial_active_size - 3, _storage.block_count());
}
TEST_VM_F(OopStorageTestWithAllocation, delete_empty_blocks_concurrent) {
size_t initial_active_size = active_count(_storage);
EXPECT_EQ(initial_active_size, _storage.block_count());
ASSERT_LE(3u, initial_active_size); // Need at least 3 blocks for test
for (size_t i = 0; empty_block_count(_storage) < 3; ++i) {
ASSERT_GT(_max_entries, i);
release_entry(_storage, _entries[i]);
}
EXPECT_EQ(initial_active_size, active_count(_storage));
EXPECT_EQ(initial_active_size, _storage.block_count());
EXPECT_EQ(3u, empty_block_count(_storage));
_storage.delete_empty_blocks_concurrent();
EXPECT_EQ(0u, empty_block_count(_storage));
EXPECT_EQ(initial_active_size - 3, active_count(_storage));
EXPECT_EQ(initial_active_size - 3, _storage.block_count());
}
TEST_VM_F(OopStorageTestWithAllocation, allocation_status) {
oop* retained = _entries[200];
oop* released = _entries[300];
@ -1092,8 +1055,7 @@ TEST_VM_F(OopStorageTestWithAllocation, allocation_status) {
{
ThreadInVMfromNative invm(JavaThread::current());
VM_DeleteBlocksAtSafepoint op(&_storage);
VMThread::execute(&op);
while (_storage.delete_empty_blocks()) {}
}
EXPECT_EQ(OopStorage::ALLOCATED_ENTRY, _storage.allocation_status(retained));
#ifndef DISABLE_GARBAGE_ALLOCATION_STATUS_TESTS