8241390: 'Deadlock' with VM_RedefineClasses::lock_classes()

Reviewed-by: coleenp, sspitsyn
This commit is contained in:
Daniil Titov 2020-09-21 19:32:24 +00:00
parent 2e30ff61b0
commit f800af978c
4 changed files with 179 additions and 9 deletions

View File

@ -96,31 +96,69 @@ static inline InstanceKlass* get_ik(jclass def) {
// If any of the classes are being redefined, wait
// Parallel constant pool merging leads to indeterminate constant pools.
void VM_RedefineClasses::lock_classes() {
JvmtiThreadState *state = JvmtiThreadState::state_for(JavaThread::current());
GrowableArray<Klass*>* redef_classes = state->get_classes_being_redefined();
MonitorLocker ml(RedefineClasses_lock);
if (redef_classes == NULL) {
redef_classes = new(ResourceObj::C_HEAP, mtClass) GrowableArray<Klass*>(1, mtClass);
state->set_classes_being_redefined(redef_classes);
}
bool has_redefined;
do {
has_redefined = false;
// Go through classes each time until none are being redefined.
// Go through classes each time until none are being redefined. Skip
// the ones that are being redefined by this thread currently. Class file
// load hook event may trigger new class redefine when we are redefining
// a class (after lock_classes()).
for (int i = 0; i < _class_count; i++) {
if (get_ik(_class_defs[i].klass)->is_being_redefined()) {
ml.wait();
has_redefined = true;
break; // for loop
InstanceKlass* ik = get_ik(_class_defs[i].klass);
// Check if we are currently redefining the class in this thread already.
if (redef_classes->contains(ik)) {
assert(ik->is_being_redefined(), "sanity");
} else {
if (ik->is_being_redefined()) {
ml.wait();
has_redefined = true;
break; // for loop
}
}
}
} while (has_redefined);
for (int i = 0; i < _class_count; i++) {
get_ik(_class_defs[i].klass)->set_is_being_redefined(true);
InstanceKlass* ik = get_ik(_class_defs[i].klass);
redef_classes->push(ik); // Add to the _classes_being_redefined list
ik->set_is_being_redefined(true);
}
ml.notify_all();
}
void VM_RedefineClasses::unlock_classes() {
JvmtiThreadState *state = JvmtiThreadState::state_for(JavaThread::current());
GrowableArray<Klass*>* redef_classes = state->get_classes_being_redefined();
assert(redef_classes != NULL, "_classes_being_redefined is not allocated");
MonitorLocker ml(RedefineClasses_lock);
for (int i = 0; i < _class_count; i++) {
assert(get_ik(_class_defs[i].klass)->is_being_redefined(),
for (int i = _class_count - 1; i >= 0; i--) {
InstanceKlass* def_ik = get_ik(_class_defs[i].klass);
if (redef_classes->length() > 0) {
// Remove the class from _classes_being_redefined list
Klass* k = redef_classes->pop();
assert(def_ik == k, "unlocking wrong class");
}
assert(def_ik->is_being_redefined(),
"should be being redefined to get here");
get_ik(_class_defs[i].klass)->set_is_being_redefined(false);
// Unlock after we finish all redefines for this class within
// the thread. Same class can be pushed to the list multiple
// times (not more than once by each recursive redefinition).
if (!redef_classes->contains(def_ik)) {
def_ik->set_is_being_redefined(false);
}
}
ml.notify_all();
}

View File

@ -57,6 +57,7 @@ JvmtiThreadState::JvmtiThreadState(JavaThread* thread)
_pending_step_for_popframe = false;
_class_being_redefined = NULL;
_class_load_kind = jvmti_class_load_kind_load;
_classes_being_redefined = NULL;
_head_env_thread_state = NULL;
_dynamic_code_event_collector = NULL;
_vm_object_alloc_event_collector = NULL;
@ -106,6 +107,10 @@ JvmtiThreadState::JvmtiThreadState(JavaThread* thread)
JvmtiThreadState::~JvmtiThreadState() {
assert(JvmtiThreadState_lock->is_locked(), "sanity check");
if (_classes_being_redefined != NULL) {
delete _classes_being_redefined; // free the GrowableArray on C heap
}
// clear this as the state for the thread
get_thread()->set_jvmti_thread_state(NULL);

View File

@ -99,6 +99,7 @@ class JvmtiThreadState : public CHeapObj<mtInternal> {
// info to the class file load hook event handler.
Klass* _class_being_redefined;
JvmtiClassLoadKind _class_load_kind;
GrowableArray<Klass*>* _classes_being_redefined;
// This is only valid when is_interp_only_mode() returns true
int _cur_stack_depth;
@ -244,6 +245,15 @@ class JvmtiThreadState : public CHeapObj<mtInternal> {
return _class_load_kind;
}
// Get the classes that are currently being redefined by this thread.
inline GrowableArray<Klass*>* get_classes_being_redefined() {
return _classes_being_redefined;
}
inline void set_classes_being_redefined(GrowableArray<Klass*>* redef_classes) {
_classes_being_redefined = redef_classes;
}
// RedefineClasses support
// The bug 6214132 caused the verification to fail.
//

View File

@ -0,0 +1,117 @@
/*
* Copyright (c) 2020, 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 8241390
* @summary Test recursively retransforms the same class. The test hangs if
* a deadlock happens.
* @requires vm.jvmti
* @library /test/lib
* @modules java.instrument
* @compile TransformerDeadlockTest.java
* @run driver TransformerDeadlockTest
*/
import jdk.test.lib.process.ProcessTools;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.ProtectionDomain;
public class TransformerDeadlockTest {
private static String manifest = "Premain-Class: " +
TransformerDeadlockTest.Agent.class.getName() + "\n"
+ "Can-Retransform-Classes: true\n"
+ "Can-Retransform-Classes: true\n";
private static String CP = System.getProperty("test.classes");
public static void main(String args[]) throws Throwable {
String agentJar = buildAgent();
ProcessTools.executeProcess(
ProcessTools.createJavaProcessBuilder(
"-javaagent:" + agentJar,
TransformerDeadlockTest.Agent.class.getName())
).shouldHaveExitValue(0);
}
private static String buildAgent() throws Exception {
Path jar = Files.createTempFile(Paths.get("."), null, ".jar");
String jarPath = jar.toAbsolutePath().toString();
ClassFileInstaller.writeJar(jarPath,
ClassFileInstaller.Manifest.fromString(manifest),
TransformerDeadlockTest.class.getName());
return jarPath;
}
public static class Agent implements ClassFileTransformer {
private static Instrumentation instrumentation;
public static void premain(String agentArgs, Instrumentation inst) {
instrumentation = inst;
}
@Override
public byte[] transform(
ClassLoader loader,
String className,
Class<?> classBeingRedefined,
ProtectionDomain protectionDomain,
byte[] classfileBuffer)
throws IllegalClassFormatException {
if (!TransformerDeadlockTest.class.getName().replace(".", "/").equals(className)) {
return null;
}
invokeRetransform();
return classfileBuffer;
}
public static void main(String[] args) throws Exception {
instrumentation.addTransformer(new TransformerDeadlockTest.Agent(), true);
try {
instrumentation.retransformClasses(TransformerDeadlockTest.class);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private static void invokeRetransform() {
try {
instrumentation.retransformClasses(TransformerDeadlockTest.class);
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
}
}
}
}