Skip to content

Commit c18e3a3

Browse files
author
Igor Veresov
committed
8379819: Creating AOT configuration crashes in MethodTrainingData::prepare
Reviewed-by: iklam, kvn
1 parent cddee6d commit c18e3a3

6 files changed

Lines changed: 117 additions & 16 deletions

File tree

src/hotspot/share/oops/methodData.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -329,7 +329,7 @@ static bool is_excluded(Klass* k) {
329329
log_debug(aot, training)("Purged %s from MDO: unloaded class", k->name()->as_C_string());
330330
return true;
331331
} else {
332-
bool excluded = SystemDictionaryShared::should_be_excluded(k);
332+
bool excluded = SystemDictionaryShared::should_be_excluded(k) || !SystemDictionaryShared::is_builtin_loader(k->class_loader_data());
333333
if (excluded) {
334334
log_debug(aot, training)("Purged %s from MDO: excluded class", k->name()->as_C_string());
335335
}

src/hotspot/share/oops/trainingData.cpp

Lines changed: 36 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -118,10 +118,23 @@ void TrainingData::verify() {
118118
}
119119
}
120120

121+
static bool is_excluded(InstanceKlass* k) {
122+
if (!k->is_loaded() || k->has_been_redefined()) {
123+
return true;
124+
}
125+
if (CDSConfig::is_at_aot_safepoint()) {
126+
// Check for AOT exclusion only at AOT safe point.
127+
return SystemDictionaryShared::should_be_excluded(k) || !SystemDictionaryShared::is_builtin_loader(k->class_loader_data());
128+
}
129+
return false;
130+
}
131+
121132
MethodTrainingData* MethodTrainingData::make(const methodHandle& method, bool null_if_not_found, bool use_cache) {
122-
MethodTrainingData* mtd = nullptr;
123133
if (!have_data() && !need_data()) {
124-
return mtd;
134+
return nullptr;
135+
}
136+
if (is_excluded(method->method_holder())) {
137+
return nullptr;
125138
}
126139
// Try grabbing the cached value first.
127140
// Cache value is stored in MethodCounters and the following are the
@@ -133,6 +146,7 @@ MethodTrainingData* MethodTrainingData::make(const methodHandle& method, bool nu
133146
// i.e. null_if_no_found == true, then just return a null.
134147
// 3. Cache value is not null.
135148
// Return it, the value of training_data_lookup_failed doesn't matter.
149+
MethodTrainingData* mtd = nullptr;
136150
MethodCounters* mcs = method->method_counters();
137151
if (mcs != nullptr) {
138152
mtd = mcs->method_training_data();
@@ -175,6 +189,7 @@ MethodTrainingData* MethodTrainingData::make(const methodHandle& method, bool nu
175189
return nullptr; // allocation failure
176190
}
177191
td = training_data_set()->install(mtd);
192+
assert(!is_excluded(method->method_holder()), "Should not be excluded");
178193
assert(td == mtd, "");
179194
} else {
180195
mtd = nullptr;
@@ -376,6 +391,9 @@ void CompileTrainingData::prepare(Visitor& visitor) {
376391
}
377392

378393
KlassTrainingData* KlassTrainingData::make(InstanceKlass* holder, bool null_if_not_found) {
394+
if (is_excluded(holder)) {
395+
return nullptr;
396+
}
379397
Key key(holder);
380398
TrainingData* td = CDS_ONLY(have_data() ? lookup_archived_training_data(&key) :) nullptr;
381399
KlassTrainingData* ktd = nullptr;
@@ -401,6 +419,7 @@ KlassTrainingData* KlassTrainingData::make(InstanceKlass* holder, bool null_if_n
401419
}
402420
td = training_data_set()->install(ktd);
403421
assert(ktd == td, "");
422+
assert(!is_excluded(holder), "Should not be excluded");
404423
} else {
405424
ktd = td->as_KlassTrainingData();
406425
guarantee(ktd->holder() != nullptr, "null holder");
@@ -543,18 +562,24 @@ void TrainingData::cleanup_training_data() {
543562
}
544563
}
545564

565+
void TrainingData::cleanup_after_redefinition() {
566+
if (need_data()) {
567+
TrainingDataLocker l;
568+
ResourceMark rm;
569+
Visitor visitor(training_data_set()->size());
570+
training_data_set()->iterate([&](TrainingData* td) {
571+
td->cleanup(visitor);
572+
});
573+
}
574+
}
575+
546576
void KlassTrainingData::cleanup(Visitor& visitor) {
547577
if (visitor.is_visited(this)) {
548578
return;
549579
}
550580
visitor.visit(this);
551581
if (has_holder()) {
552-
bool is_excluded = !holder()->is_loaded();
553-
if (CDSConfig::is_at_aot_safepoint()) {
554-
// Check for AOT exclusion only at AOT safe point.
555-
is_excluded |= SystemDictionaryShared::should_be_excluded(holder());
556-
}
557-
if (is_excluded) {
582+
if (is_excluded(holder())) {
558583
ResourceMark rm;
559584
log_debug(aot, training)("Cleanup KTD %s", name()->as_klass_external_name());
560585
_holder = nullptr;
@@ -572,12 +597,8 @@ void MethodTrainingData::cleanup(Visitor& visitor) {
572597
}
573598
visitor.visit(this);
574599
if (has_holder()) {
575-
if (CDSConfig::is_at_aot_safepoint() && SystemDictionaryShared::should_be_excluded(holder()->method_holder())) {
576-
// Check for AOT exclusion only at AOT safe point.
600+
if (is_excluded(holder()->method_holder())) {
577601
log_debug(aot, training)("Cleanup MTD %s::%s", name()->as_klass_external_name(), signature()->as_utf8());
578-
if (_final_profile != nullptr && _final_profile->method() != _holder) {
579-
log_warning(aot, training)("Stale MDO for %s::%s", name()->as_klass_external_name(), signature()->as_utf8());
580-
}
581602
_final_profile = nullptr;
582603
_final_counters = nullptr;
583604
_holder = nullptr;
@@ -593,6 +614,7 @@ void MethodTrainingData::cleanup(Visitor& visitor) {
593614
}
594615

595616
void KlassTrainingData::verify() {
617+
guarantee(!has_holder() || !is_excluded(holder()), "Bad holder");
596618
for (int i = 0; i < comp_dep_count(); i++) {
597619
CompileTrainingData* ctd = comp_dep(i);
598620
if (!ctd->_init_deps.contains(this)) {
@@ -604,6 +626,7 @@ void KlassTrainingData::verify() {
604626
}
605627

606628
void MethodTrainingData::verify(bool verify_dep_counter) {
629+
guarantee(!has_holder() || !is_excluded(holder()->method_holder()), "Bad holder");
607630
iterate_compiles([&](CompileTrainingData* ctd) {
608631
ctd->verify(verify_dep_counter);
609632
});

src/hotspot/share/oops/trainingData.hpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,7 @@ class TrainingData : public Metadata {
310310
static void iterate(Function& fn) { // lambda enabled API
311311
TrainingDataLocker l;
312312
if (have_data()) {
313-
archived_training_data_dictionary()->iterate(fn);
313+
archived_training_data_dictionary()->iterate_all(fn);
314314
}
315315
if (need_data()) {
316316
training_data_set()->iterate(fn);
@@ -431,6 +431,8 @@ class TrainingData : public Metadata {
431431
}
432432
return nullptr;
433433
}
434+
435+
static void cleanup_after_redefinition();
434436
};
435437

436438
// Training data that is associated with an InstanceKlass

src/hotspot/share/prims/jvmtiRedefineClasses.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
#include "oops/method.hpp"
5454
#include "oops/oop.inline.hpp"
5555
#include "oops/recordComponent.hpp"
56+
#include "oops/trainingData.hpp"
5657
#include "prims/jvmtiImpl.hpp"
5758
#include "prims/jvmtiRedefineClasses.hpp"
5859
#include "prims/jvmtiThreadState.inline.hpp"
@@ -274,6 +275,10 @@ void VM_RedefineClasses::doit() {
274275
redefine_single_class(current, _class_defs[i].klass, _scratch_classes[i]);
275276
}
276277

278+
#if INCLUDE_CDS
279+
TrainingData::cleanup_after_redefinition();
280+
#endif
281+
277282
// Flush all compiled code that depends on the classes redefined.
278283
flush_dependent_code();
279284

test/hotspot/jtreg/runtime/cds/appcds/aotCache/RedefineClassesInProfile.java

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,14 +41,19 @@
4141
* @run driver jdk.test.lib.helpers.ClassFileInstaller -jar redef1.jar
4242
* RedefFoo RedefBar
4343
* RedefTaz0 RedefTaz1 RedefTaz2 RedefTaz3 RedefTaz4
44-
44+
*
45+
* @compile test-classes/CustomLoadee.java
46+
* @run driver jdk.test.lib.helpers.ClassFileInstaller -jar cust.jar CustomLoadee
47+
*
4548
* @run driver RedefineClassHelper
4649
* @build RedefineClassesInProfile
4750
* @run driver jdk.test.lib.helpers.ClassFileInstaller -jar app.jar RedefineClassesInProfileApp Util
4851
* @run driver RedefineClassesInProfile
4952
*/
5053

5154
import java.io.File;
55+
import java.net.URL;
56+
import java.net.URLClassLoader;
5257
import jdk.test.lib.cds.SimpleCDSAppTester;
5358
import jdk.test.lib.process.OutputAnalyzer;
5459
import jdk.test.lib.process.ProcessTools;
@@ -89,6 +94,7 @@ public static void main(String... args) throws Exception {
8994
out.shouldNotMatch(prefix + "RedefFoo");
9095
out.shouldNotMatch(prefix + "RedefBar");
9196
out.shouldNotMatch(prefix + "RedefTaz");
97+
out.shouldNotMatch(prefix + "CustomLoadee");
9298
})
9399
.setProductionChecker((OutputAnalyzer out) -> {
94100
out.shouldContain("Redefined: class RedefBar");
@@ -104,6 +110,7 @@ class RedefineClassesInProfileApp {
104110

105111
public static void main(String[] args) throws Exception {
106112
test1();
113+
test2();
107114
}
108115

109116
// test1
@@ -175,4 +182,30 @@ static void redefine(String jarFile, Class c) {
175182
throw new RuntimeException("Unexpected failure", t);
176183
}
177184
}
185+
186+
// Test 2 -- TrainingData interaction with custom class loaders.
187+
static void test2() throws Exception {
188+
// Do this several times. The AOT cache should contain only one
189+
// copy of CustomLoadee as an "unregistered" class, which will be
190+
// used in the first iteration of this loop.
191+
//
192+
// The JVM should work well even if the cached version of CustomLoadee
193+
// has been unloaded.
194+
for (int i = 0; i < 4; i++) {
195+
test2_inner();
196+
System.gc(); // trigger unloading of CustomLoadee.
197+
}
198+
}
199+
200+
static void test2_inner() throws Exception {
201+
// Load a class and run a loop to make sure it's compiled, but
202+
// TrainingData should not record any class/method that are loaded
203+
// by custom class loaders
204+
File custJar = new File("cust.jar");
205+
URL[] urls = new URL[] {custJar.toURI().toURL()};
206+
URLClassLoader loader = new URLClassLoader(urls, RedefineClassesInProfileApp.class.getClassLoader());
207+
Class<?> c = loader.loadClass("CustomLoadee");
208+
System.out.println(c.newInstance());
209+
}
178210
}
211+
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Copyright (c) 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+
public class CustomLoadee {
26+
public CustomLoadee() {
27+
hotspot2();
28+
}
29+
30+
static volatile int x;
31+
static void hotspot2() {
32+
long start = System.currentTimeMillis();
33+
// run this loop long enough (400ms) for it to be JIT compiled.
34+
while (System.currentTimeMillis() - start < 400) {
35+
x += 1;
36+
}
37+
}
38+
}

0 commit comments

Comments
 (0)