Skip to content

Commit f800af9

Browse files
author
Daniil Titov
committedSep 21, 2020
8241390: 'Deadlock' with VM_RedefineClasses::lock_classes()
Reviewed-by: coleenp, sspitsyn
1 parent 2e30ff6 commit f800af9

File tree

4 files changed

+179
-9
lines changed

4 files changed

+179
-9
lines changed
 

‎src/hotspot/share/prims/jvmtiRedefineClasses.cpp

+47-9
Original file line numberDiff line numberDiff line change
@@ -96,31 +96,69 @@ static inline InstanceKlass* get_ik(jclass def) {
9696
// If any of the classes are being redefined, wait
9797
// Parallel constant pool merging leads to indeterminate constant pools.
9898
void VM_RedefineClasses::lock_classes() {
99+
JvmtiThreadState *state = JvmtiThreadState::state_for(JavaThread::current());
100+
GrowableArray<Klass*>* redef_classes = state->get_classes_being_redefined();
101+
99102
MonitorLocker ml(RedefineClasses_lock);
103+
104+
if (redef_classes == NULL) {
105+
redef_classes = new(ResourceObj::C_HEAP, mtClass) GrowableArray<Klass*>(1, mtClass);
106+
state->set_classes_being_redefined(redef_classes);
107+
}
108+
100109
bool has_redefined;
101110
do {
102111
has_redefined = false;
103-
// Go through classes each time until none are being redefined.
112+
// Go through classes each time until none are being redefined. Skip
113+
// the ones that are being redefined by this thread currently. Class file
114+
// load hook event may trigger new class redefine when we are redefining
115+
// a class (after lock_classes()).
104116
for (int i = 0; i < _class_count; i++) {
105-
if (get_ik(_class_defs[i].klass)->is_being_redefined()) {
106-
ml.wait();
107-
has_redefined = true;
108-
break; // for loop
117+
InstanceKlass* ik = get_ik(_class_defs[i].klass);
118+
// Check if we are currently redefining the class in this thread already.
119+
if (redef_classes->contains(ik)) {
120+
assert(ik->is_being_redefined(), "sanity");
121+
} else {
122+
if (ik->is_being_redefined()) {
123+
ml.wait();
124+
has_redefined = true;
125+
break; // for loop
126+
}
109127
}
110128
}
111129
} while (has_redefined);
130+
112131
for (int i = 0; i < _class_count; i++) {
113-
get_ik(_class_defs[i].klass)->set_is_being_redefined(true);
132+
InstanceKlass* ik = get_ik(_class_defs[i].klass);
133+
redef_classes->push(ik); // Add to the _classes_being_redefined list
134+
ik->set_is_being_redefined(true);
114135
}
115136
ml.notify_all();
116137
}
117138

118139
void VM_RedefineClasses::unlock_classes() {
140+
JvmtiThreadState *state = JvmtiThreadState::state_for(JavaThread::current());
141+
GrowableArray<Klass*>* redef_classes = state->get_classes_being_redefined();
142+
assert(redef_classes != NULL, "_classes_being_redefined is not allocated");
143+
119144
MonitorLocker ml(RedefineClasses_lock);
120-
for (int i = 0; i < _class_count; i++) {
121-
assert(get_ik(_class_defs[i].klass)->is_being_redefined(),
145+
146+
for (int i = _class_count - 1; i >= 0; i--) {
147+
InstanceKlass* def_ik = get_ik(_class_defs[i].klass);
148+
if (redef_classes->length() > 0) {
149+
// Remove the class from _classes_being_redefined list
150+
Klass* k = redef_classes->pop();
151+
assert(def_ik == k, "unlocking wrong class");
152+
}
153+
assert(def_ik->is_being_redefined(),
122154
"should be being redefined to get here");
123-
get_ik(_class_defs[i].klass)->set_is_being_redefined(false);
155+
156+
// Unlock after we finish all redefines for this class within
157+
// the thread. Same class can be pushed to the list multiple
158+
// times (not more than once by each recursive redefinition).
159+
if (!redef_classes->contains(def_ik)) {
160+
def_ik->set_is_being_redefined(false);
161+
}
124162
}
125163
ml.notify_all();
126164
}

‎src/hotspot/share/prims/jvmtiThreadState.cpp

+5
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ JvmtiThreadState::JvmtiThreadState(JavaThread* thread)
5757
_pending_step_for_popframe = false;
5858
_class_being_redefined = NULL;
5959
_class_load_kind = jvmti_class_load_kind_load;
60+
_classes_being_redefined = NULL;
6061
_head_env_thread_state = NULL;
6162
_dynamic_code_event_collector = NULL;
6263
_vm_object_alloc_event_collector = NULL;
@@ -106,6 +107,10 @@ JvmtiThreadState::JvmtiThreadState(JavaThread* thread)
106107
JvmtiThreadState::~JvmtiThreadState() {
107108
assert(JvmtiThreadState_lock->is_locked(), "sanity check");
108109

110+
if (_classes_being_redefined != NULL) {
111+
delete _classes_being_redefined; // free the GrowableArray on C heap
112+
}
113+
109114
// clear this as the state for the thread
110115
get_thread()->set_jvmti_thread_state(NULL);
111116

‎src/hotspot/share/prims/jvmtiThreadState.hpp

+10
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ class JvmtiThreadState : public CHeapObj<mtInternal> {
9999
// info to the class file load hook event handler.
100100
Klass* _class_being_redefined;
101101
JvmtiClassLoadKind _class_load_kind;
102+
GrowableArray<Klass*>* _classes_being_redefined;
102103

103104
// This is only valid when is_interp_only_mode() returns true
104105
int _cur_stack_depth;
@@ -244,6 +245,15 @@ class JvmtiThreadState : public CHeapObj<mtInternal> {
244245
return _class_load_kind;
245246
}
246247

248+
// Get the classes that are currently being redefined by this thread.
249+
inline GrowableArray<Klass*>* get_classes_being_redefined() {
250+
return _classes_being_redefined;
251+
}
252+
253+
inline void set_classes_being_redefined(GrowableArray<Klass*>* redef_classes) {
254+
_classes_being_redefined = redef_classes;
255+
}
256+
247257
// RedefineClasses support
248258
// The bug 6214132 caused the verification to fail.
249259
//
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
/*
2+
* Copyright (c) 2020, 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+
* @test
26+
* @bug 8241390
27+
* @summary Test recursively retransforms the same class. The test hangs if
28+
* a deadlock happens.
29+
* @requires vm.jvmti
30+
* @library /test/lib
31+
* @modules java.instrument
32+
* @compile TransformerDeadlockTest.java
33+
* @run driver TransformerDeadlockTest
34+
*/
35+
36+
import jdk.test.lib.process.ProcessTools;
37+
38+
import java.lang.instrument.ClassFileTransformer;
39+
import java.lang.instrument.IllegalClassFormatException;
40+
import java.lang.instrument.Instrumentation;
41+
import java.nio.file.Files;
42+
import java.nio.file.Path;
43+
import java.nio.file.Paths;
44+
import java.security.ProtectionDomain;
45+
46+
47+
public class TransformerDeadlockTest {
48+
49+
private static String manifest = "Premain-Class: " +
50+
TransformerDeadlockTest.Agent.class.getName() + "\n"
51+
+ "Can-Retransform-Classes: true\n"
52+
+ "Can-Retransform-Classes: true\n";
53+
54+
private static String CP = System.getProperty("test.classes");
55+
56+
public static void main(String args[]) throws Throwable {
57+
String agentJar = buildAgent();
58+
ProcessTools.executeProcess(
59+
ProcessTools.createJavaProcessBuilder(
60+
"-javaagent:" + agentJar,
61+
TransformerDeadlockTest.Agent.class.getName())
62+
).shouldHaveExitValue(0);
63+
}
64+
65+
private static String buildAgent() throws Exception {
66+
Path jar = Files.createTempFile(Paths.get("."), null, ".jar");
67+
String jarPath = jar.toAbsolutePath().toString();
68+
ClassFileInstaller.writeJar(jarPath,
69+
ClassFileInstaller.Manifest.fromString(manifest),
70+
TransformerDeadlockTest.class.getName());
71+
return jarPath;
72+
}
73+
74+
public static class Agent implements ClassFileTransformer {
75+
private static Instrumentation instrumentation;
76+
77+
public static void premain(String agentArgs, Instrumentation inst) {
78+
instrumentation = inst;
79+
}
80+
81+
@Override
82+
public byte[] transform(
83+
ClassLoader loader,
84+
String className,
85+
Class<?> classBeingRedefined,
86+
ProtectionDomain protectionDomain,
87+
byte[] classfileBuffer)
88+
throws IllegalClassFormatException {
89+
90+
if (!TransformerDeadlockTest.class.getName().replace(".", "/").equals(className)) {
91+
return null;
92+
}
93+
invokeRetransform();
94+
return classfileBuffer;
95+
96+
}
97+
98+
public static void main(String[] args) throws Exception {
99+
instrumentation.addTransformer(new TransformerDeadlockTest.Agent(), true);
100+
101+
try {
102+
instrumentation.retransformClasses(TransformerDeadlockTest.class);
103+
} catch (Exception e) {
104+
throw new RuntimeException(e);
105+
}
106+
}
107+
108+
private static void invokeRetransform() {
109+
try {
110+
instrumentation.retransformClasses(TransformerDeadlockTest.class);
111+
} catch (Exception e) {
112+
throw new RuntimeException(e);
113+
} finally {
114+
}
115+
}
116+
}
117+
}

0 commit comments

Comments
 (0)
Please sign in to comment.