diff --git a/src/hotspot/share/prims/jvmtiExport.cpp b/src/hotspot/share/prims/jvmtiExport.cpp index ceab8da5c49..15d7847d8a4 100644 --- a/src/hotspot/share/prims/jvmtiExport.cpp +++ b/src/hotspot/share/prims/jvmtiExport.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2022, 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 @@ -1490,15 +1490,21 @@ void JvmtiExport::post_thread_end(JavaThread *thread) { } } -void JvmtiExport::post_object_free(JvmtiEnv* env, jlong tag) { +void JvmtiExport::post_object_free(JvmtiEnv* env, GrowableArray* objects) { + assert(objects != NULL, "Nothing to post"); 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")); + JavaThread* javaThread = JavaThread::current(); + 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)); + } } } diff --git a/src/hotspot/share/prims/jvmtiExport.hpp b/src/hotspot/share/prims/jvmtiExport.hpp index fd9e897e648..5d583178a0b 100644 --- a/src/hotspot/share/prims/jvmtiExport.hpp +++ b/src/hotspot/share/prims/jvmtiExport.hpp @@ -370,7 +370,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* 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. diff --git a/src/hotspot/share/prims/jvmtiTagMap.cpp b/src/hotspot/share/prims/jvmtiTagMap.cpp index e54e526a44c..68228f5141b 100644 --- a/src/hotspot/share/prims/jvmtiTagMap.cpp +++ b/src/hotspot/share/prims/jvmtiTagMap.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2022, 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 @@ -76,7 +76,8 @@ JvmtiTagMap::JvmtiTagMap(JvmtiEnv* env) : _lock(Mutex::nonleaf+1, "JvmtiTagMap_lock", Mutex::_allow_vm_block_flag, Mutex::_safepoint_check_never), _needs_rehashing(false), - _needs_cleaning(false) { + _needs_cleaning(false), + _posting_events(false) { assert(JvmtiThreadState_lock->is_locked(), "sanity check"); assert(((JvmtiEnvBase *)env)->tag_map() == NULL, "tag map already exists for environment"); @@ -135,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* 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"); @@ -156,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* objects) { assert(SafepointSynchronize::is_at_safepoint(), "called from safepoints"); // Verify that the tag map tables are valid and unconditionally post events @@ -167,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); } } } @@ -358,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); @@ -393,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); @@ -888,15 +887,17 @@ static jint invoke_primitive_field_callback_for_instance_fields( class VM_HeapIterateOperation: public VM_Operation { private: ObjectClosure* _blk; + GrowableArray* const _dead_objects; public: - VM_HeapIterateOperation(ObjectClosure* blk) { _blk = blk; } + VM_HeapIterateOperation(ObjectClosure* blk, GrowableArray* 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 @@ -1135,14 +1136,20 @@ void JvmtiTagMap::iterate_over_heap(jvmtiHeapObjectFilter object_filter, object_filter == JVMTI_HEAP_OBJECT_EITHER, JavaThread::current()); eb.deoptimize_objects_all_threads(); - MutexLocker ml(Heap_lock); - IterateOverHeapObjectClosure blk(this, - klass, - object_filter, - heap_object_callback, - user_data); - VM_HeapIterateOperation op(&blk); - VMThread::execute(&op); + Arena dead_object_arena(mtServiceability); + GrowableArray dead_objects(&dead_object_arena, 10, 0, 0); + { + MutexLocker ml(Heap_lock); + IterateOverHeapObjectClosure blk(this, + klass, + object_filter, + heap_object_callback, + user_data); + VM_HeapIterateOperation op(&blk, &dead_objects); + VMThread::execute(&op); + } + // Post events outside of Heap_lock + post_dead_objects(&dead_objects); } @@ -1155,67 +1162,83 @@ void JvmtiTagMap::iterate_through_heap(jint heap_filter, // EA based optimizations on tagged objects are already reverted. 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(mtServiceability); + GrowableArray 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* 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* 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* 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. -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 objects; + remove_dead_entries(&objects); + post_dead_objects(&objects); } void JvmtiTagMap::flush_object_free_events() { assert_not_at_safepoint(); if (env()->is_enabled(JVMTI_EVENT_OBJECT_FREE)) { { - MutexLocker ml(lock(), Mutex::_no_safepoint_check_flag); + MonitorLocker ml(lock(), Mutex::_no_safepoint_check_flag); + // If another thread is posting events, let it finish + while (_posting_events) { + ml.wait(); + } + if (!_needs_cleaning || is_empty()) { _needs_cleaning = false; return; } + _posting_events = true; } // 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(); + { + MonitorLocker ml(lock(), Mutex::_no_safepoint_check_flag); + _posting_events = false; + ml.notify_all(); + } } else { - remove_dead_entries(false); + remove_dead_entries(NULL); } } @@ -1327,9 +1350,6 @@ jvmtiError JvmtiTagMap::get_objects_with_tags(const jlong* tags, // it is collected yet. entry_iterate(&collector); } - if (collector.some_dead_found() && env()->is_enabled(JVMTI_EVENT_OBJECT_FREE)) { - post_dead_objects_on_vm_thread(); - } return collector.result(count_ptr, object_result_ptr, tag_result_ptr); } @@ -2376,6 +2396,9 @@ class VM_HeapWalkOperation: public VM_Operation { Handle _initial_object; GrowableArray* _visit_stack; // the visit stack + // Dead object tags in JvmtiTagMap + GrowableArray* _dead_objects; + bool _following_object_refs; // are we following object references bool _reporting_primitive_fields; // optional reporting @@ -2417,12 +2440,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* objects); VM_HeapWalkOperation(JvmtiTagMap* tag_map, Handle initial_object, AdvancedHeapWalkContext callbacks, - const void* user_data); + const void* user_data, + GrowableArray* objects); ~VM_HeapWalkOperation(); @@ -2434,7 +2459,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* objects) { _is_advanced_heap_walk = false; _tag_map = tag_map; _initial_object = initial_object; @@ -2443,6 +2469,7 @@ 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); @@ -2451,7 +2478,8 @@ VM_HeapWalkOperation::VM_HeapWalkOperation(JvmtiTagMap* tag_map, VM_HeapWalkOperation::VM_HeapWalkOperation(JvmtiTagMap* tag_map, Handle initial_object, AdvancedHeapWalkContext callbacks, - const void* user_data) { + const void* user_data, + GrowableArray* objects) { _is_advanced_heap_walk = true; _tag_map = tag_map; _initial_object = initial_object; @@ -2460,6 +2488,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); } @@ -2930,7 +2959,7 @@ void VM_HeapWalkOperation::doit() { ObjectMarkerController marker; ClassFieldMapCacheMark cm; - JvmtiTagMap::check_hashmaps_for_heapwalk(); + JvmtiTagMap::check_hashmaps_for_heapwalk(_dead_objects); assert(visit_stack()->is_empty(), "visit stack must be empty"); @@ -2978,10 +3007,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(mtServiceability); + GrowableArray 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 @@ -2991,10 +3026,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(mtServiceability); + GrowableArray 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 @@ -3012,10 +3053,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(mtServiceability); + GrowableArray 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 diff --git a/src/hotspot/share/prims/jvmtiTagMap.hpp b/src/hotspot/share/prims/jvmtiTagMap.hpp index ab5f9d7f2b6..a87ff45baac 100644 --- a/src/hotspot/share/prims/jvmtiTagMap.hpp +++ b/src/hotspot/share/prims/jvmtiTagMap.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2022, 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 @@ -38,10 +38,11 @@ class JvmtiTagMap : public CHeapObj { private: JvmtiEnv* _env; // the jvmti environment - Mutex _lock; // lock for this tag map + Monitor _lock; // lock for this tag map JvmtiTagMapTable* _hashmap; // the hashmap for tags bool _needs_rehashing; bool _needs_cleaning; + bool _posting_events; static bool _has_object_free_events; @@ -51,15 +52,14 @@ class JvmtiTagMap : public CHeapObj { // accessors inline JvmtiEnv* env() const { return _env; } - void check_hashmap(bool post_events); + void check_hashmap(GrowableArray* objects); void entry_iterate(JvmtiTagMapEntryClosure* closure); - void post_dead_objects_on_vm_thread(); public: // indicates if this tag map is locked bool is_locked() { return lock()->is_locked(); } - inline Mutex* lock() { return &_lock; } + inline Monitor* lock() { return &_lock; } JvmtiTagMapTable* hashmap() { return _hashmap; } @@ -109,11 +109,12 @@ class JvmtiTagMap : public CHeapObj { jint* count_ptr, jobject** object_result_ptr, jlong** tag_result_ptr); + void remove_and_post_dead_objects(); + void remove_dead_entries(GrowableArray* objects); + void remove_dead_entries_locked(GrowableArray* objects); + void post_dead_objects(GrowableArray* const objects); - void remove_dead_entries(bool post_object_free); - void remove_dead_entries_locked(bool post_object_free); - - static void check_hashmaps_for_heapwalk(); + static void check_hashmaps_for_heapwalk(GrowableArray* objects); static void set_needs_rehashing() NOT_JVMTI_RETURN; static void set_needs_cleaning() NOT_JVMTI_RETURN; static void gc_notification(size_t num_dead_entries) NOT_JVMTI_RETURN; diff --git a/src/hotspot/share/prims/jvmtiTagMapTable.cpp b/src/hotspot/share/prims/jvmtiTagMapTable.cpp index e7eb730585d..8eddd81e0da 100644 --- a/src/hotspot/share/prims/jvmtiTagMapTable.cpp +++ b/src/hotspot/share/prims/jvmtiTagMapTable.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2022, 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 @@ -186,8 +186,9 @@ void JvmtiTagMapTable::resize_if_needed() { } } -// Serially remove entries for dead oops from the table, and notify jvmti. -void JvmtiTagMapTable::remove_dead_entries(JvmtiEnv* env, bool post_object_free) { +// Serially remove entries for dead oops from the table and store dead oops' +// tag in objects array if provided. +void JvmtiTagMapTable::remove_dead_entries(GrowableArray* objects) { int oops_removed = 0; int oops_counted = 0; for (int i = 0; i < table_size(); ++i) { @@ -206,19 +207,18 @@ void JvmtiTagMapTable::remove_dead_entries(JvmtiEnv* env, bool post_object_free) *p = entry->next(); free_entry(entry); - // post the event to the profiler - if (post_object_free) { - JvmtiExport::post_object_free(env, tag); + // collect object tags for posting JVMTI events later + if (objects != NULL) { + objects->append(tag); } - } // get next entry entry = *p; } } - log_info(jvmti, table) ("JvmtiTagMap entries counted %d removed %d; %s", - oops_counted, oops_removed, post_object_free ? "free object posted" : "no posting"); + log_info(jvmti, table) ("JvmtiTagMap entries counted %d removed %d", + oops_counted, oops_removed); } // Rehash oops in the table diff --git a/src/hotspot/share/prims/jvmtiTagMapTable.hpp b/src/hotspot/share/prims/jvmtiTagMapTable.hpp index 5f83f4e44c8..1cac459d652 100644 --- a/src/hotspot/share/prims/jvmtiTagMapTable.hpp +++ b/src/hotspot/share/prims/jvmtiTagMapTable.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2022, 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 @@ -87,8 +87,8 @@ class JvmtiTagMapTable : public Hashtable { bool is_empty() const { return number_of_entries() == 0; } - // Cleanup cleared entries and post - void remove_dead_entries(JvmtiEnv* env, bool post_object_free); + // Cleanup cleared entries and store dead object tags in objects array + void remove_dead_entries(GrowableArray* objects); void rehash(); void clear(); }; diff --git a/src/jdk.jdwp.agent/share/native/libjdwp/classTrack.c b/src/jdk.jdwp.agent/share/native/libjdwp/classTrack.c index de9918780bf..b5f8fb29fca 100644 --- a/src/jdk.jdwp.agent/share/native/libjdwp/classTrack.c +++ b/src/jdk.jdwp.agent/share/native/libjdwp/classTrack.c @@ -37,6 +37,7 @@ #include "util.h" #include "bag.h" #include "classTrack.h" +#include "eventHandler.h" #define NOT_TAGGED 0 @@ -46,64 +47,14 @@ static jvmtiEnv* trackingEnv; /* - * A bag containing all the deleted classes' signatures. Must be accessed under - * classTrackLock. + * Invoke the callback when classes are freed. */ -struct bag* deletedSignatures; - -/* - * Lock to keep integrity of deletedSignatures. - */ -static jrawMonitorID classTrackLock; - -/* - * Invoke the callback when classes are freed, find and record the signature - * in deletedSignatures. Those are only used in addPreparedClass() by the - * same thread. - */ -static void JNICALL +void JNICALL cbTrackingObjectFree(jvmtiEnv* jvmti_env, jlong tag) { - debugMonitorEnter(classTrackLock); - if (deletedSignatures == NULL) { - debugMonitorExit(classTrackLock); - return; - } - *(char**)bagAdd(deletedSignatures) = (char*)jlong_to_ptr(tag); - - debugMonitorExit(classTrackLock); -} - -/* - * Called after class unloads have occurred. - * The signatures of classes which were unloaded are returned. - */ -struct bag * -classTrack_processUnloads(JNIEnv *env) -{ - if (deletedSignatures == NULL) { - return NULL; - } - - /* Allocate new bag outside classTrackLock lock to avoid deadlock. - * - * Note: jvmtiAllocate/jvmtiDeallocate() may be blocked by ongoing safepoints. - * It is dangerous to call them (via bagCreateBag/bagDestroyBag()) while holding monitor(s), - * because jvmti may post events, e.g. JVMTI_EVENT_OBJECT_FREE at safepoints and event processing - * code may acquire the same monitor(s), e.g. classTrackLock in cbTrackingObjectFree(), - * which can lead to deadlock. - */ - struct bag* new_bag = bagCreateBag(sizeof(char*), 10); - debugMonitorEnter(classTrackLock); - struct bag* deleted = deletedSignatures; - deletedSignatures = new_bag; - debugMonitorExit(classTrackLock); - return deleted; + eventHandler_synthesizeUnloadEvent((char*)jlong_to_ptr(tag), getEnv()); } -/* - * Add a class to the prepared class table. - */ void classTrack_addPreparedClass(JNIEnv *env_unused, jclass klass) { @@ -162,8 +113,6 @@ setupEvents() void classTrack_initialize(JNIEnv *env) { - deletedSignatures = NULL; - classTrackLock = debugMonitorCreate("Deleted class tag lock"); trackingEnv = getSpecialJvmti(); if (trackingEnv == NULL) { EXIT_ERROR(AGENT_ERROR_INTERNAL, "Failed to allocate tag-tracking jvmtiEnv"); @@ -195,44 +144,3 @@ classTrack_initialize(JNIEnv *env) EXIT_ERROR(error,"loaded classes array"); } } - -/* - * Called to activate class-tracking when a listener registers for EI_GC_FINISH. - */ -void -classTrack_activate(JNIEnv *env) -{ - // Allocate bag outside classTrackLock lock to avoid deadlock. - // See comments in classTrack_processUnloads() for details. - struct bag* new_bag = bagCreateBag(sizeof(char*), 1000); - debugMonitorEnter(classTrackLock); - deletedSignatures = new_bag; - debugMonitorExit(classTrackLock); -} - -static jboolean -cleanDeleted(void *signatureVoid, void *arg) -{ - char* sig = *(char**)signatureVoid; - jvmtiDeallocate(sig); - return JNI_TRUE; -} - -/* - * Called when agent detaches. - */ -void -classTrack_reset(void) -{ - debugMonitorEnter(classTrackLock); - struct bag* to_delete = deletedSignatures; - deletedSignatures = NULL; - debugMonitorExit(classTrackLock); - - // Deallocate bag outside classTrackLock to avoid deadlock. - // See comments in classTrack_processUnloads() for details. - if (to_delete != NULL) { - bagEnumerateOver(to_delete, cleanDeleted, NULL); - bagDestroyBag(to_delete); - } -} diff --git a/src/jdk.jdwp.agent/share/native/libjdwp/debugInit.c b/src/jdk.jdwp.agent/share/native/libjdwp/debugInit.c index 3c69b71ee43..860b5cc1903 100644 --- a/src/jdk.jdwp.agent/share/native/libjdwp/debugInit.c +++ b/src/jdk.jdwp.agent/share/native/libjdwp/debugInit.c @@ -792,7 +792,6 @@ debugInit_reset(JNIEnv *env) threadControl_reset(); util_reset(); commonRef_reset(env); - classTrack_reset(); /* * If this is a server, we are now ready to accept another connection. diff --git a/src/jdk.jdwp.agent/share/native/libjdwp/eventHandler.c b/src/jdk.jdwp.agent/share/native/libjdwp/eventHandler.c index 2a5912f0777..c79d27246bc 100644 --- a/src/jdk.jdwp.agent/share/native/libjdwp/eventHandler.c +++ b/src/jdk.jdwp.agent/share/native/libjdwp/eventHandler.c @@ -457,16 +457,10 @@ reportEvents(JNIEnv *env, jbyte sessionID, jthread thread, EventIndex ei, } } -/* A bagEnumerateFunction. Create a synthetic class unload event - * for every class no longer present. Analogous to event_callback - * combined with a handler in a unload specific (no event - * structure) kind of way. - */ -static jboolean -synthesizeUnloadEvent(void *signatureVoid, void *envVoid) +/* Create a synthetic class unload event for the specified signature. */ +jboolean +eventHandler_synthesizeUnloadEvent(char *signature, JNIEnv *env) { - JNIEnv *env = (JNIEnv *)envVoid; - char *signature = *(char **)signatureVoid; char *classname; HandlerNode *node; jbyte eventSessionID = currentSessionID; @@ -560,39 +554,10 @@ event_callback(JNIEnv *env, EventInfo *evinfo) currentException = JNI_FUNC_PTR(env,ExceptionOccurred)(env); JNI_FUNC_PTR(env,ExceptionClear)(env); - /* See if a garbage collection finish event happened earlier. - * - * Note: The "if" is an optimization to avoid entering the lock on every - * event; garbageCollected may be zapped before we enter - * the lock but then this just becomes one big no-op. - */ - if ( garbageCollected > 0 ) { - struct bag *unloadedSignatures = NULL; - - /* We want to compact the hash table of all - * objects sent to the front end by removing objects that have - * been collected. - */ + /* See if a garbage collection finish event happened earlier. */ + if ( garbageCollected > 0) { commonRef_compact(); - - /* We also need to simulate the class unload events. */ - - debugMonitorEnter(handlerLock); - - /* Clear garbage collection counter */ garbageCollected = 0; - - /* Analyze which class unloads occurred */ - unloadedSignatures = classTrack_processUnloads(env); - - debugMonitorExit(handlerLock); - - /* Generate the synthetic class unload events and/or just cleanup. */ - if ( unloadedSignatures != NULL ) { - (void)bagEnumerateOver(unloadedSignatures, synthesizeUnloadEvent, - (void *)env); - bagDestroyBag(unloadedSignatures); - } } thread = evinfo->thread; @@ -1627,9 +1592,6 @@ installHandler(HandlerNode *node, node->handlerID = external? ++requestIdCounter : 0; error = eventFilterRestricted_install(node); - if (node->ei == EI_GC_FINISH) { - classTrack_activate(getEnv()); - } if (error == JVMTI_ERROR_NONE) { insert(getHandlerChain(node->ei), node); } diff --git a/src/jdk.jdwp.agent/share/native/libjdwp/eventHandler.h b/src/jdk.jdwp.agent/share/native/libjdwp/eventHandler.h index aba03bf143e..dbc180556ae 100644 --- a/src/jdk.jdwp.agent/share/native/libjdwp/eventHandler.h +++ b/src/jdk.jdwp.agent/share/native/libjdwp/eventHandler.h @@ -76,6 +76,7 @@ void eventHandler_reset(jbyte sessionID); void eventHandler_lock(void); void eventHandler_unlock(void); +jboolean eventHandler_synthesizeUnloadEvent(char *signature, JNIEnv *env); jclass getMethodClass(jvmtiEnv *jvmti_env, jmethodID method); diff --git a/test/hotspot/jtreg/ProblemList.txt b/test/hotspot/jtreg/ProblemList.txt index 5d43b504f98..efd43f1289b 100644 --- a/test/hotspot/jtreg/ProblemList.txt +++ b/test/hotspot/jtreg/ProblemList.txt @@ -1,5 +1,5 @@ # -# Copyright (c) 2016, 2021, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2016, 2022, 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 diff --git a/test/jdk/com/sun/jdi/ClassUnloadEventTest.java b/test/jdk/com/sun/jdi/ClassUnloadEventTest.java new file mode 100644 index 00000000000..40499e61523 --- /dev/null +++ b/test/jdk/com/sun/jdi/ClassUnloadEventTest.java @@ -0,0 +1,182 @@ +/* + * Copyright (c) 2022, 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 8256811 + * @modules java.base/jdk.internal.org.objectweb.asm + * java.base/jdk.internal.misc + * @library /test/lib + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm/native ClassUnloadEventTest run + */ + +import jdk.internal.org.objectweb.asm.ClassWriter; +import jdk.internal.org.objectweb.asm.Label; +import jdk.internal.org.objectweb.asm.MethodVisitor; +import jdk.internal.org.objectweb.asm.Opcodes; +import jdk.test.lib.classloader.ClassUnloadCommon; + +import com.sun.jdi.*; +import com.sun.jdi.connect.*; +import com.sun.jdi.event.*; +import com.sun.jdi.request.*; + +import java.util.*; +import java.io.*; + +public class ClassUnloadEventTest { + static final String CLASS_NAME_PREFIX = "SampleClass__"; + static final String CLASS_NAME_ALT_PREFIX = CLASS_NAME_PREFIX + "Alt__"; + static final int NUM_CLASSES = 10; + static final int NUM_ALT_CLASSES = NUM_CLASSES / 2; + + public static void main(String[] args) throws Exception { + if (args.length == 0) { + runDebuggee(); + } else { + runDebugger(); + } + } + + private static class TestClassLoader extends ClassLoader implements Opcodes { + private static byte[] generateSampleClass(String name) { + ClassWriter cw = new ClassWriter(0); + + cw.visit(52, ACC_SUPER | ACC_PUBLIC, name, null, "java/lang/Object", null); + MethodVisitor mv = cw.visitMethod(ACC_PUBLIC | ACC_STATIC, "m", "()V", null, null); + mv.visitCode(); + mv.visitInsn(RETURN); + mv.visitMaxs(0, 0); + mv.visitEnd(); + cw.visitEnd(); + return cw.toByteArray(); + } + + @Override + protected Class findClass(String name) throws ClassNotFoundException { + if (name.startsWith(CLASS_NAME_PREFIX)) { + byte[] bytecode = generateSampleClass(name); + return defineClass(name, bytecode, 0, bytecode.length); + } else { + return super.findClass(name); + } + } + } + + private static void runDebuggee() { + System.out.println("Running debuggee"); + ClassLoader loader = new TestClassLoader(); + for (int index = 0; index < NUM_CLASSES; index++) { + try { + if (index < NUM_ALT_CLASSES) { + Class.forName(CLASS_NAME_ALT_PREFIX + index, true, loader); + } else { + Class.forName(CLASS_NAME_PREFIX + index, true, loader); + } + } catch (Exception e) { + throw new RuntimeException("Failed to create Sample class"); + } + } + loader = null; + // Trigger class unloading + ClassUnloadCommon.triggerUnloading(); + } + + private static void runDebugger() throws Exception { + System.out.println("Running debugger"); + HashSet unloadedSampleClasses = new HashSet<>(); + HashSet unloadedSampleClasses_alt = new HashSet<>(); + VirtualMachine vm = null; + vm = connectAndLaunchVM(); + ClassUnloadRequest classUnloadRequest = vm.eventRequestManager().createClassUnloadRequest(); + classUnloadRequest.addClassFilter(CLASS_NAME_PREFIX + "*"); + classUnloadRequest.enable(); + + ClassUnloadRequest classUnloadRequest_alt = vm.eventRequestManager().createClassUnloadRequest(); + classUnloadRequest_alt.addClassFilter(CLASS_NAME_ALT_PREFIX + "*"); + classUnloadRequest_alt.enable(); + + EventSet eventSet = null; + boolean exited = false; + while (!exited && (eventSet = vm.eventQueue().remove()) != null) { + System.out.println("EventSet: " + eventSet); + for (Event event : eventSet) { + if (event instanceof ClassUnloadEvent) { + String className = ((ClassUnloadEvent)event).className(); + + // The unloaded class should always match CLASS_NAME_PREFIX. + if (className.indexOf(CLASS_NAME_PREFIX) == -1) { + throw new RuntimeException("FAILED: Unexpected unloaded class: " + className); + } + + // Unloaded classes with ALT names should only occur on the classUnloadRequest_alt. + if (event.request() == classUnloadRequest_alt) { + unloadedSampleClasses_alt.add(className); + if (className.indexOf(CLASS_NAME_ALT_PREFIX) == -1) { + throw new RuntimeException("FAILED: non-alt class unload event for classUnloadRequest_alt."); + } + } else { + unloadedSampleClasses.add(className); + } + + // If the unloaded class matches the ALT prefix, then we should have + // unload events in this EventSet for each of the two ClassUnloadRequesta. + int expectedEventSetSize; + if (className.indexOf(CLASS_NAME_ALT_PREFIX) != -1) { + expectedEventSetSize = 2; + } else { + expectedEventSetSize = 1; + } + if (eventSet.size() != expectedEventSetSize) { + throw new RuntimeException("FAILED: Unexpected eventSet size: " + eventSet.size()); + } + } + + if (event instanceof VMDeathEvent) { + exited = true; + break; + } + } + eventSet.resume(); + } + + if (unloadedSampleClasses.size() != NUM_CLASSES) { + throw new RuntimeException("Wrong number of class unload events: expected " + NUM_CLASSES + " got " + unloadedSampleClasses.size()); + } + if (unloadedSampleClasses_alt.size() != NUM_ALT_CLASSES) { + throw new RuntimeException("Wrong number of alt class unload events: expected " + NUM_ALT_CLASSES + " got " + unloadedSampleClasses_alt.size()); + } + } + + private static VirtualMachine connectAndLaunchVM() throws IOException, + IllegalConnectorArgumentsException, + VMStartException { + LaunchingConnector launchingConnector = Bootstrap.virtualMachineManager().defaultConnector(); + Map arguments = launchingConnector.defaultArguments(); + arguments.get("main").setValue(ClassUnloadEventTest.class.getName()); + arguments.get("options").setValue("--add-exports java.base/jdk.internal.org.objectweb.asm=ALL-UNNAMED -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI"); + return launchingConnector.launch(arguments); + } +}