曹工杂谈: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 = '