zoukankan      html  css  js  c++  java
  • Java 类加载出现死锁? 转

    出处: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 = ''; // 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的初始化,如果这时候发生了循环依赖,就会导致死锁。

  • 相关阅读:
    [js高手之路] es6系列教程
    [js高手之路] es6系列教程
    [js高手之路] es6系列教程
    [js高手之路]Node.js+jade+mongoose实战todolist(分页,ajax编辑,删除)
    [js高手之路]Node.js+jade+express+mongodb+mongoose+promise实现todolist
    [js高手之路]Node.js+jade+mongodb+mongoose实现爬虫分离入库与生成静态文件
    [js高手之路]Node.js+jade抓取博客所有文章生成静态html文件
    [js高手之路]Node.js模板引擎教程-jade速学与实战4-模板引用,继承,插件使用
    [js高手之路]Node.js模板引擎教程-jade速学与实战3-mixin
    [js高手之路]Node.js模板引擎教程-jade速学与实战2-流程控制,转义与非转义
  • 原文地址:https://www.cnblogs.com/myseries/p/12899428.html
Copyright © 2011-2022 走看看