Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

8256811: Delayed/missed jdwp class unloading events #9168

Closed
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
580d0ce
v0
Jun 14, 2022
1434ced
Add test
Jun 14, 2022
681cbb5
Update
Jun 14, 2022
911e8e5
Update
Jun 14, 2022
c1293f5
Remove empty line
Jun 14, 2022
d8e961c
Cleanup
Jun 14, 2022
b48ba1b
Update
Jun 14, 2022
abfe1aa
Remove redundant check
Jun 15, 2022
2fdfba9
v1
Jun 21, 2022
87a25b2
v2
Jun 21, 2022
44cc76a
v0
Jun 22, 2022
6bee14b
Merge branch 'jdi_tmp' into JDK-8256811-jdi-missing-class-unloading-e…
Jun 22, 2022
9edaea7
Merge branch 'master' into JDK-8256811-jdi-missing-class-unloading-event
Jun 22, 2022
a286c0c
v2
Jun 22, 2022
6cbe888
v3
Jun 22, 2022
89da788
v4
Jun 23, 2022
f521a03
Merge branch 'master' into JDK-8256811-jdi-missing-class-unloading-event
Jun 23, 2022
559b4bf
Improve naming and cleanup
Jun 23, 2022
2d9a81e
v5
Jun 27, 2022
bf334b1
Renamed eventHandler_synthesizeUnloadEvent
Jun 28, 2022
759e305
Removed HiddenClass test from Problem.txt and cleanup test
Jun 29, 2022
0b74710
Adding log for debugging test failure on Windows
Jun 29, 2022
4a2f49e
debug test
Jun 29, 2022
1c91f24
Use Shenandoah GC for debuggee for deterministic
Jun 29, 2022
e0338c4
Fix test
Jun 30, 2022
425521f
Moved TestClassUnloadEvents.java to new location
Jun 30, 2022
a07b373
Merge branch 'master' into JDK-8256811-jdi-missing-class-unloading-event
Jun 30, 2022
d265b76
Incorporated plummercj's test changes
Jul 1, 2022
b474927
Fix test to ensure class unloading
Jul 11, 2022
6fed417
plummercj's comment
Jul 11, 2022
c45dd04
Merge
Jul 11, 2022
9205633
Rename test
Jul 12, 2022
6f14f45
Fix race
Jul 12, 2022
6ef6c2f
Fix a bug
Jul 12, 2022
fce565d
Coleen's comments
Jul 18, 2022
902f0ff
Don't freeing objects and posting events from JvmtiEnv::GetObjectsWit…
Jul 21, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
13 changes: 9 additions & 4 deletions src/hotspot/share/prims/jvmtiExport.cpp
Expand Up @@ -1690,20 +1690,25 @@ void JvmtiExport::continuation_yield_cleanup(JavaThread* thread, jint continuati
}
}

void JvmtiExport::post_object_free(JvmtiEnv* env, jlong tag) {
Thread *thread = Thread::current();
void JvmtiExport::post_object_free(JvmtiEnv* env, GrowableArray<jlong>* objects) {
assert(objects != NULL, "Nothing to post");

if (thread->is_Java_thread() && JavaThread::cast(thread)->is_in_VTMS_transition()) {
JavaThread *javaThread = JavaThread::current();
if (javaThread->is_in_VTMS_transition()) {
return; // no events should be posted if thread is in a VTMS transition
}
zhengyu123 marked this conversation as resolved.
Show resolved Hide resolved
assert(env->is_enabled(JVMTI_EVENT_OBJECT_FREE), "checking");

EVT_TRIG_TRACE(JVMTI_EVENT_OBJECT_FREE, ("[?] Trg Object Free triggered" ));
EVT_TRACE(JVMTI_EVENT_OBJECT_FREE, ("[?] Evt Object Free sent"));

JvmtiThreadEventMark jem(javaThread);
JvmtiJavaThreadEventTransition jet(javaThread);
jvmtiEventObjectFree callback = env->callbacks()->ObjectFree;
if (callback != NULL) {
(*callback)(env->jvmti_external(), tag);
for (int index = 0; index < objects->length(); index++) {
(*callback)(env->jvmti_external(), objects->at(index));
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/hotspot/share/prims/jvmtiExport.hpp
Expand Up @@ -386,7 +386,7 @@ class JvmtiExport : public AllStatic {
static void post_monitor_contended_entered(JavaThread *thread, ObjectMonitor *obj_mntr) NOT_JVMTI_RETURN;
static void post_monitor_wait(JavaThread *thread, oop obj, jlong timeout) NOT_JVMTI_RETURN;
static void post_monitor_waited(JavaThread *thread, ObjectMonitor *obj_mntr, jboolean timed_out) NOT_JVMTI_RETURN;
static void post_object_free(JvmtiEnv* env, jlong tag) NOT_JVMTI_RETURN;
static void post_object_free(JvmtiEnv* env, GrowableArray<jlong>* objects) NOT_JVMTI_RETURN;
static void post_resource_exhausted(jint resource_exhausted_flags, const char* detail) NOT_JVMTI_RETURN;
static void record_vm_internal_object_allocation(oop object) NOT_JVMTI_RETURN;
// Post objects collected by vm_object_alloc_event_collector.
Expand Down
161 changes: 98 additions & 63 deletions src/hotspot/share/prims/jvmtiTagMap.cpp
Expand Up @@ -136,18 +136,16 @@ bool JvmtiTagMap::is_empty() {
}

// This checks for posting and rehashing before operations that
// this tagmap table. The calls from a JavaThread only rehash, posting is
// only done before heap walks.
void JvmtiTagMap::check_hashmap(bool post_events) {
assert(!post_events || SafepointSynchronize::is_at_safepoint(), "precondition");
// this tagmap table.
void JvmtiTagMap::check_hashmap(GrowableArray<jlong>* objects) {
assert(is_locked(), "checking");

if (is_empty()) { return; }

if (_needs_cleaning &&
post_events &&
objects != NULL &&
env()->is_enabled(JVMTI_EVENT_OBJECT_FREE)) {
remove_dead_entries_locked(true /* post_object_free */);
remove_dead_entries_locked(objects);
}
if (_needs_rehashing) {
log_info(jvmti, table)("TagMap table needs rehashing");
Expand All @@ -157,7 +155,7 @@ void JvmtiTagMap::check_hashmap(bool post_events) {
}

// This checks for posting and rehashing and is called from the heap walks.
void JvmtiTagMap::check_hashmaps_for_heapwalk() {
void JvmtiTagMap::check_hashmaps_for_heapwalk(GrowableArray<jlong>* objects) {
assert(SafepointSynchronize::is_at_safepoint(), "called from safepoints");

// Verify that the tag map tables are valid and unconditionally post events
Expand All @@ -168,7 +166,7 @@ void JvmtiTagMap::check_hashmaps_for_heapwalk() {
if (tag_map != NULL) {
// The ZDriver may be walking the hashmaps concurrently so this lock is needed.
MutexLocker ml(tag_map->lock(), Mutex::_no_safepoint_check_flag);
tag_map->check_hashmap(/*post_events*/ true);
tag_map->check_hashmap(objects);
}
}
}
Expand Down Expand Up @@ -359,7 +357,7 @@ void JvmtiTagMap::set_tag(jobject object, jlong tag) {
// SetTag should not post events because the JavaThread has to
// transition to native for the callback and this cannot stop for
// safepoints with the hashmap lock held.
check_hashmap(/*post_events*/ false);
check_hashmap(NULL); /* don't collect dead objects */

// resolve the object
oop o = JNIHandles::resolve_non_null(object);
Expand Down Expand Up @@ -394,7 +392,7 @@ jlong JvmtiTagMap::get_tag(jobject object) {
// GetTag should not post events because the JavaThread has to
// transition to native for the callback and this cannot stop for
// safepoints with the hashmap lock held.
check_hashmap(/*post_events*/ false);
check_hashmap(NULL); /* don't collect dead objects */

// resolve the object
oop o = JNIHandles::resolve_non_null(object);
Expand Down Expand Up @@ -889,15 +887,17 @@ static jint invoke_primitive_field_callback_for_instance_fields(
class VM_HeapIterateOperation: public VM_Operation {
private:
ObjectClosure* _blk;
GrowableArray<jlong>* const _dead_objects;
public:
VM_HeapIterateOperation(ObjectClosure* blk) { _blk = blk; }
VM_HeapIterateOperation(ObjectClosure* blk, GrowableArray<jlong>* objects) :
_blk(blk), _dead_objects(objects) { }

VMOp_Type type() const { return VMOp_HeapIterateOperation; }
void doit() {
// allows class files maps to be cached during iteration
ClassFieldMapCacheMark cm;

JvmtiTagMap::check_hashmaps_for_heapwalk();
JvmtiTagMap::check_hashmaps_for_heapwalk(_dead_objects);

// make sure that heap is parsable (fills TLABs with filler objects)
Universe::heap()->ensure_parsability(false); // no need to retire TLABs
Expand All @@ -911,7 +911,6 @@ class VM_HeapIterateOperation: public VM_Operation {
// do the iteration
Universe::heap()->object_iterate(_blk);
}

};


Expand Down Expand Up @@ -1142,8 +1141,11 @@ void JvmtiTagMap::iterate_over_heap(jvmtiHeapObjectFilter object_filter,
object_filter,
heap_object_callback,
user_data);
VM_HeapIterateOperation op(&blk);
Arena dead_object_arena(mtInternal);
zhengyu123 marked this conversation as resolved.
Show resolved Hide resolved
GrowableArray<jlong> dead_objects(&dead_object_arena, 10, 0, 0);
VM_HeapIterateOperation op(&blk, &dead_objects);
VMThread::execute(&op);
post_dead_objects(&dead_objects);
}


Expand All @@ -1157,50 +1159,55 @@ void JvmtiTagMap::iterate_through_heap(jint heap_filter,
// disabled if vritual threads are enabled with --enable-preview
EscapeBarrier eb(!(heap_filter & JVMTI_HEAP_FILTER_UNTAGGED), JavaThread::current());
eb.deoptimize_objects_all_threads();
MutexLocker ml(Heap_lock);
IterateThroughHeapObjectClosure blk(this,
klass,
heap_filter,
callbacks,
user_data);
VM_HeapIterateOperation op(&blk);
VMThread::execute(&op);

Arena dead_object_arena(mtInternal);
GrowableArray<jlong> dead_objects(&dead_object_arena, 10, 0, 0);
{
MutexLocker ml(Heap_lock);
IterateThroughHeapObjectClosure blk(this,
klass,
heap_filter,
callbacks,
user_data);
VM_HeapIterateOperation op(&blk, &dead_objects);
VMThread::execute(&op);
}
// Post events outside of Heap_lock
post_dead_objects(&dead_objects);
}

void JvmtiTagMap::remove_dead_entries_locked(bool post_object_free) {
void JvmtiTagMap::remove_dead_entries_locked(GrowableArray<jlong>* objects) {
assert(is_locked(), "precondition");
if (_needs_cleaning) {
// Recheck whether to post object free events under the lock.
post_object_free = post_object_free && env()->is_enabled(JVMTI_EVENT_OBJECT_FREE);
if (!env()->is_enabled(JVMTI_EVENT_OBJECT_FREE)) {
objects = NULL;
}
log_info(jvmti, table)("TagMap table needs cleaning%s",
(post_object_free ? " and posting" : ""));
hashmap()->remove_dead_entries(env(), post_object_free);
((objects != NULL) ? " and posting" : ""));
hashmap()->remove_dead_entries(objects);
_needs_cleaning = false;
}
}

void JvmtiTagMap::remove_dead_entries(bool post_object_free) {
void JvmtiTagMap::remove_dead_entries(GrowableArray<jlong>* objects) {
MutexLocker ml(lock(), Mutex::_no_safepoint_check_flag);
remove_dead_entries_locked(post_object_free);
remove_dead_entries_locked(objects);
}

class VM_JvmtiPostObjectFree: public VM_Operation {
JvmtiTagMap* _tag_map;
public:
VM_JvmtiPostObjectFree(JvmtiTagMap* tag_map) : _tag_map(tag_map) {}
VMOp_Type type() const { return VMOp_Cleanup; }
void doit() {
_tag_map->remove_dead_entries(true /* post_object_free */);
void JvmtiTagMap::post_dead_objects(GrowableArray<jlong>* const objects) {
assert(Thread::current()->is_Java_thread(), "Must post from JavaThread");
if (objects != NULL && objects->length() > 0) {
JvmtiExport::post_object_free(env(), objects);
log_info(jvmti)("%d free object posted", objects->length());
}
}

// Doesn't need a safepoint, just the VM thread
virtual bool evaluate_at_safepoint() const { return false; }
};

// PostObjectFree can't be called by JavaThread, so call it from the VM thread.
zhengyu123 marked this conversation as resolved.
Show resolved Hide resolved
void JvmtiTagMap::post_dead_objects_on_vm_thread() {
VM_JvmtiPostObjectFree op(this);
VMThread::execute(&op);
void JvmtiTagMap::remove_and_post_dead_objects() {
ResourceMark rm;
GrowableArray<jlong> objects;
remove_dead_entries(&objects);
post_dead_objects(&objects);
}

void JvmtiTagMap::flush_object_free_events() {
Expand All @@ -1215,9 +1222,9 @@ void JvmtiTagMap::flush_object_free_events() {
} // Drop the lock so we can do the cleaning on the VM thread.
// Needs both cleaning and event posting (up to some other thread
// getting there first after we dropped the lock).
post_dead_objects_on_vm_thread();
remove_and_post_dead_objects();
} else {
remove_dead_entries(false);
remove_dead_entries(NULL);
}
}

Expand Down Expand Up @@ -1330,7 +1337,7 @@ jvmtiError JvmtiTagMap::get_objects_with_tags(const jlong* tags,
entry_iterate(&collector);
}
if (collector.some_dead_found() && env()->is_enabled(JVMTI_EVENT_OBJECT_FREE)) {
post_dead_objects_on_vm_thread();
remove_and_post_dead_objects();
}
return collector.result(count_ptr, object_result_ptr, tag_result_ptr);
}
Expand Down Expand Up @@ -2252,6 +2259,9 @@ class VM_HeapWalkOperation: public VM_Operation {

JVMTIBitSet _bitset;

// Dead object tags in JvmtiTagMap
GrowableArray<jlong>* _dead_objects;

bool _following_object_refs; // are we following object references

bool _reporting_primitive_fields; // optional reporting
Expand Down Expand Up @@ -2293,12 +2303,14 @@ class VM_HeapWalkOperation: public VM_Operation {
VM_HeapWalkOperation(JvmtiTagMap* tag_map,
Handle initial_object,
BasicHeapWalkContext callbacks,
const void* user_data);
const void* user_data,
GrowableArray<jlong>* objects);

VM_HeapWalkOperation(JvmtiTagMap* tag_map,
Handle initial_object,
AdvancedHeapWalkContext callbacks,
const void* user_data);
const void* user_data,
GrowableArray<jlong>* objects);

~VM_HeapWalkOperation();

Expand All @@ -2310,7 +2322,8 @@ class VM_HeapWalkOperation: public VM_Operation {
VM_HeapWalkOperation::VM_HeapWalkOperation(JvmtiTagMap* tag_map,
Handle initial_object,
BasicHeapWalkContext callbacks,
const void* user_data) {
const void* user_data,
GrowableArray<jlong>* objects) {
_is_advanced_heap_walk = false;
_tag_map = tag_map;
_initial_object = initial_object;
Expand All @@ -2319,14 +2332,16 @@ VM_HeapWalkOperation::VM_HeapWalkOperation(JvmtiTagMap* tag_map,
_reporting_primitive_array_values = false;
_reporting_string_values = false;
_visit_stack = create_visit_stack();
_dead_objects = objects;

CallbackInvoker::initialize_for_basic_heap_walk(tag_map, _visit_stack, user_data, callbacks, &_bitset);
}

VM_HeapWalkOperation::VM_HeapWalkOperation(JvmtiTagMap* tag_map,
Handle initial_object,
AdvancedHeapWalkContext callbacks,
const void* user_data) {
const void* user_data,
GrowableArray<jlong>* objects) {
_is_advanced_heap_walk = true;
_tag_map = tag_map;
_initial_object = initial_object;
Expand All @@ -2335,6 +2350,7 @@ VM_HeapWalkOperation::VM_HeapWalkOperation(JvmtiTagMap* tag_map,
_reporting_primitive_array_values = (callbacks.array_primitive_value_callback() != NULL);;
_reporting_string_values = (callbacks.string_primitive_value_callback() != NULL);;
_visit_stack = create_visit_stack();
_dead_objects = objects;
CallbackInvoker::initialize_for_advanced_heap_walk(tag_map, _visit_stack, user_data, callbacks, &_bitset);
}

Expand Down Expand Up @@ -2803,7 +2819,7 @@ void VM_HeapWalkOperation::doit() {
ResourceMark rm;
ClassFieldMapCacheMark cm;

JvmtiTagMap::check_hashmaps_for_heapwalk();
JvmtiTagMap::check_hashmaps_for_heapwalk(_dead_objects);

assert(visit_stack()->is_empty(), "visit stack must be empty");

Expand Down Expand Up @@ -2842,10 +2858,16 @@ void JvmtiTagMap::iterate_over_reachable_objects(jvmtiHeapRootCallback heap_root
JavaThread* jt = JavaThread::current();
EscapeBarrier eb(true, jt);
eb.deoptimize_objects_all_threads();
MutexLocker ml(Heap_lock);
BasicHeapWalkContext context(heap_root_callback, stack_ref_callback, object_ref_callback);
VM_HeapWalkOperation op(this, Handle(), context, user_data);
VMThread::execute(&op);
Arena dead_object_arena(mtInternal);
GrowableArray<jlong> dead_objects(&dead_object_arena, 10, 0, 0);
{
MutexLocker ml(Heap_lock);
BasicHeapWalkContext context(heap_root_callback, stack_ref_callback, object_ref_callback);
VM_HeapWalkOperation op(this, Handle(), context, user_data, &dead_objects);
VMThread::execute(&op);
}
// Post events outside of Heap_lock
post_dead_objects(&dead_objects);
}

// iterate over all objects that are reachable from a given object
Expand All @@ -2855,10 +2877,16 @@ void JvmtiTagMap::iterate_over_objects_reachable_from_object(jobject object,
oop obj = JNIHandles::resolve(object);
Handle initial_object(Thread::current(), obj);

MutexLocker ml(Heap_lock);
BasicHeapWalkContext context(NULL, NULL, object_ref_callback);
VM_HeapWalkOperation op(this, initial_object, context, user_data);
VMThread::execute(&op);
Arena dead_object_arena(mtInternal);
GrowableArray<jlong> dead_objects(&dead_object_arena, 10, 0, 0);
{
MutexLocker ml(Heap_lock);
BasicHeapWalkContext context(NULL, NULL, object_ref_callback);
VM_HeapWalkOperation op(this, initial_object, context, user_data, &dead_objects);
VMThread::execute(&op);
}
// Post events outside of Heap_lock
post_dead_objects(&dead_objects);
}

// follow references from an initial object or the GC roots
Expand All @@ -2876,10 +2904,17 @@ void JvmtiTagMap::follow_references(jint heap_filter,
!(heap_filter & JVMTI_HEAP_FILTER_UNTAGGED),
jt);
eb.deoptimize_objects_all_threads();
MutexLocker ml(Heap_lock);
AdvancedHeapWalkContext context(heap_filter, klass, callbacks);
VM_HeapWalkOperation op(this, initial_object, context, user_data);
VMThread::execute(&op);

Arena dead_object_arena(mtInternal);
GrowableArray<jlong> dead_objects(&dead_object_arena, 10, 0, 0);
{
MutexLocker ml(Heap_lock);
AdvancedHeapWalkContext context(heap_filter, klass, callbacks);
VM_HeapWalkOperation op(this, initial_object, context, user_data, &dead_objects);
VMThread::execute(&op);
}
// Post events outside of Heap_lock
post_dead_objects(&dead_objects);
}

// Concurrent GC needs to call this in relocation pause, so after the objects are moved
Expand Down