曹工杂谈:Java 类加载还会死锁?这是什么情况?
- 2019 年 10 月 3 日
- 筆記
一、前言
今天事不是很多,正好在Java交流群里,看到一个比较有意思的问题,于是花了点时间研究了一下,这里做个简单的分享。
先贴一份测试代码,大家可以先猜测一下,执行结果会是怎样的:
2 3 import java.util.concurrent.TimeUnit; 4 5 6 public class TestClassLoading { 7 public static class A{ 8 static { 9 System.out.println("class A init"); 10 try { 11 TimeUnit.SECONDS.sleep(1); 12 } catch (InterruptedException e) { 13 e.printStackTrace(); 14 } 15 new B(); 16 } 17 18 public static void test() { 19 System.out.println("aaa"); 20 } 21 } 22 23 public static class B{ 24 static { 25 System.out.println("class B init"); 26 new A(); 27 } 28 29 30 public static void test() { 31 System.out.println("bbb"); 32 } 33 } 34 public static void main(String[] args) { 35 new Thread(() -> A.test()).start(); 36 new Thread(() -> B.test()).start(); 37 } 38 }
不知道,你猜对了没有呢,实际的执行结果会是下面这样的:
二、原因分析
这里,一开始大家分析的是,和new有关系;但下面的代码和上面的结果完全一致,基本可以排除 new 的嫌疑:
1 public class TestClassLoadingNew { 2 public static class A{ 3 static { 4 System.out.println("class A init"); 5 try { 6 TimeUnit.SECONDS.sleep(1); 7 } catch (InterruptedException e) { 8 e.printStackTrace(); 9 } 10 B.test(); 11 } 12 13 public static void test() { 14 System.out.println("aaa"); 15 } 16 } 17 18 public static class B{ 19 static { 20 System.out.println("class B init"); 21 A.test(); 22 } 23 24 25 public static void test() { 26 System.out.println("bbb"); 27 } 28 } 29 public static void main(String[] args) { 30 new Thread(() -> A.test()).start(); 31 new Thread(() -> B.test()).start(); 32 } 33 }
这里,问题的根本原因,其实是:
classloader在初始化一个类的时候,会对当前类加锁,加锁后,再执行类的静态初始化块。
所以,上面会发生:
1、线程1:类A对class A加锁,加锁后,执行类的静态初始化块(在堆栈里体现为<clinit>函数),发现用到了class B,于是去加载B;
2、线程2:类B对class B加锁,加锁后,执行类的静态初始化块(在堆栈里体现为<clinit>函数),发现用到了class A,于是去加载A;
3、死锁发生。
有经验的同学,对于死锁是毫无畏惧的,因为我们有神器,jstack。 jstack 加上 -l 参数,即可打印出各个线程持有的锁的信息。(windows上直接jconsole就行,还能死锁检测):
"Thread-1" #15 prio=5 os_prio=0 tid=0x000000002178a000 nid=0x2df8 in Object.wait() [0x0000000021f4e000] java.lang.Thread.State: RUNNABLE at com.dmtest.netty_learn.TestClassLoading$B.<clinit>(TestClassLoading.java:32) at com.dmtest.netty_learn.TestClassLoading.lambda$main$1(TestClassLoading.java:42) at com.dmtest.netty_learn.TestClassLoading$$Lambda$2/736709391.run(Unknown Source) at java.lang.Thread.run(Thread.java:748) Locked ownable synchronizers: - None "Thread-0" #14 prio=5 os_prio=0 tid=0x0000000021787800 nid=0x2618 in Object.wait() [0x00000000213be000] java.lang.Thread.State: RUNNABLE at com.dmtest.netty_learn.TestClassLoading$A.<clinit>(TestClassLoading.java:21) at com.dmtest.netty_learn.TestClassLoading.lambda$main$0(TestClassLoading.java:41) at com.dmtest.netty_learn.TestClassLoading$$Lambda$1/611437735.run(Unknown Source) at java.lang.Thread.run(Thread.java:748) Locked ownable synchronizers: - None
这里,很奇怪的一个原因是,明明这两个线程发生了死锁,为什么没有显示呢?
因为,这是 jvm 内部加了锁,所以,jconsole、jstack都失效了。
三、一起深入JVM,探个究竟
1、单步跟踪
class 的加载都是由 classloader 来完成的,而且部分工作是在 jvm 层面完成,我们可以看到,在 java.lang.ClassLoader#defineClass1 的定义中:
以上几个方法都是本地方法。
其实际的实现在:/home/ckl/openjdk-jdk8u/jdk/src/share/native/java/lang/ClassLoader.c,
1 JNIEXPORT jclass JNICALL 2 Java_java_lang_ClassLoader_defineClass1(JNIEnv *env, 3 jobject loader, 4 jstring name, 5 jbyteArray data, 6 jint offset, 7 jint length, 8 jobject pd, 9 jstring source) 10 { 11 jbyte *body; 12 char *utfName; 13 jclass result = 0; 14 char buf[128]; 15 char* utfSource; 16 char sourceBuf[1024]; 17 18 if (data == NULL) { 19 JNU_ThrowNullPointerException(env, 0); 20 return 0; 21 } 22 23 /* Work around 4153825. malloc crashes on Solaris when passed a 24 * negative size. 25 */ 26 if (length < 0) { 27 JNU_ThrowArrayIndexOutOfBoundsException(env, 0); 28 return 0; 29 } 30 31 body = (jbyte *)malloc(length); 32 33 if (body == 0) { 34 JNU_ThrowOutOfMemoryError(env, 0); 35 return 0; 36 } 37 38 (*env)->GetByteArrayRegion(env, data, offset, length, body); 39 40 if ((*env)->ExceptionOccurred(env)) 41 goto free_body; 42 43 if (name != NULL) { 44 utfName = getUTF(env, name, buf, sizeof(buf)); 45 if (utfName == NULL) { 46 goto free_body; 47 } 48 VerifyFixClassname(utfName); 49 } else { 50 utfName = NULL; 51 } 52 53 if (source != NULL) { 54 utfSource = getUTF(env, source, sourceBuf, sizeof(sourceBuf)); 55 if (utfSource == NULL) { 56 goto free_utfName; 57 } 58 } else { 59 utfSource = NULL; 60 } 61 result = JVM_DefineClassWithSource(env, utfName, loader, body, length, pd, utfSource); 62 63 if (utfSource && utfSource != sourceBuf) 64 free(utfSource); 65 66 free_utfName: 67 if (utfName && utfName != buf) 68 free(utfName); 69 70 free_body: 71 free(body); 72 return result; 73 }
大家可以跟着标红的代码,我们一起大概看一下,这个方法的实现在/home/ckl/openjdk-jdk8u/hotspot/src/share/vm/prims/jvm.cpp 中,
1 JVM_ENTRY(jclass, JVM_DefineClassWithSource(JNIEnv *env, const char *name, jobject loader, const jbyte *buf, jsize len, jobject pd, const char *source)) 2 JVMWrapper2("JVM_DefineClassWithSource %s", name); 3 4 return jvm_define_class_common(env, name, loader, buf, len, pd, source, true, THREAD); 5 JVM_END
jvm_define_class_common 的实现,还是在 jvm.cpp 中,
1 // common code for JVM_DefineClass() and JVM_DefineClassWithSource() 2 // and JVM_DefineClassWithSourceCond() 3 static jclass jvm_define_class_common(JNIEnv *env, const char *name, 4 jobject loader, const jbyte *buf, 5 jsize len, jobject pd, const char *source, 6 jboolean verify, TRAPS) { 7 if (source == NULL) source = "__JVM_DefineClass__"; 8 9 assert(THREAD->is_Java_thread(), "must be a JavaThread"); 10 JavaThread* jt = (JavaThread*) THREAD; 11 12 PerfClassTraceTime vmtimer(ClassLoader::perf_define_appclass_time(), 13 ClassLoader::perf_define_appclass_selftime(), 14 ClassLoader::perf_define_appclasses(), 15 jt->get_thread_stat()->perf_recursion_counts_addr(), 16 jt->get_thread_stat()->perf_timers_addr(), 17 PerfClassTraceTime::DEFINE_CLASS); 18 19 if (UsePerfData) { 20 ClassLoader::perf_app_classfile_bytes_read()->inc(len); 21 } 22 23 // Since exceptions can be thrown, class initialization can take place 24 // if name is NULL no check for class name in .class stream has to be made. 25 TempNewSymbol class_name = NULL; 26 if (name != NULL) { 27 const int str_len = (int)strlen(name); 28 if (str_len > Symbol::max_length()) { 29 // It's impossible to create this class; the name cannot fit 30 // into the constant pool. 31 THROW_MSG_0(vmSymbols::java_lang_NoClassDefFoundError(), name); 32 } 33 class_name = SymbolTable::new_symbol(name, str_len, CHECK_NULL); 34 } 35 36 ResourceMark rm(THREAD); 37 ClassFileStream st((u1*) buf, len, (char *)source); 38 Handle class_loader (THREAD, JNIHandles::resolve(loader)); 39 if (UsePerfData) { 40 is_lock_held_by_thread(class_loader, 41 ClassLoader::sync_JVMDefineClassLockFreeCounter(), 42 THREAD); 43 } 44 Handle protection_domain (THREAD, JNIHandles::resolve(pd)); 45 Klass* k = SystemDictionary::resolve_from_stream(class_name, class_loader, 46 protection_domain, &st, 47 verify != 0, 48 CHECK_NULL); 49 50 if (TraceClassResolution && k != NULL) { 51 trace_class_resolution(k); 52 } 53 54 return (jclass) JNIHandles::make_local(env, k->java_mirror()); 55 }
resolve_from_stream 的实现在 SystemDictionary 类中,下面我们看下:
1 Klass* SystemDictionary::resolve_from_stream(Symbol* class_name, 2 Handle class_loader, 3 Handle protection_domain, 4 ClassFileStream* st, 5 bool verify, 6 TRAPS) { 7 8 // Classloaders that support parallelism, e.g. bootstrap classloader, 9 // or all classloaders with UnsyncloadClass do not acquire lock here 10 bool DoObjectLock = true; 11 if (is_parallelCapable(class_loader)) { 12 DoObjectLock = false; 13 } 14 15 ClassLoaderData* loader_data = register_loader(class_loader, CHECK_NULL); 16 17 // Make sure we are synchronized on the class loader before we proceed
18 Handle lockObject = compute_loader_lock_object(class_loader, THREAD); 19 check_loader_lock_contention(lockObject, THREAD); 20 ObjectLocker ol(lockObject, THREAD, DoObjectLock); 21 22 TempNewSymbol parsed_name = NULL; 23 24 // Parse the stream. Note that we do this even though this klass might 25 // already be present in the SystemDictionary, otherwise we would not 26 // throw potential ClassFormatErrors. 27 // 28 // Note: "name" is updated. 29 30 instanceKlassHandle k = ClassFileParser(st).parseClassFile(class_name, 31 loader_data, 32 protection_domain, 33 parsed_name, 34 verify, 35 THREAD); 36 37 const char* pkg = "java/"; 38 size_t pkglen = strlen(pkg); 39 if (!HAS_PENDING_EXCEPTION && 40 !class_loader.is_null() && 41 parsed_name != NULL && 42 parsed_name->utf8_length() >= (int)pkglen && 43 !strncmp((const char*)parsed_name->bytes(), pkg, pkglen)) { 44 // It is illegal to define classes in the "java." package from 45 // JVM_DefineClass or jni_DefineClass unless you're the bootclassloader 46 ResourceMark rm(THREAD); 47 char* name = parsed_name->as_C_string(); 48 char* index = strrchr(name, '/'); 49 assert(index != NULL, "must be"); 50 *index = '