8306843: JVMTI tag map extremely slow after JDK-8292741

Reviewed-by: sspitsyn, iklam
This commit is contained in:
Coleen Phillimore 2023-05-10 12:32:06 +00:00
parent ab34cb98c8
commit 4251b56214
8 changed files with 286 additions and 84 deletions

@ -659,12 +659,11 @@ class VerifyCompStrings : StackObj {
return java_lang_String::equals(a, b);
}
ResizeableResourceHashtable<oop, bool,
AnyObj::C_HEAP, mtInternal,
ResizeableResourceHashtable<oop, bool, AnyObj::C_HEAP, mtInternal,
string_hash, string_equals> _table;
public:
size_t _errors;
VerifyCompStrings() : _table(unsigned(_items_count / 8) + 1), _errors(0) {}
VerifyCompStrings() : _table(unsigned(_items_count / 8) + 1, 0 /* do not resize */), _errors(0) {}
bool operator()(WeakHandle* val) {
oop s = val->resolve();
if (s == nullptr) {

@ -241,23 +241,14 @@ class CallbackWrapper : public StackObj {
void inline CallbackWrapper::post_callback_tag_update(oop o,
JvmtiTagMapTable* hashmap,
jlong obj_tag) {
jlong current_tag = hashmap->find(o);
if (current_tag == 0) {
if (obj_tag != 0) {
// callback has tagged the object
assert(Thread::current()->is_VM_thread(), "must be VMThread");
hashmap->add(o, obj_tag);
}
if (obj_tag == 0) {
// callback has untagged the object, remove the entry if present
hashmap->remove(o);
} else {
// object was previously tagged - the callback may have untagged
// the object or changed the tag value
if (obj_tag == 0) {
hashmap->remove(o);
} else {
if (obj_tag != current_tag) {
hashmap->update(o, obj_tag);
}
}
// object was previously tagged or not present - the callback may have
// changed the tag value
assert(Thread::current()->is_VM_thread(), "must be VMThread");
hashmap->add(o, obj_tag);
}
}
@ -347,24 +338,14 @@ void JvmtiTagMap::set_tag(jobject object, jlong tag) {
// see if the object is already tagged
JvmtiTagMapTable* hashmap = _hashmap;
jlong found_tag = hashmap->find(o);
// if the object is not already tagged then we tag it
if (found_tag == 0) {
if (tag != 0) {
hashmap->add(o, tag);
} else {
// no-op
}
if (tag == 0) {
// remove the entry if present
hashmap->remove(o);
} else {
// if the object is already tagged then we either update
// the tag (if a new tag value has been provided)
// or remove the object if the new tag value is 0.
if (tag == 0) {
hashmap->remove(o);
} else {
hashmap->update(o, tag);
}
// if the object is already tagged or not present then we add/update
// the tag
hashmap->add(o, tag);
}
}

@ -35,25 +35,23 @@ JvmtiTagMapKey::JvmtiTagMapKey(oop obj) : _obj(obj) {}
JvmtiTagMapKey::JvmtiTagMapKey(const JvmtiTagMapKey& src) {
// move object into WeakHandle when copying into the table
assert(src._obj != nullptr, "must be set");
if (src._obj != nullptr) {
// obj was read with AS_NO_KEEPALIVE, or equivalent, like during
// a heap walk. The object needs to be kept alive when it is published.
Universe::heap()->keep_alive(src._obj);
// obj was read with AS_NO_KEEPALIVE, or equivalent, like during
// a heap walk. The object needs to be kept alive when it is published.
Universe::heap()->keep_alive(src._obj);
_wh = WeakHandle(JvmtiExport::weak_tag_storage(), src._obj);
_wh = WeakHandle(JvmtiExport::weak_tag_storage(), src._obj);
} else {
// resizing needs to create a copy.
_wh = src._wh;
}
// obj is always null after a copy.
_obj = nullptr;
}
JvmtiTagMapKey::~JvmtiTagMapKey() {
// If obj is set null it out, this is called for stack object on lookup,
// and it should not have a WeakHandle created for it yet.
if (_obj != nullptr) {
_obj = nullptr;
assert(_wh.is_null(), "WeakHandle should be null");
} else {
_wh.release(JvmtiExport::weak_tag_storage());
}
void JvmtiTagMapKey::release_weak_handle() const {
_wh.release(JvmtiExport::weak_tag_storage());
}
oop JvmtiTagMapKey::object() const {
@ -66,11 +64,15 @@ oop JvmtiTagMapKey::object_no_keepalive() const {
return _wh.peek();
}
JvmtiTagMapTable::JvmtiTagMapTable() : _table(Constants::_table_size) {}
static const int INITIAL_TABLE_SIZE = 1007;
static const int MAX_TABLE_SIZE = 0x3fffffff;
JvmtiTagMapTable::JvmtiTagMapTable() : _table(INITIAL_TABLE_SIZE, MAX_TABLE_SIZE) {}
void JvmtiTagMapTable::clear() {
struct RemoveAll {
bool do_entry(const JvmtiTagMapKey& entry, const jlong& tag) {
entry.release_weak_handle();
return true;
}
} remove_all;
@ -104,31 +106,35 @@ jlong JvmtiTagMapTable::find(oop obj) {
void JvmtiTagMapTable::add(oop obj, jlong tag) {
JvmtiTagMapKey new_entry(obj);
bool is_added = false;
_table.put_if_absent(new_entry, tag, &is_added);
assert(is_added, "should be added");
}
void JvmtiTagMapTable::update(oop obj, jlong tag) {
JvmtiTagMapKey new_entry(obj);
bool is_updated = _table.put(new_entry, tag) == false;
assert(is_updated, "should be updated and not added");
bool is_added;
if (obj->fast_no_hash_check()) {
// Can't be in the table so add it fast.
is_added = _table.put_when_absent(new_entry, tag);
} else {
jlong* value = _table.put_if_absent(new_entry, tag, &is_added);
*value = tag; // assign the new tag
}
if (is_added) {
if (_table.maybe_grow(5, true /* use_large_table_sizes */)) {
int max_bucket_size = DEBUG_ONLY(_table.verify()) NOT_DEBUG(0);
log_info(jvmti, table) ("JvmtiTagMap table resized to %d for %d entries max bucket %d",
_table.table_size(), _table.number_of_entries(), max_bucket_size);
}
}
}
void JvmtiTagMapTable::remove(oop obj) {
JvmtiTagMapKey jtme(obj);
bool is_removed = _table.remove(jtme);
assert(is_removed, "remove not succesfull.");
auto clean = [] (const JvmtiTagMapKey& entry, jlong tag) {
entry.release_weak_handle();
};
_table.remove(jtme, clean);
}
void JvmtiTagMapTable::entry_iterate(JvmtiTagMapKeyClosure* closure) {
_table.iterate(closure);
}
void JvmtiTagMapTable::resize_if_needed() {
_table.maybe_grow();
}
void JvmtiTagMapTable::remove_dead_entries(GrowableArray<jlong>* objects) {
struct IsDead {
GrowableArray<jlong>* _objects;
@ -138,6 +144,7 @@ void JvmtiTagMapTable::remove_dead_entries(GrowableArray<jlong>* objects) {
if (_objects != nullptr) {
_objects->append(tag);
}
entry.release_weak_handle();
return true;
}
return false;;

@ -48,11 +48,9 @@ class JvmtiTagMapKey : public CHeapObj<mtServiceability> {
JvmtiTagMapKey(const JvmtiTagMapKey& src);
JvmtiTagMapKey& operator=(const JvmtiTagMapKey&) = delete;
~JvmtiTagMapKey();
void resolve();
oop object() const;
oop object_no_keepalive() const;
void release_weak_handle() const;
static unsigned get_hash(const JvmtiTagMapKey& entry) {
assert(entry._obj != nullptr, "must lookup obj to hash");
@ -73,12 +71,7 @@ ResizeableResourceHashtable <JvmtiTagMapKey, jlong,
JvmtiTagMapKey::equals> ResizableResourceHT;
class JvmtiTagMapTable : public CHeapObj<mtServiceability> {
enum Constants {
_table_size = 1007
};
private:
void resize_if_needed();
ResizableResourceHT _table;
public:
@ -87,7 +80,6 @@ class JvmtiTagMapTable : public CHeapObj<mtServiceability> {
jlong find(oop obj);
void add(oop obj, jlong tag);
void update(oop obj, jlong tag);
void remove(oop obj);

@ -85,19 +85,44 @@ class ResizeableResourceHashtable : public ResourceHashtableBase<
K, V, ALLOC_TYPE, MEM_TYPE, HASH, EQUALS>;
using Node = ResourceHashtableNode<K, V>;
NONCOPYABLE(ResizeableResourceHashtable);
// Calculate next "good" hashtable size based on requested count
int calculate_resize(bool use_large_table_sizes) const {
const int resize_factor = 2; // by how much we will resize using current number of entries
// possible hashmap sizes - odd primes that roughly double in size.
// To avoid excessive resizing the odd primes from 4801-76831 and
// 76831-307261 have been removed.
const int large_table_sizes[] = { 107, 1009, 2017, 4049, 5051, 10103, 20201,
40423, 76831, 307261, 614563, 1228891, 2457733,
4915219, 9830479, 19660831, 39321619, 78643219 };
const int large_array_size = sizeof(large_table_sizes)/sizeof(int);
int requested = resize_factor * BASE::number_of_entries();
int start_at = use_large_table_sizes ? 8 : 0;
int newsize;
for (int i = start_at; i < large_array_size; i++) {
newsize = large_table_sizes[i];
if (newsize >= requested) {
return newsize;
}
}
return requested; // greater than a size in the table
}
public:
ResizeableResourceHashtable(unsigned size, unsigned max_size = 0)
ResizeableResourceHashtable(unsigned size, unsigned max_size)
: BASE(size), _max_size(max_size) {
assert(size <= 0x3fffffff && max_size <= 0x3fffffff, "avoid overflow in resize");
}
bool maybe_grow(int load_factor = 8) {
bool maybe_grow(int load_factor = 8, bool use_large_table_sizes = false) {
unsigned old_size = BASE::_table_size;
if (old_size >= _max_size) {
return false;
}
if (BASE::number_of_entries() / int(old_size) > load_factor) {
unsigned new_size = MIN2<unsigned>(old_size * 2, _max_size);
unsigned new_size = MIN2<unsigned>(calculate_resize(use_large_table_sizes), _max_size);
resize(new_size);
return true;
} else {
@ -114,7 +139,7 @@ public:
Node* node = *bucket;
while (node != nullptr) {
Node* next = node->_next;
unsigned hash = HASH(node->_key);
unsigned hash = node->_hash;
unsigned index = hash % new_size;
node->_next = new_table[index];
@ -131,6 +156,28 @@ public:
BASE::_table = new_table;
BASE::_table_size = new_size;
}
#ifdef ASSERT
int verify() {
Node** table = BASE::_table;
// Return max bucket size. If hashcode is broken, this will be
// too high.
int max_bucket_size = 0;
int index = 0;
Node* const* bucket = table;
while (bucket < &table[BASE::_table_size]) {
int count = 0;
Node* node = *bucket;
while (node != nullptr) {
count++;
node = node->_next;
}
max_bucket_size = MAX2(count, max_bucket_size);
++bucket;
}
return max_bucket_size;
}
#endif // ASSERT
};
#endif // SHARE_UTILITIES_RESIZEABLERESOURCEHASH_HPP

@ -40,12 +40,14 @@ public:
V _value;
ResourceHashtableNode* _next;
ResourceHashtableNode(unsigned hash, K const& key, V const& value) :
_hash(hash), _key(key), _value(value), _next(nullptr) {}
ResourceHashtableNode(unsigned hash, K const& key, V const& value,
ResourceHashtableNode* next = nullptr) :
_hash(hash), _key(key), _value(value), _next(next) {}
// Create a node with a default-constructed value.
ResourceHashtableNode(unsigned hash, K const& key) :
_hash(hash), _key(key), _value(), _next(nullptr) {}
ResourceHashtableNode(unsigned hash, K const& key,
ResourceHashtableNode* next = nullptr) :
_hash(hash), _key(key), _value(), _next(next) {}
};
template<
@ -136,6 +138,29 @@ class ResourceHashtableBase : public STORAGE {
}
}
/**
* Inserts a value in the front of the table, assuming that
* the entry is absent.
* The table must be locked for the get or test that the entry
* is absent, and for this operation.
* This is a faster variant of put_if_absent because it adds to the
* head of the bucket, and doesn't search the bucket.
* @return: true: a new item is always added
*/
bool put_when_absent(K const& key, V const& value) {
unsigned hv = HASH(key);
unsigned index = hv % table_size();
assert(*lookup_node(hv, key) == nullptr, "use put_if_absent");
Node** ptr = bucket_at(index);
if (ALLOC_TYPE == AnyObj::C_HEAP) {
*ptr = new (MEM_TYPE) Node(hv, key, value, *ptr);
} else {
*ptr = new Node(hv, key, value, *ptr);
}
_number_of_entries ++;
return true;
}
/**
* Inserts or replaces a value in the table.
* @return: true: if a new item is added
@ -202,14 +227,15 @@ class ResourceHashtableBase : public STORAGE {
return &(*ptr)->_value;
}
bool remove(K const& key) {
template<typename Function>
bool remove(K const& key, Function function) {
unsigned hv = HASH(key);
Node** ptr = lookup_node(hv, key);
Node* node = *ptr;
if (node != nullptr) {
*ptr = node->_next;
function(node->_key, node->_value);
if (ALLOC_TYPE == AnyObj::C_HEAP) {
delete node;
}
@ -219,6 +245,11 @@ class ResourceHashtableBase : public STORAGE {
return false;
}
bool remove(K const& key) {
auto dummy = [&] (K& k, V& v) { };
return remove(key, dummy);
}
// ITER contains bool do_entry(K const&, V const&), which will be
// called for each entry in the table. If do_entry() returns false,
// the iteration is cancelled.

@ -0,0 +1,71 @@
/*
* 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.
*/
/*
* @test
* @bug 8306843
* @summary Test that 10M tags doesn't time out.
* @requires vm.jvmti
* @run main/othervm/native -agentlib:TagMapTest
* -Xlog:jvmti+table
* TagMapTest
*/
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
public class TagMapTest {
private static final List<TagMapTest> items = new ArrayList<>();
private static native void setTag(Object object);
private static native long getTag(Object object);
private static native void iterate(boolean tagged);
public static void main(String[] args) {
System.loadLibrary("TagMapTest");
for (int i = 0; i < 10_000_000; i++) {
items.add(new TagMapTest());
}
long startTime = System.nanoTime();
for (TagMapTest item : items) {
setTag(item);
}
System.out.println("setTag: " + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime) + " ms");
startTime = System.nanoTime();
for (TagMapTest item : items) {
getTag(item);
}
System.out.println("getTag: " + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime) + " ms");
startTime = System.nanoTime();
iterate(true);
System.out.println("iterate tagged: " + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime) + " ms");
startTime = System.nanoTime();
iterate(false);
System.out.println("iterate all: " + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime) + " ms");
}
}

@ -0,0 +1,74 @@
/*
* 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 <jvmti.h>
#include <cstdlib>
#include <cstring>
namespace {
jlong nextTag = 1;
jvmtiEnv *jvmti = NULL;
void checkJvmti(int code, const char* message) {
if (code != JVMTI_ERROR_NONE) {
printf("Error %s: %d\n", message, code);
abort();
}
}
jvmtiIterationControl JNICALL heapObjectCallback(jlong class_tag, jlong size, jlong* tag_ptr, void* user_data) {
if (*tag_ptr == 0) {
*tag_ptr = nextTag++;
}
return JVMTI_ITERATION_CONTINUE;
}
}
extern "C" JNIEXPORT void JNICALL Java_TagMapTest_setTag(JNIEnv* jni_env, jclass clazz, jobject object) {
checkJvmti(jvmti->SetTag(object, nextTag++), "could not set tag");
}
extern "C" JNIEXPORT jlong JNICALL Java_TagMapTest_getTag(JNIEnv* jni_env, jclass clazz, jobject object) {
jlong tag;
checkJvmti(jvmti->GetTag(object, &tag), "could not get tag");
return tag;
}
extern "C" JNIEXPORT void JNICALL Java_TagMapTest_iterate(JNIEnv* jni_env, jclass clazz, jboolean tagged) {
checkJvmti(jvmti->IterateOverHeap(tagged ? JVMTI_HEAP_OBJECT_TAGGED : JVMTI_HEAP_OBJECT_EITHER, &heapObjectCallback, NULL), "could not iterate");
}
extern "C" JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *vm, char *options, void *reserved) {
if (vm->GetEnv(reinterpret_cast<void **>(&jvmti), JVMTI_VERSION) != JNI_OK || !jvmti) {
printf("Could not initialize JVMTI\n");
abort();
}
jvmtiCapabilities capabilities;
memset(&capabilities, 0, sizeof(capabilities));
capabilities.can_tag_objects = 1;
checkJvmti(jvmti->AddCapabilities(&capabilities), "adding capabilities");
printf("Loaded agent\n");
fflush(stdout);
return JVMTI_ERROR_NONE;
}