1 /*
2 * Copyright (c) 2025, 2026, 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 "cds/aotReferenceObjSupport.hpp"
26 #include "cds/heapShared.hpp"
27 #include "classfile/javaClasses.hpp"
28 #include "classfile/symbolTable.hpp"
29 #include "classfile/systemDictionary.hpp"
30 #include "classfile/vmSymbols.hpp"
31 #include "logging/log.hpp"
32 #include "memory/resourceArea.hpp"
33 #include "memory/universe.hpp"
34 #include "oops/oop.inline.hpp"
35 #include "oops/oopCast.inline.hpp"
36 #include "oops/oopHandle.inline.hpp"
37 #include "runtime/fieldDescriptor.inline.hpp"
38 #include "runtime/javaCalls.hpp"
39 #include "utilities/hashTable.hpp"
40
41 // Handling of java.lang.ref.Reference objects in the AOT cache
42 // ============================================================
43 //
44 // When AOTArtifactFinder finds an oop which is a instance of java.lang.ref.Reference:
45 //
46 // - We check if the oop is eligible to be stored in the AOT cache. If not, the AOT cache
47 // creation fails -- see AOTReferenceObjSupport::check_if_ref_obj()
48 //
49 // - Otherwise, we store the oop into the AOT cache, but we unconditionally reset its
50 // "next" and "discovered" fields to null. Otherwise, if AOTArtifactFinder follows these
51 // fields, it may found unrelated objects that we don't intend to cache.
52 //
53 // Eligibility
54 // ===========
55 //
56 // [1] A reference that does not require special clean up (i.e., Reference::queue == ReferenceQueue.NULL_QUEUE)
57 // is eligible.
58 //
59 // [2] A reference that REQUIRE specials clean up (i.e., Reference::queue != ReferenceQueue.NULL_QUEUE)
60 // is eligible ONLY if its referent is not null.
61 //
62 // As of this version, the only oops in group [2] that can be found by AOTArtifactFinder are
63 // the keys used by ReferencedKeyMap in the implementation of MethodType::internTable.
64 // stabilize_cached_reference_objects() ensures that all keys found by AOTArtifactFinder are eligible.
65 //
66 // The purpose of the error check in check_if_ref_obj() is to guard against changes in the JDK core
67 // libs that might introduce new types of oops in group [2] into the AOT cache.
68 //
69 // Reasons for the eligibility restrictions
70 // ========================================
71 //
72 // Reference handling is complex. In this version, we implement only enough functionality to support
73 // the use of Weak/Soft references used by java.lang.invoke.
74 //
75 // We intend to evolve the implementation in the future by
76 // -- implementing more assemblySetup() operations for other use cases, and/or
77 // -- relaxing the eligibility restrictions.
78 //
79 //
80 // null referents for group [1]
81 // ============================
82 //
83 // Any cached reference R1 of group [1] is allowed to have a null referent.
84 // This can happen in the following situations:
85 // (a) R1.clear() was called by Java code during the assembly phase.
86 // (b) The referent has been collected, and R1 is in the "pending" state.
87 // In case (b), the "next" and "discovered" fields of the cached copy of R1 will
88 // be set to null. During the production run:
89 // - It would appear to the Java program as if immediately during VM start-up, the referent
90 // was collected and ReferenceThread completed processing of R1.
91 // - It would appear to the GC as if immediately during VM start-up, the Java program called
92 // R1.clear().
93
94 #if INCLUDE_CDS_JAVA_HEAP
95
96 class KeepAliveObjectsTable : public HashTable<oop, bool,
97 36137, // prime number
98 AnyObj::C_HEAP,
99 mtClassShared,
100 HeapShared::oop_address_hash> {};
101
102 static KeepAliveObjectsTable* _keep_alive_objs_table;
103 static OopHandle _keep_alive_objs_array;
104 static OopHandle _null_queue;
105
106 bool AOTReferenceObjSupport::is_enabled() {
107 // For simplicity, AOTReferenceObjSupport is enabled only when dumping method handles.
108 // Otherwise we won't see Reference objects in the AOT cache. Let's be conservative now.
109 return CDSConfig::is_dumping_method_handles();
110 }
111
112 void AOTReferenceObjSupport::initialize(TRAPS) {
113 if (!AOTReferenceObjSupport::is_enabled()) {
114 return;
115 }
116
117 TempNewSymbol class_name = SymbolTable::new_symbol("java/lang/ref/ReferenceQueue");
118 Klass* k = SystemDictionary::resolve_or_fail(class_name, true, CHECK);
119 InstanceKlass* ik = InstanceKlass::cast(k);
120 ik->initialize(CHECK);
121
122 TempNewSymbol field_name = SymbolTable::new_symbol("NULL_QUEUE");
123 fieldDescriptor fd;
124 bool found = ik->find_local_field(field_name, vmSymbols::referencequeue_signature(), &fd);
125 precond(found);
126 precond(fd.is_static());
127
128 _null_queue = OopHandle(Universe::vm_global(), ik->java_mirror()->obj_field(fd.offset()));
129 }
130
131 // Ensure that all group [2] references found by AOTArtifactFinder are eligible.
132 void AOTReferenceObjSupport::stabilize_cached_reference_objects(TRAPS) {
133 if (AOTReferenceObjSupport::is_enabled()) {
134 // This assert means that the MethodType and MethodTypeForm tables won't be
135 // updated concurrently, so we can remove GC'ed entries ...
136 assert(CDSConfig::allow_only_single_java_thread(), "Required");
137
138 {
139 TempNewSymbol method_name = SymbolTable::new_symbol("assemblySetup");
140 JavaValue result(T_VOID);
141 JavaCalls::call_static(&result, vmClasses::MethodType_klass(),
142 method_name,
143 vmSymbols::void_method_signature(),
144 CHECK);
145 }
146 {
147 TempNewSymbol method_name = SymbolTable::new_symbol("assemblySetup");
148 JavaValue result(T_VOID);
149 Symbol* baseLocale_name = vmSymbols::sun_util_locale_BaseLocale();
150 Klass* baseLocale_klass = SystemDictionary::resolve_or_fail(baseLocale_name, true, CHECK);
151 JavaCalls::call_static(&result, baseLocale_klass,
152 method_name,
153 vmSymbols::void_method_signature(),
154 CHECK);
155 }
156
157 {
158 Symbol* cds_name = vmSymbols::jdk_internal_misc_CDS();
159 Klass* cds_klass = SystemDictionary::resolve_or_fail(cds_name, true /*throw error*/, CHECK);
160 TempNewSymbol method_name = SymbolTable::new_symbol("getKeepAliveObjects");
161 TempNewSymbol method_sig = SymbolTable::new_symbol("()[Ljava/lang/Object;");
162 JavaValue result(T_OBJECT);
163 JavaCalls::call_static(&result, cds_klass, method_name, method_sig, CHECK);
164
165 _keep_alive_objs_array = OopHandle(Universe::vm_global(), result.get_oop());
166 }
167
168 // Trigger a GC to prune eligible referents that were not kept alive
169 Universe::heap()->collect(GCCause::_java_lang_system_gc);
170 }
171 }
172
173 void AOTReferenceObjSupport::init_keep_alive_objs_table() {
174 assert_at_safepoint(); // _keep_alive_objs_table uses raw oops
175 oop a = _keep_alive_objs_array.resolve();
176 if (a != nullptr) {
177 precond(a->is_refArray());
178 precond(AOTReferenceObjSupport::is_enabled());
179 refArrayOop array = oop_cast<refArrayOop>(a);
180
181 _keep_alive_objs_table = new (mtClass)KeepAliveObjectsTable();
182 for (int i = 0; i < array->length(); i++) {
183 oop obj = array->obj_at(i);
184 _keep_alive_objs_table->put(obj, true); // The array may have duplicated entries but that's OK.
185 }
186 }
187 }
188
189 // Returns true IFF obj is an instance of java.lang.ref.Reference. If so, perform extra eligibility checks.
190 bool AOTReferenceObjSupport::check_if_ref_obj(oop obj) {
191 assert_at_safepoint(); // _keep_alive_objs_table uses raw oops
192
193 if (obj->klass()->is_subclass_of(vmClasses::Reference_klass())) {
194 // The following check works only if the java.lang.ref.Reference$ReferenceHandler thread
195 // is not running.
196 //
197 // This code is called on every object found by AOTArtifactFinder. When dumping the
198 // preimage archive, AOTArtifactFinder should not find any Reference objects.
199 precond(!CDSConfig::is_dumping_preimage_static_archive());
200 precond(CDSConfig::allow_only_single_java_thread());
201
202 precond(AOTReferenceObjSupport::is_enabled());
203 precond(JavaClasses::is_supported_for_archiving(obj));
204 precond(_keep_alive_objs_table != nullptr);
205
206 // GC needs to know about this load, It will keep referent alive until the current safepoint ends.
207 oop referent = HeapAccess<ON_UNKNOWN_OOP_REF>::oop_load_at(obj, java_lang_ref_Reference::referent_offset());
208
209 oop queue = obj->obj_field(java_lang_ref_Reference::queue_offset());
210 oop next = java_lang_ref_Reference::next(obj);
211 oop discovered = java_lang_ref_Reference::discovered(obj);
212 bool needs_special_cleanup = (queue != _null_queue.resolve());
213
214 // If you see the errors below, you probably modified the implementation of java.lang.invoke.
215 // Please check the comments at the top of this file.
216 if (needs_special_cleanup && (referent == nullptr || !_keep_alive_objs_table->contains(referent))) {
217 ResourceMark rm;
218
219 log_error(aot, heap)("Cannot archive reference object " PTR_FORMAT " of class %s",
220 p2i(obj), obj->klass()->external_name());
221 log_error(aot, heap)("referent = " PTR_FORMAT
222 ", queue = " PTR_FORMAT
223 ", next = " PTR_FORMAT
224 ", discovered = " PTR_FORMAT,
225 p2i(referent), p2i(queue), p2i(next), p2i(discovered));
226 log_error(aot, heap)("This object requires special clean up as its queue is not ReferenceQueue::N" "ULL ("
227 PTR_FORMAT ")", p2i(_null_queue.resolve()));
228 log_error(aot, heap)("%s", (referent == nullptr) ?
229 "referent cannot be null" : "referent is not registered with CDS.keepAlive()");
230 HeapShared::debug_trace();
231 AOTMetaspace::unrecoverable_writing_error();
232 }
233
234 if (log_is_enabled(Info, aot, ref)) {
235 ResourceMark rm;
236 log_info(aot, ref)("Reference obj:"
237 " r=" PTR_FORMAT
238 " q=" PTR_FORMAT
239 " n=" PTR_FORMAT
240 " d=" PTR_FORMAT
241 " %s",
242 p2i(referent),
243 p2i(queue),
244 p2i(next),
245 p2i(discovered),
246 obj->klass()->external_name());
247 }
248 return true;
249 } else {
250 return false;
251 }
252 }
253
254 bool AOTReferenceObjSupport::skip_field(int field_offset) {
255 return (field_offset == java_lang_ref_Reference::next_offset() ||
256 field_offset == java_lang_ref_Reference::discovered_offset());
257 }
258
259 #endif // INCLUDE_CDS_JAVA_HEAP