1 /* 2 * Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 * 23 */ 24 25 #include "memory/allocation.hpp" 26 #include "memory/universe.hpp" 27 #include "oops/fieldStreams.inline.hpp" 28 #include "oops/inlineKlass.hpp" 29 #include "oops/oop.inline.hpp" 30 #include "oops/weakHandle.inline.hpp" 31 #include "prims/jvmtiExport.hpp" 32 #include "prims/jvmtiTagMapTable.hpp" 33 34 35 static unsigned get_value_object_hash(oop holder, int offset, Klass* klass) { 36 assert(klass->is_inline_klass(), "Must be InlineKlass"); 37 // For inline types, use the klass as a hash code and let the equals match the obj. 38 // It might have a long bucket but TBD to improve this if a customer situation arises. 39 return (unsigned)((int64_t)klass >> 3); 40 } 41 42 static unsigned get_value_object_hash(const JvmtiHeapwalkObject & obj) { 43 assert(obj.is_value(), "Must be value class"); 44 return get_value_object_hash(obj.obj(), obj.offset(), obj.inline_klass()); 45 } 46 47 static bool equal_oops(oop obj1, oop obj2); // forward declaration 48 49 static bool equal_fields(char type, oop obj1, int offset1, oop obj2, int offset2) { 50 switch (type) { 51 case JVM_SIGNATURE_BOOLEAN: 52 return obj1->bool_field(offset1) == obj2->bool_field(offset2); 53 case JVM_SIGNATURE_CHAR: 54 return obj1->char_field(offset1) == obj2->char_field(offset2); 55 case JVM_SIGNATURE_FLOAT: 56 return obj1->float_field(offset1) == obj2->float_field(offset2); 57 case JVM_SIGNATURE_DOUBLE: 58 return obj1->double_field(offset1) == obj2->double_field(offset2); 59 case JVM_SIGNATURE_BYTE: 60 return obj1->byte_field(offset1) == obj2->byte_field(offset2); 61 case JVM_SIGNATURE_SHORT: 62 return obj1->short_field(offset1) == obj2->short_field(offset2); 63 case JVM_SIGNATURE_INT: 64 return obj1->int_field(offset1) == obj2->int_field(offset2); 65 case JVM_SIGNATURE_LONG: 66 return obj1->long_field(offset1) == obj2->long_field(offset2); 67 case JVM_SIGNATURE_CLASS: 68 case JVM_SIGNATURE_ARRAY: 69 return equal_oops(obj1->obj_field(offset1), obj2->obj_field(offset2)); 70 } 71 ShouldNotReachHere(); 72 } 73 74 static bool is_null_flat_field(oop obj, int offset, InlineKlass* klass) { 75 return klass->is_payload_marked_as_null(cast_from_oop<address>(obj) + offset); 76 } 77 78 // For heap-allocated objects offset is 0 and 'klass' is obj1->klass() (== obj2->klass()). 79 // For flattened objects offset is the offset in the holder object, 'klass' is inlined object class. 80 // The object must be prechecked for non-null values. 81 static bool equal_value_objects(oop obj1, int offset1, oop obj2, int offset2, InlineKlass* klass) { 82 for (JavaFieldStream fld(klass); !fld.done(); fld.next()) { 83 // ignore static fields 84 if (fld.access_flags().is_static()) { 85 continue; 86 } 87 int field_offset1 = offset1 + fld.offset() - (offset1 > 0 ? klass->payload_offset() : 0); 88 int field_offset2 = offset2 + fld.offset() - (offset2 > 0 ? klass->payload_offset() : 0); 89 if (fld.is_flat()) { // flat value field 90 InstanceKlass* holder_klass = fld.field_holder(); 91 InlineKlass* field_klass = holder_klass->get_inline_type_field_klass(fld.index()); 92 if (!fld.is_null_free_inline_type()) { 93 bool field1_is_null = is_null_flat_field(obj1, field_offset1, field_klass); 94 bool field2_is_null = is_null_flat_field(obj2, field_offset2, field_klass); 95 if (field1_is_null != field2_is_null) { 96 return false; 97 } 98 if (field1_is_null) { // if both fields are null, go to next field 99 continue; 100 } 101 } 102 103 if (!equal_value_objects(obj1, field_offset1, obj2, field_offset2, field_klass)) { 104 return false; 105 } 106 } else { 107 if (!equal_fields(fld.signature()->char_at(0), obj1, field_offset1, obj2, field_offset2)) { 108 return false; 109 } 110 } 111 } 112 return true; 113 } 114 115 // handles null oops 116 static bool equal_oops(oop obj1, oop obj2) { 117 if (obj1 == obj2) { 118 return true; 119 } 120 121 if (obj1 != nullptr && obj2 != nullptr && obj1->klass() == obj2->klass() && obj1->is_inline_type()) { 122 InlineKlass* vk = InlineKlass::cast(obj1->klass()); 123 return equal_value_objects(obj1, 0, obj2, 0, vk); 124 } 125 return false; 126 } 127 128 129 bool JvmtiHeapwalkObject::equals(const JvmtiHeapwalkObject& obj1, const JvmtiHeapwalkObject& obj2) { 130 if (obj1 == obj2) { // the same oop/offset/inline_klass 131 return true; 132 } 133 134 if (obj1.is_value() && obj1.inline_klass() == obj2.inline_klass()) { 135 // instances of the same value class 136 return equal_value_objects(obj1.obj(), obj1.offset(), obj2.obj(), obj2.offset(), obj1.inline_klass()); 137 } 138 return false; 139 } 140 141 142 JvmtiTagMapKey::JvmtiTagMapKey(const JvmtiHeapwalkObject* obj) : _obj(obj) { 143 } 144 145 JvmtiTagMapKey::JvmtiTagMapKey(const JvmtiTagMapKey& src): _h() { 146 if (src._obj != nullptr) { 147 // move object into Handle when copying into the table 148 assert(!src._obj->is_flat(), "cannot put flat object to JvmtiTagMapKey"); 149 _is_weak = !src._obj->is_value(); 150 151 // obj was read with AS_NO_KEEPALIVE, or equivalent, like during 152 // a heap walk. The object needs to be kept alive when it is published. 153 Universe::heap()->keep_alive(src._obj->obj()); 154 155 if (_is_weak) { 156 _wh = WeakHandle(JvmtiExport::weak_tag_storage(), src._obj->obj()); 157 } else { 158 _h = OopHandle(JvmtiExport::jvmti_oop_storage(), src._obj->obj()); 159 } 160 } else { 161 // resizing needs to create a copy. 162 _is_weak = src._is_weak; 163 if (_is_weak) { 164 _wh = src._wh; 165 } else { 166 _h = src._h; 167 } 168 } 169 // obj is always null after a copy. 170 _obj = nullptr; 171 } 172 173 void JvmtiTagMapKey::release_handle() { 174 if (_is_weak) { 175 _wh.release(JvmtiExport::weak_tag_storage()); 176 } else { 177 _h.release(JvmtiExport::jvmti_oop_storage()); 178 } 179 } 180 181 JvmtiHeapwalkObject JvmtiTagMapKey::heapwalk_object() const { 182 return _obj != nullptr ? JvmtiHeapwalkObject(_obj->obj(), _obj->offset(), _obj->inline_klass(), _obj->layout_kind()) 183 : JvmtiHeapwalkObject(object_no_keepalive()); 184 } 185 186 oop JvmtiTagMapKey::object() const { 187 assert(_obj == nullptr, "Must have a handle and not object"); 188 return _is_weak ? _wh.resolve() : _h.resolve(); 189 } 190 191 oop JvmtiTagMapKey::object_no_keepalive() const { 192 assert(_obj == nullptr, "Must have a handle and not object"); 193 return _is_weak ? _wh.peek() : _h.peek(); 194 } 195 196 unsigned JvmtiTagMapKey::get_hash(const JvmtiTagMapKey& entry) { 197 const JvmtiHeapwalkObject* obj = entry._obj; 198 assert(obj != nullptr, "must lookup obj to hash"); 199 if (obj->is_value()) { 200 return get_value_object_hash(*obj); 201 } else { 202 return (unsigned)obj->obj()->identity_hash(); 203 } 204 } 205 206 bool JvmtiTagMapKey::equals(const JvmtiTagMapKey& lhs, const JvmtiTagMapKey& rhs) { 207 JvmtiHeapwalkObject lhs_obj = lhs.heapwalk_object(); 208 JvmtiHeapwalkObject rhs_obj = rhs.heapwalk_object(); 209 return JvmtiHeapwalkObject::equals(lhs_obj, rhs_obj); 210 } 211 212 static const int INITIAL_TABLE_SIZE = 1007; 213 static const int MAX_TABLE_SIZE = 0x3fffffff; 214 215 JvmtiTagMapTable::JvmtiTagMapTable() : _table(INITIAL_TABLE_SIZE, MAX_TABLE_SIZE) {} 216 217 void JvmtiTagMapTable::clear() { 218 struct RemoveAll { 219 bool do_entry(JvmtiTagMapKey& entry, const jlong& tag) { 220 entry.release_handle(); 221 return true; 222 } 223 } remove_all; 224 // The unlink method of ResourceHashTable gets a pointer to a type whose 'do_entry(K,V)' method is callled 225 // while iterating over all the elements of the table. If the do_entry() method returns true the element 226 // will be removed. 227 // In this case, we always return true from do_entry to clear all the elements. 228 _table.unlink(&remove_all); 229 230 assert(_table.number_of_entries() == 0, "should have removed all entries"); 231 } 232 233 JvmtiTagMapTable::~JvmtiTagMapTable() { 234 clear(); 235 } 236 237 jlong* JvmtiTagMapTable::lookup(const JvmtiHeapwalkObject& obj) const { 238 if (is_empty()) { 239 return 0; 240 } 241 242 if (!obj.is_value()) { 243 if (obj.obj()->fast_no_hash_check()) { 244 // Objects in the table all have a hashcode, unless inlined types. 245 return nullptr; 246 } 247 } 248 JvmtiTagMapKey entry(&obj); 249 jlong* found = _table.get(entry); 250 return found; 251 } 252 253 254 jlong JvmtiTagMapTable::find(const JvmtiHeapwalkObject& obj) const { 255 jlong* found = lookup(obj); 256 return found == nullptr ? 0 : *found; 257 } 258 259 void JvmtiTagMapTable::add(const JvmtiHeapwalkObject& obj, jlong tag) { 260 assert(!obj.is_flat(), "Cannot add flat object to JvmtiTagMapTable"); 261 JvmtiTagMapKey new_entry(&obj); 262 bool is_added; 263 if (!obj.is_value() && obj.obj()->fast_no_hash_check()) { 264 // Can't be in the table so add it fast. 265 is_added = _table.put_when_absent(new_entry, tag); 266 } else { 267 jlong* value = _table.put_if_absent(new_entry, tag, &is_added); 268 *value = tag; // assign the new tag 269 } 270 if (is_added) { 271 if (_table.maybe_grow(5, true /* use_large_table_sizes */)) { 272 int max_bucket_size = DEBUG_ONLY(_table.verify()) NOT_DEBUG(0); 273 log_info(jvmti, table) ("JvmtiTagMap table resized to %d for %d entries max bucket %d", 274 _table.table_size(), _table.number_of_entries(), max_bucket_size); 275 } 276 } 277 } 278 279 bool JvmtiTagMapTable::update(const JvmtiHeapwalkObject& obj, jlong tag) { 280 jlong* found = lookup(obj); 281 if (found == nullptr) { 282 return false; 283 } 284 *found = tag; 285 return true; 286 } 287 288 bool JvmtiTagMapTable::remove(const JvmtiHeapwalkObject& obj) { 289 JvmtiTagMapKey entry(&obj); 290 auto clean = [](JvmtiTagMapKey & entry, jlong tag) { 291 entry.release_handle(); 292 }; 293 return _table.remove(entry, clean); 294 } 295 296 void JvmtiTagMapTable::entry_iterate(JvmtiTagMapKeyClosure* closure) { 297 _table.iterate(closure); 298 } 299 300 void JvmtiTagMapTable::remove_dead_entries(GrowableArray<jlong>* objects) { 301 struct IsDead { 302 GrowableArray<jlong>* _objects; 303 IsDead(GrowableArray<jlong>* objects) : _objects(objects) {} 304 bool do_entry(JvmtiTagMapKey& entry, jlong tag) { 305 if (entry.object_no_keepalive() == nullptr) { 306 if (_objects != nullptr) { 307 _objects->append(tag); 308 } 309 entry.release_handle(); 310 return true; 311 } 312 return false;; 313 } 314 } is_dead(objects); 315 _table.unlink(&is_dead); 316 } 317 318 319 JvmtiFlatTagMapKey::JvmtiFlatTagMapKey(const JvmtiHeapwalkObject& obj) 320 : _holder(obj.obj()), _offset(obj.offset()), _inline_klass(obj.inline_klass()), _layout_kind(obj.layout_kind()) { 321 } 322 323 JvmtiFlatTagMapKey::JvmtiFlatTagMapKey(const JvmtiFlatTagMapKey& src) : _h() { 324 // move object into Handle when copying into the table 325 if (src._holder != nullptr) { 326 // Holder object was read with AS_NO_KEEPALIVE. Needs to be kept alive when it is published. 327 Universe::heap()->keep_alive(src._holder); 328 _h = OopHandle(JvmtiExport::jvmti_oop_storage(), src._holder); 329 } else { 330 // resizing needs to create a copy. 331 _h = src._h; 332 } 333 // holder object is always null after a copy. 334 _holder = nullptr; 335 _offset = src._offset; 336 _inline_klass = src._inline_klass; 337 _layout_kind = src._layout_kind; 338 } 339 340 JvmtiHeapwalkObject JvmtiFlatTagMapKey::heapwalk_object() const { 341 return JvmtiHeapwalkObject(_holder != nullptr ? _holder : holder_no_keepalive(), _offset, _inline_klass, _layout_kind); 342 } 343 344 oop JvmtiFlatTagMapKey::holder() const { 345 assert(_holder == nullptr, "Must have a handle and not object"); 346 return _h.resolve(); 347 } 348 349 oop JvmtiFlatTagMapKey::holder_no_keepalive() const { 350 assert(_holder == nullptr, "Must have a handle and not object"); 351 return _h.peek(); 352 353 } 354 355 void JvmtiFlatTagMapKey::release_handle() { 356 _h.release(JvmtiExport::jvmti_oop_storage()); 357 } 358 359 unsigned JvmtiFlatTagMapKey::get_hash(const JvmtiFlatTagMapKey& entry) { 360 return get_value_object_hash(entry._holder, entry._offset, entry._inline_klass); 361 } 362 363 bool JvmtiFlatTagMapKey::equals(const JvmtiFlatTagMapKey& lhs, const JvmtiFlatTagMapKey& rhs) { 364 if (lhs._inline_klass == rhs._inline_klass) { 365 oop lhs_obj = lhs._holder != nullptr ? lhs._holder : lhs._h.peek(); 366 oop rhs_obj = rhs._holder != nullptr ? rhs._holder : rhs._h.peek(); 367 return equal_value_objects(lhs_obj, lhs._offset, rhs_obj, rhs._offset, lhs._inline_klass); 368 } 369 return false; 370 } 371 372 373 JvmtiFlatTagMapTable::JvmtiFlatTagMapTable(): _table(INITIAL_TABLE_SIZE, MAX_TABLE_SIZE) {} 374 375 JvmtiFlatTagMapTable::~JvmtiFlatTagMapTable() { 376 clear(); 377 } 378 379 jlong JvmtiFlatTagMapTable::find(const JvmtiHeapwalkObject& obj) const { 380 if (is_empty()) { 381 return 0; 382 } 383 384 JvmtiFlatTagMapKey entry(obj); 385 jlong* found = _table.get(entry); 386 return found == nullptr ? 0 : *found; 387 } 388 389 void JvmtiFlatTagMapTable::add(const JvmtiHeapwalkObject& obj, jlong tag) { 390 assert(obj.is_value() && obj.is_flat(), "Must be flattened value object"); 391 JvmtiFlatTagMapKey entry(obj); 392 bool is_added; 393 jlong* value = _table.put_if_absent(entry, tag, &is_added); 394 *value = tag; // assign the new tag 395 if (is_added) { 396 if (_table.maybe_grow(5, true /* use_large_table_sizes */)) { 397 int max_bucket_size = DEBUG_ONLY(_table.verify()) NOT_DEBUG(0); 398 log_info(jvmti, table) ("JvmtiFlatTagMapTable table resized to %d for %d entries max bucket %d", 399 _table.table_size(), _table.number_of_entries(), max_bucket_size); 400 } 401 } 402 } 403 404 jlong JvmtiFlatTagMapTable::remove(const JvmtiHeapwalkObject& obj) { 405 JvmtiFlatTagMapKey entry(obj); 406 jlong ret = 0; 407 auto clean = [&](JvmtiFlatTagMapKey& entry, jlong tag) { 408 ret = tag; 409 entry.release_handle(); 410 }; 411 _table.remove(entry, clean); 412 return ret; 413 } 414 415 void JvmtiFlatTagMapTable::entry_iterate(JvmtiFlatTagMapKeyClosure* closure) { 416 _table.iterate(closure); 417 } 418 419 void JvmtiFlatTagMapTable::clear() { 420 struct RemoveAll { 421 bool do_entry(JvmtiFlatTagMapKey& entry, const jlong& tag) { 422 entry.release_handle(); 423 return true; 424 } 425 } remove_all; 426 // The unlink method of ResourceHashTable gets a pointer to a type whose 'do_entry(K,V)' method is callled 427 // while iterating over all the elements of the table. If the do_entry() method returns true the element 428 // will be removed. 429 // In this case, we always return true from do_entry to clear all the elements. 430 _table.unlink(&remove_all); 431 432 assert(_table.number_of_entries() == 0, "should have removed all entries"); 433 }