Java 类加载出现死锁? 转
一、前言
先贴一份测试代码,大家可以先猜测一下,执行结果会是怎样的:
import java.util.concurrent.TimeUnit; public class TestClassLoading { public static class A{ static { System.out.println("class A init"); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } new B(); } public static void test() { System.out.println("aaa"); } } public static class B{ static { System.out.println("class B init"); new A(); } public static void test() { System.out.println("bbb"); } } public static void main(String[] args) { new Thread(() -> A.test()).start(); new Thread(() -> B.test()).start(); } }
不知道,你猜对了没有呢,实际的执行结果会是下面这样的:
二、原因分析
这里,一开始大家分析的是,和new有关系;但下面的代码和上面的结果完全一致,基本可以排除 new 的嫌疑:
public class TestClassLoadingNew { public static class A{ static { System.out.println("class A init"); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } B.test(); } public static void test() { System.out.println("aaa"); } } public static class B{ static { System.out.println("class B init"); A.test(); } public static void test() { System.out.println("bbb"); } } public static void main(String[] args) { new Thread(() -> A.test()).start(); new Thread(() -> B.test()).start(); } }
这里,问题的根本原因,其实是:
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,
JNIEXPORT jclass JNICALL Java_java_lang_ClassLoader_defineClass1(JNIEnv *env, jobject loader, jstring name, jbyteArray data, jint offset, jint length, jobject pd, jstring source) { jbyte *body; char *utfName; jclass result = 0; char buf[128]; char* utfSource; char sourceBuf[1024]; if (data == NULL) { JNU_ThrowNullPointerException(env, 0); return 0; } /* Work around 4153825. malloc crashes on Solaris when passed a * negative size. */ if (length < 0) { JNU_ThrowArrayIndexOutOfBoundsException(env, 0); return 0; } body = (jbyte *)malloc(length); if (body == 0) { JNU_ThrowOutOfMemoryError(env, 0); return 0; } (*env)->GetByteArrayRegion(env, data, offset, length, body); if ((*env)->ExceptionOccurred(env)) goto free_body; if (name != NULL) { utfName = getUTF(env, name, buf, sizeof(buf)); if (utfName == NULL) { goto free_body; } VerifyFixClassname(utfName); } else { utfName = NULL; } if (source != NULL) { utfSource = getUTF(env, source, sourceBuf, sizeof(sourceBuf)); if (utfSource == NULL) { goto free_utfName; } } else { utfSource = NULL; } result = JVM_DefineClassWithSource(env, utfName, loader, body, length, pd, utfSource); if (utfSource && utfSource != sourceBuf) free(utfSource); free_utfName: if (utfName && utfName != buf) free(utfName); free_body: free(body); return result; }
大家可以跟着标红的代码,我们一起大概看一下,这个方法的实现在/home/ckl/openjdk-jdk8u/hotspot/src/share/vm/prims/jvm.cpp 中,
JVM_ENTRY(jclass, JVM_DefineClassWithSource(JNIEnv *env, const char *name, jobject loader, const jbyte *buf, jsize len, jobject pd, const char *source)) JVMWrapper2("JVM_DefineClassWithSource %s", name); return jvm_define_class_common(env, name, loader, buf, len, pd, source, true, THREAD); JVM_END
jvm_define_class_common 的实现,还是在 jvm.cpp 中,
// common code for JVM_DefineClass() and JVM_DefineClassWithSource() // and JVM_DefineClassWithSourceCond() static jclass jvm_define_class_common(JNIEnv *env, const char *name, jobject loader, const jbyte *buf, jsize len, jobject pd, const char *source, jboolean verify, TRAPS) { if (source == NULL) source = "__JVM_DefineClass__"; assert(THREAD->is_Java_thread(), "must be a JavaThread"); JavaThread* jt = (JavaThread*) THREAD; PerfClassTraceTime vmtimer(ClassLoader::perf_define_appclass_time(), ClassLoader::perf_define_appclass_selftime(), ClassLoader::perf_define_appclasses(), jt->get_thread_stat()->perf_recursion_counts_addr(), jt->get_thread_stat()->perf_timers_addr(), PerfClassTraceTime::DEFINE_CLASS); if (UsePerfData) { ClassLoader::perf_app_classfile_bytes_read()->inc(len); } // Since exceptions can be thrown, class initialization can take place // if name is NULL no check for class name in .class stream has to be made. TempNewSymbol class_name = NULL; if (name != NULL) { const int str_len = (int)strlen(name); if (str_len > Symbol::max_length()) { // It's impossible to create this class; the name cannot fit // into the constant pool. THROW_MSG_0(vmSymbols::java_lang_NoClassDefFoundError(), name); } class_name = SymbolTable::new_symbol(name, str_len, CHECK_NULL); } ResourceMark rm(THREAD); ClassFileStream st((u1*) buf, len, (char *)source); Handle class_loader (THREAD, JNIHandles::resolve(loader)); if (UsePerfData) { is_lock_held_by_thread(class_loader, ClassLoader::sync_JVMDefineClassLockFreeCounter(), THREAD); } Handle protection_domain (THREAD, JNIHandles::resolve(pd)); Klass* k = SystemDictionary::resolve_from_stream(class_name, class_loader, protection_domain, &st, verify != 0, CHECK_NULL); if (TraceClassResolution && k != NULL) { trace_class_resolution(k); } return (jclass) JNIHandles::make_local(env, k->java_mirror()); }
resolve_from_stream 的实现在 SystemDictionary 类中,下面我们看下:
Klass* SystemDictionary::resolve_from_stream(Symbol* class_name, Handle class_loader, Handle protection_domain, ClassFileStream* st, bool verify, TRAPS) { // Classloaders that support parallelism, e.g. bootstrap classloader, // or all classloaders with UnsyncloadClass do not acquire lock here bool DoObjectLock = true; if (is_parallelCapable(class_loader)) { DoObjectLock = false; } ClassLoaderData* loader_data = register_loader(class_loader, CHECK_NULL); // Make sure we are synchronized on the class loader before we proceed Handle lockObject = compute_loader_lock_object(class_loader, THREAD); check_loader_lock_contention(lockObject, THREAD); ObjectLocker ol(lockObject, THREAD, DoObjectLock); TempNewSymbol parsed_name = NULL; // Parse the stream. Note that we do this even though this klass might // already be present in the SystemDictionary, otherwise we would not // throw potential ClassFormatErrors. // // Note: "name" is updated. instanceKlassHandle k = ClassFileParser(st).parseClassFile(class_name, loader_data, protection_domain, parsed_name, verify, THREAD); const char* pkg = "java/"; size_t pkglen = strlen(pkg); if (!HAS_PENDING_EXCEPTION && !class_loader.is_null() && parsed_name != NULL && parsed_name->utf8_length() >= (int)pkglen && !strncmp((const char*)parsed_name->bytes(), pkg, pkglen)) { // It is illegal to define classes in the "java." package from // JVM_DefineClass or jni_DefineClass unless you're the bootclassloader ResourceMark rm(THREAD); char* name = parsed_name->as_C_string(); char* index = strrchr(name, '/'); assert(index != NULL, "must be"); *index = '\0'; // chop to just the package name while ((index = strchr(name, '/')) != NULL) { *index = '.'; // replace '/' with '.' in package name } const char* fmt = "Prohibited package name: %s"; size_t len = strlen(fmt) + strlen(name); char* message = NEW_RESOURCE_ARRAY(char, len); jio_snprintf(message, len, fmt, name); Exceptions::_throw_msg(THREAD_AND_LOCATION, vmSymbols::java_lang_SecurityException(), message); } if (!HAS_PENDING_EXCEPTION) { assert(parsed_name != NULL, "Sanity"); assert(class_name == NULL || class_name == parsed_name, "name mismatch"); // Verification prevents us from creating names with dots in them, this // asserts that that's the case. assert(is_internal_format(parsed_name), "external class name format used internally"); // Add class just loaded // If a class loader supports parallel classloading handle parallel define requests // find_or_define_instance_class may return a different InstanceKlass if (is_parallelCapable(class_loader)) { k = find_or_define_instance_class(class_name, class_loader, k, THREAD); } else { define_instance_class(k, THREAD); } } 96 return k(); }
上面的方法里,有几处值得注意的:
1:18-20行,进行了加锁,18行获取锁对象,这里是当前类加载器(从注释可以看出),20行就是加锁的语法
2:37-60行,这里是判断要加载的类的包名是否以 java 开头,以 java 开头的类是非法的,不能加载
3:第76行, define_instance_class(k, THREAD); 进行后续操作
接下来,我们看看 define_instance_class 的实现:
void SystemDictionary::define_instance_class(instanceKlassHandle k, TRAPS) { ClassLoaderData* loader_data = k->class_loader_data(); Handle class_loader_h(THREAD, loader_data->class_loader()); for (uintx it = 0; it < GCExpandToAllocateDelayMillis; it++){} // for bootstrap and other parallel classloaders don't acquire lock, // use placeholder token // If a parallelCapable class loader calls define_instance_class instead of // find_or_define_instance_class to get here, we have a timing // hole with systemDictionary updates and check_constraints if (!class_loader_h.is_null() && !is_parallelCapable(class_loader_h)) { assert(ObjectSynchronizer::current_thread_holds_lock((JavaThread*)THREAD, compute_loader_lock_object(class_loader_h, THREAD)), "define called without lock"); } // Check class-loading constraints. Throw exception if violation is detected. // Grabs and releases SystemDictionary_lock // The check_constraints/find_class call and update_dictionary sequence // must be "atomic" for a specific class/classloader pair so we never // define two different instanceKlasses for that class/classloader pair. // Existing classloaders will call define_instance_class with the // classloader lock held // Parallel classloaders will call find_or_define_instance_class // which will require a token to perform the define class Symbol* name_h = k->name(); unsigned int d_hash = dictionary()->compute_hash(name_h, loader_data); int d_index = dictionary()->hash_to_index(d_hash); check_constraints(d_index, d_hash, k, class_loader_h, true, CHECK); // Register class just loaded with class loader (placed in Vector) // Note we do this before updating the dictionary, as this can // fail with an OutOfMemoryError (if it does, we will *not* put this // class in the dictionary and will not update the class hierarchy). // JVMTI FollowReferences needs to find the classes this way. if (k->class_loader() != NULL) { methodHandle m(THREAD, Universe::loader_addClass_method()); JavaValue result(T_VOID); JavaCallArguments args(class_loader_h); args.push_oop(Handle(THREAD, k->java_mirror())); JavaCalls::call(&result, m, &args, CHECK); } // Add the new class. We need recompile lock during update of CHA. { unsigned int p_hash = placeholders()->compute_hash(name_h, loader_data); int p_index = placeholders()->hash_to_index(p_hash); MutexLocker mu_r(Compile_lock, THREAD); // Add to class hierarchy, initialize vtables, and do possible // deoptimizations. add_to_hierarchy(k, CHECK); // No exception, but can block // Add to systemDictionary - so other classes can see it. // Grabs and releases SystemDictionary_lock update_dictionary(d_index, d_hash, p_index, p_hash, k, class_loader_h, THREAD); } k->eager_initialize(THREAD); // notify jvmti if (JvmtiExport::should_post_class_load()) { assert(THREAD->is_Java_thread(), "thread->is_Java_thread()"); JvmtiExport::post_class_load((JavaThread *) THREAD, k()); } }
这里,由于我们的案例中,是class A 在初始化过程中出现死锁,所以我们关注第62行,eager_initialize:
void InstanceKlass::eager_initialize(Thread *thread) { if (!EagerInitialization) return; if (this->is_not_initialized()) { // abort if the the class has a class initializer if (this->class_initializer() != NULL) return; // abort if it is java.lang.Object (initialization is handled in genesis) Klass* super = this->super(); if (super == NULL) return; // abort if the super class should be initialized if (!InstanceKlass::cast(super)->is_initialized()) return; // call body to expose the this pointer instanceKlassHandle this_oop(thread, this); eager_initialize_impl(this_oop); } }
我们接着进入 eager_initialize_impl,该方法进入到了 InstanceKlass:
void InstanceKlass::eager_initialize_impl(instanceKlassHandle this_oop) { EXCEPTION_MARK; oop init_lock = this_oop->init_lock(); ObjectLocker ol(init_lock, THREAD, init_lock != NULL); // abort if someone beat us to the initialization if (!this_oop->is_not_initialized()) return; // note: not equivalent to is_initialized() ClassState old_state = this_oop->init_state(); link_class_impl(this_oop, true, THREAD); if (HAS_PENDING_EXCEPTION) { CLEAR_PENDING_EXCEPTION; // Abort if linking the class throws an exception. // Use a test to avoid redundantly resetting the state if there's // no change. Set_init_state() asserts that state changes make // progress, whereas here we might just be spinning in place. if( old_state != this_oop->_init_state ) this_oop->set_init_state (old_state); } else { // linking successfull, mark class as initialized this_oop->set_init_state (fully_initialized); this_oop->fence_and_clear_init_lock(); // trace if (TraceClassInitialization) { ResourceMark rm(THREAD); tty->print_cr("[Initialized %s without side effects]", this_oop->external_name()); } } }
这里,我们重点关注第3,4行:
1、第3行,获取初始化锁;
2、第4行,加锁
2、获取初始化锁并加锁
这里,我们首先获取锁的操作,
oop InstanceKlass::init_lock() const { // return the init lock from the mirror oop lock = java_lang_Class::init_lock(java_mirror()); // Prevent reordering with any access of initialization state OrderAccess::loadload(); assert((oop)lock != NULL || !is_not_initialized(), // initialized or in_error state "only fully initialized state can have a null lock"); return lock; }
其中,java_mirror() 方法就是返回 Klass 类中的以下字段:
// java/lang/Class instance mirroring this class oop _java_mirror;
再看 init_lock 方法:
oop java_lang_Class::init_lock(oop java_class) { assert(_init_lock_offset != 0, "must be set"); return java_class->obj_field(_init_lock_offset); }
这里呢,应该就是获取 我们传入的 java_class 中的某个字段,该字段就是充当 init_lock。(个人水平有限,还请指正)
下面为加锁操作的语句:
ObjectLocker ol(init_lock, THREAD, init_lock != NULL);
/ ObjectLocker enforced balanced locking and can never thrown an // IllegalMonitorStateException. However, a pending exception may // have to pass through, and we must also be able to deal with // asynchronous exceptions. The caller is responsible for checking // the threads pending exception if needed. // doLock was added to support classloading with UnsyncloadClass which // requires flag based choice of locking the classloader lock. class ObjectLocker : public StackObj { private: Thread* _thread; Handle _obj; BasicLock _lock; bool _dolock; // default true public: ObjectLocker(Handle obj, Thread* thread, bool doLock = true);
// ----------------------------------------------------------------------------- // Internal VM locks on java objects // standard constructor, allows locking failures ObjectLocker::ObjectLocker(Handle obj, Thread* thread, bool doLock) { _dolock = doLock; _thread = thread; _obj = obj; if (_dolock) { TEVENT (ObjectLocker) ; ObjectSynchronizer::fast_enter(_obj, &_lock, false, _thread); } }
接下来会进入到 synchronizer.cpp,
// ----------------------------------------------------------------------------- // Fast Monitor Enter/Exit // This the fast monitor enter. The interpreter and compiler use // some assembly copies of this code. Make sure update those code // if the following function is changed. The implementation is // extremely sensitive to race condition. Be careful. void ObjectSynchronizer::fast_enter(Handle obj, BasicLock* lock, bool attempt_rebias, TRAPS) { if (UseBiasedLocking) { if (!SafepointSynchronize::is_at_safepoint()) { BiasedLocking::Condition cond = BiasedLocking::revoke_and_rebias(obj, attempt_rebias, THREAD); if (cond == BiasedLocking::BIAS_REVOKED_AND_REBIASED) { return; } } else { assert(!attempt_rebias, "can not rebias toward VM thread"); BiasedLocking::revoke_at_safepoint(obj); } assert(!obj->mark()->has_bias_pattern(), "biases should be revoked by now"); } slow_enter (obj, lock, THREAD) ; }
上面会判断,是否使用偏向锁,如果不使用,则走 slow_enter 。
// ----------------------------------------------------------------------------- // Interpreter/Compiler Slow Case // This routine is used to handle interpreter/compiler slow case // We don't need to use fast path here, because it must have been // failed in the interpreter/compiler code. void ObjectSynchronizer::slow_enter(Handle obj, BasicLock* lock, TRAPS) { markOop mark = obj->mark(); assert(!mark->has_bias_pattern(), "should not see bias pattern here"); if (mark->is_neutral()) { // Anticipate successful CAS -- the ST of the displaced mark must // be visible <= the ST performed by the CAS. lock->set_displaced_header(mark); if (mark == (markOop) Atomic::cmpxchg_ptr(lock, obj()->mark_addr(), mark)) { TEVENT (slow_enter: release stacklock) ; return ; } // Fall through to inflate() ... } else if (mark->has_locker() && THREAD->is_lock_owned((address)mark->locker())) { assert(lock != mark->locker(), "must not re-lock the same lock"); assert(lock != (BasicLock*)obj->mark(), "don't relock with same BasicLock"); lock->set_displaced_header(NULL); return; } // The object header will never be displaced to this lock, // so it does not matter what the value is, except that it // must be non-zero to avoid looking like a re-entrant lock, // and must not look locked either. lock->set_displaced_header(markOopDesc::unused_mark()); ObjectSynchronizer::inflate(THREAD, obj())->enter(THREAD); }
这里的代码结合注释,能大概看出来是,前面部分为轻量级锁,这里先不展开了,锁这块都可以单独写了。有兴趣的读者可以自行阅读。
四:如何解决类加载出现的死锁问题?
可以显式在主线程最开始用forName加载这些类的,这样类加载就变成在main线程中串行加载,问题得到解决:
public static void main(String[] args) throws ClassNotFoundException{ Class.forName("com.**.**.A"); Class.forName("com.**.**.B"); new Thread(() -> A.test(), "thread-1").start(); new Thread(() -> B.test(), "thread-2").start(); }
五、总结
这里再说下结论吧,类初始化的过程,会对class加锁,再执行class的初始化,如果这时候发生了循环依赖,就会导致死锁。