一、前言
今天事不是很多,正好在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 = '