曹工杂谈:Java 类加载还会死锁?这是什么情况?

一、前言

今天事不是很多,正好在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 = ‘\0‘; // chop to just the package name
51     while ((index = strchr(name, ‘/‘)) != NULL) {
52       *index = ‘.‘; // replace ‘/‘ with ‘.‘ in package name
53     }
54     const char* fmt = "Prohibited package name: %s";
55     size_t len = strlen(fmt) + strlen(name);
56     char* message = NEW_RESOURCE_ARRAY(char, len);
57     jio_snprintf(message, len, fmt, name);
58     Exceptions::_throw_msg(THREAD_AND_LOCATION,
59       vmSymbols::java_lang_SecurityException(), message);
60   }
61
62   if (!HAS_PENDING_EXCEPTION) {
63     assert(parsed_name != NULL, "Sanity");
64     assert(class_name == NULL || class_name == parsed_name, "name mismatch");
65     // Verification prevents us from creating names with dots in them, this
66     // asserts that that‘s the case.
67     assert(is_internal_format(parsed_name),
68            "external class name format used internally");
69
70     // Add class just loaded
71     // If a class loader supports parallel classloading handle parallel define requests
72     // find_or_define_instance_class may return a different InstanceKlass
73     if (is_parallelCapable(class_loader)) {
74       k = find_or_define_instance_class(class_name, class_loader, k, THREAD);
75     } else {
76       define_instance_class(k, THREAD);
77     }
78   }
79 96
97   return k();
98 }

上面的方法里,有几处值得注意的:

1:18-20行,进行了加锁,18行获取锁对象,这里是当前类加载器(从注释可以看出),20行就是加锁的语法

2:37-60行,这里是判断要加载的类的包名是否以 java 开头,以 java 开头的类是非法的,不能加载

3:第76行, define_instance_class(k, THREAD); 进行后续操作

接下来,我们看看 define_instance_class 的实现:

 1 void SystemDictionary::define_instance_class(instanceKlassHandle k, TRAPS) {
 2
 3   ClassLoaderData* loader_data = k->class_loader_data();
 4   Handle class_loader_h(THREAD, loader_data->class_loader());
 5
 6   for (uintx it = 0; it < GCExpandToAllocateDelayMillis; it++){}
 7
 8  // for bootstrap and other parallel classloaders don‘t acquire lock,
 9  // use placeholder token
10  // If a parallelCapable class loader calls define_instance_class instead of
11  // find_or_define_instance_class to get here, we have a timing
12  // hole with systemDictionary updates and check_constraints
13  if (!class_loader_h.is_null() && !is_parallelCapable(class_loader_h)) {
14     assert(ObjectSynchronizer::current_thread_holds_lock((JavaThread*)THREAD,
15          compute_loader_lock_object(class_loader_h, THREAD)),
16          "define called without lock");
17   }
18
19   // Check class-loading constraints. Throw exception if violation is detected.
20   // Grabs and releases SystemDictionary_lock
21   // The check_constraints/find_class call and update_dictionary sequence
22   // must be "atomic" for a specific class/classloader pair so we never
23   // define two different instanceKlasses for that class/classloader pair.
24   // Existing classloaders will call define_instance_class with the
25   // classloader lock held
26   // Parallel classloaders will call find_or_define_instance_class
27   // which will require a token to perform the define class
28   Symbol*  name_h = k->name();
29   unsigned int d_hash = dictionary()->compute_hash(name_h, loader_data);
30   int d_index = dictionary()->hash_to_index(d_hash);
31   check_constraints(d_index, d_hash, k, class_loader_h, true, CHECK);
32
33   // Register class just loaded with class loader (placed in Vector)
34   // Note we do this before updating the dictionary, as this can
35   // fail with an OutOfMemoryError (if it does, we will *not* put this
36   // class in the dictionary and will not update the class hierarchy).
37   // JVMTI FollowReferences needs to find the classes this way.
38   if (k->class_loader() != NULL) {
39     methodHandle m(THREAD, Universe::loader_addClass_method());
40     JavaValue result(T_VOID);
41     JavaCallArguments args(class_loader_h);
42     args.push_oop(Handle(THREAD, k->java_mirror()));
43     JavaCalls::call(&result, m, &args, CHECK);
44   }
45
46   // Add the new class. We need recompile lock during update of CHA.
47   {
48     unsigned int p_hash = placeholders()->compute_hash(name_h, loader_data);
49     int p_index = placeholders()->hash_to_index(p_hash);
50
51     MutexLocker mu_r(Compile_lock, THREAD);
52
53     // Add to class hierarchy, initialize vtables, and do possible
54     // deoptimizations.
55     add_to_hierarchy(k, CHECK); // No exception, but can block
56
57     // Add to systemDictionary - so other classes can see it.
58     // Grabs and releases SystemDictionary_lock
59     update_dictionary(d_index, d_hash, p_index, p_hash,
60                       k, class_loader_h, THREAD);
61   }
62   k->eager_initialize(THREAD);
63
64   // notify jvmti
65   if (JvmtiExport::should_post_class_load()) {
66       assert(THREAD->is_Java_thread(), "thread->is_Java_thread()");
67       JvmtiExport::post_class_load((JavaThread *) THREAD, k());
68
69   }
70
71 }

这里,由于我们的案例中,是class A 在初始化过程中出现死锁,所以我们关注第62行,eager_initialize:

 1 void InstanceKlass::eager_initialize(Thread *thread) {
 2   if (!EagerInitialization) return;
 3
 4   if (this->is_not_initialized()) {
 5     // abort if the the class has a class initializer
 6     if (this->class_initializer() != NULL) return;
 7
 8     // abort if it is java.lang.Object (initialization is handled in genesis)
 9     Klass* super = this->super();
10     if (super == NULL) return;
11
12     // abort if the super class should be initialized
13     if (!InstanceKlass::cast(super)->is_initialized()) return;
14
15     // call body to expose the this pointer
16     instanceKlassHandle this_oop(thread, this);
17     eager_initialize_impl(this_oop);
18   }
19 }

我们接着进入 eager_initialize_impl,该方法进入到了 InstanceKlass:

 1 void InstanceKlass::eager_initialize_impl(instanceKlassHandle this_oop) {
 2   EXCEPTION_MARK;
 3   oop init_lock = this_oop->init_lock();
 4   ObjectLocker ol(init_lock, THREAD, init_lock != NULL);
 5
 6   // abort if someone beat us to the initialization
 7   if (!this_oop->is_not_initialized()) return;  // note: not equivalent to is_initialized()
 8
 9   ClassState old_state = this_oop->init_state();
10   link_class_impl(this_oop, true, THREAD);
11   if (HAS_PENDING_EXCEPTION) {
12     CLEAR_PENDING_EXCEPTION;
13     // Abort if linking the class throws an exception.
14
15     // Use a test to avoid redundantly resetting the state if there‘s
16     // no change.  Set_init_state() asserts that state changes make
17     // progress, whereas here we might just be spinning in place.
18     if( old_state != this_oop->_init_state )
19       this_oop->set_init_state (old_state);
20   } else {
21     // linking successfull, mark class as initialized
22     this_oop->set_init_state (fully_initialized);
23     this_oop->fence_and_clear_init_lock();
24     // trace
25     if (TraceClassInitialization) {
26       ResourceMark rm(THREAD);
27       tty->print_cr("[Initialized %s without side effects]", this_oop->external_name());
28     }
29   }
30 }

这里,我们重点关注第3,4行:

1、第3行,获取初始化锁;

2、第4行,加锁

2、获取初始化锁并加锁

这里,我们首先获取锁的操作,

1 oop InstanceKlass::init_lock() const {
2   // return the init lock from the mirror
3   oop lock = java_lang_Class::init_lock(java_mirror());
4   // Prevent reordering with any access of initialization state
5   OrderAccess::loadload();
6   assert((oop)lock != NULL || !is_not_initialized(), // initialized or in_error state
7          "only fully initialized state can have a null lock");
8   return lock;
9 }

其中,java_mirror() 方法就是返回 Klass 类中的以下字段:

1   // java/lang/Class instance mirroring this class
2   oop       _java_mirror;

再看 init_lock 方法:

1 oop java_lang_Class::init_lock(oop java_class) {
2   assert(_init_lock_offset != 0, "must be set");
3   return java_class->obj_field(_init_lock_offset);
4 }

这里呢,应该就是获取 我们传入的 java_class 中的某个字段,该字段就是充当 init_lock。(个人水平有限,还请指正)

下面为加锁操作的语句:

1   ObjectLocker ol(init_lock, THREAD, init_lock != NULL);
 1 / ObjectLocker enforced balanced locking and can never thrown an
 2 // IllegalMonitorStateException. However, a pending exception may
 3 // have to pass through, and we must also be able to deal with
 4 // asynchronous exceptions. The caller is responsible for checking
 5 // the threads pending exception if needed.
 6 // doLock was added to support classloading with UnsyncloadClass which
 7 // requires flag based choice of locking the classloader lock.
 8 class ObjectLocker : public StackObj {
 9  private:
10   Thread*   _thread;
11   Handle    _obj;
12   BasicLock _lock;
13   bool      _dolock;   // default true
14  public:
15   ObjectLocker(Handle obj, Thread* thread, bool doLock = true);
 1 // -----------------------------------------------------------------------------
 2 // Internal VM locks on java objects
 3 // standard constructor, allows locking failures
 4 ObjectLocker::ObjectLocker(Handle obj, Thread* thread, bool doLock) {
 5   _dolock = doLock;
 6   _thread = thread;
 8   _obj = obj;
 9
10   if (_dolock) {
11     TEVENT (ObjectLocker) ;
12
13     ObjectSynchronizer::fast_enter(_obj, &_lock, false, _thread);
14   }
15 }

接下来会进入到 synchronizer.cpp,

 1 // -----------------------------------------------------------------------------
 2 //  Fast Monitor Enter/Exit
 3 // This the fast monitor enter. The interpreter and compiler use
 4 // some assembly copies of this code. Make sure update those code
 5 // if the following function is changed. The implementation is
 6 // extremely sensitive to race condition. Be careful.
 7
 8 void ObjectSynchronizer::fast_enter(Handle obj, BasicLock* lock, bool attempt_rebias, TRAPS) {
 9  if (UseBiasedLocking) {
10     if (!SafepointSynchronize::is_at_safepoint()) {
11       BiasedLocking::Condition cond = BiasedLocking::revoke_and_rebias(obj, attempt_rebias, THREAD);
12       if (cond == BiasedLocking::BIAS_REVOKED_AND_REBIASED) {
13         return;
14       }
15     } else {
16       assert(!attempt_rebias, "can not rebias toward VM thread");
17       BiasedLocking::revoke_at_safepoint(obj);
18     }
19     assert(!obj->mark()->has_bias_pattern(), "biases should be revoked by now");
20  }
21
22  slow_enter (obj, lock, THREAD) ;
23 }

上面会判断,是否使用偏向锁,如果不使用,则走 slow_enter 。

 1 // -----------------------------------------------------------------------------
 2 // Interpreter/Compiler Slow Case
 3 // This routine is used to handle interpreter/compiler slow case
 4 // We don‘t need to use fast path here, because it must have been
 5 // failed in the interpreter/compiler code.
 6 void ObjectSynchronizer::slow_enter(Handle obj, BasicLock* lock, TRAPS) {
 7   markOop mark = obj->mark();
 8   assert(!mark->has_bias_pattern(), "should not see bias pattern here");
 9
10   if (mark->is_neutral()) {
11     // Anticipate successful CAS -- the ST of the displaced mark must
12     // be visible <= the ST performed by the CAS.
13     lock->set_displaced_header(mark);
14     if (mark == (markOop) Atomic::cmpxchg_ptr(lock, obj()->mark_addr(), mark)) {
15       TEVENT (slow_enter: release stacklock) ;
16       return ;
17     }
18     // Fall through to inflate() ...
19   } else
20   if (mark->has_locker() && THREAD->is_lock_owned((address)mark->locker())) {
21     assert(lock != mark->locker(), "must not re-lock the same lock");
22     assert(lock != (BasicLock*)obj->mark(), "don‘t relock with same BasicLock");
23     lock->set_displaced_header(NULL);
24     return;
25   }
26
34
35   // The object header will never be displaced to this lock,
36   // so it does not matter what the value is, except that it
37   // must be non-zero to avoid looking like a re-entrant lock,
38   // and must not look locked either.
39   lock->set_displaced_header(markOopDesc::unused_mark());
40   ObjectSynchronizer::inflate(THREAD, obj())->enter(THREAD);
41 }

这里的代码结合注释,能大概看出来是,前面部分为轻量级锁,这里先不展开了,锁这块都可以单独写了。有兴趣的读者可以自行阅读。

四、总结

这里再说下结论吧,类初始化的过程,会对class加锁,再执行class的初始化,如果这时候发生了循环依赖,就会导致死锁。

如果有读者对上面的c++代码感兴趣,可以参考下面的文章,搭建调试环境:

源码编译OpenJdk 8,Netbeans调试Java原子类在JVM中的实现(Ubuntu 16.04)

原文地址:https://www.cnblogs.com/grey-wolf/p/11378747.html

时间: 2024-08-02 02:41:05

曹工杂谈:Java 类加载还会死锁?这是什么情况?的相关文章

曹工杂谈:用好verbose,Jar包冲突不再难

Jar包冲突的相关文章: 了不得,我可能发现了Jar 包冲突的秘密 一.前言 jar包冲突分多种,简单理解来说,就是同package且同名的类在多个jar包内出现,如果两个jar包在同一个classloader下,那么最终的结果是,只会加载其中的一个. 有时,这个错误一般在运行时出现,报的错可能是,找不到某方法,或者呢,更隐蔽的,不会报错,但是逻辑不对. 针对运行中的应用,可以考虑使用阿里出品的arthas来处理. 我今天呢,只是简单的找不到方法的情况,所以不需要用到那个. 我这里的场景是,在学

曹工杂谈:为什么很少需要改Spring源码,因为扩展点太多了,说说Spring的后置处理器

前言 最近发了好几篇,都是覆盖框架源码,但是spring的代码,我是从没覆盖过,毕竟,如果方便扩展,没谁想去改源码,而spring就是不需要改源码的那个,真的是"对扩展开放,对修改关闭"的典范. 就我说曾经用过的,spring的扩展点,就包括了listener.beanFactoryPostProcessor.beanPostProcessor,而spring boot的扩展点,除了properties.yml.java config覆盖自动配置.org.springframework

曹工说Spring Boot源码(9)-- Spring解析xml文件,到底从中得到了什么(context命名空间上)

写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享 曹工说Spring Boot源码(2)-- Bean Definition到底是什么,咱们对着接口,逐个方法讲解 曹工说Spring Boot源码(3)-- 手动注册Bean Definition不比游戏好玩吗,我们来试一下 曹工说Spring Boot源码(4)-- 我是怎么自定义ApplicationContext,从json文件读取bean de

曹工说Spring Boot源码(15)-- Spring从xml文件里到底得到了什么(context:load-time-weaver 完整解析)

写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享 曹工说Spring Boot源码(2)-- Bean Definition到底是什么,咱们对着接口,逐个方法讲解 曹工说Spring Boot源码(3)-- 手动注册Bean Definition不比游戏好玩吗,我们来试一下 曹工说Spring Boot源码(4)-- 我是怎么自定义ApplicationContext,从json文件读取bean de

曹工说Spring Boot源码(19)-- Spring 带给我们的工具利器,创建代理不用愁(ProxyFactory)

写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享 曹工说Spring Boot源码(2)-- Bean Definition到底是什么,咱们对着接口,逐个方法讲解 曹工说Spring Boot源码(3)-- 手动注册Bean Definition不比游戏好玩吗,我们来试一下 曹工说Spring Boot源码(4)-- 我是怎么自定义ApplicationContext,从json文件读取bean de

Java类加载原理解析

1       基本信息 摘要: 每个java开发人员对java.lang.ClassNotFoundExcetpion这个异常肯定都不陌生,这背后就涉及到了java技术体系中的类加载.Java的类加载机制是java技术体系中比较核心的部分,虽然和大部分开发人员直接打交道不多,但是对其背后的机理有一定理解有助于排查程序中出现的类加载失败等技术问题,对理解java虚拟机的连接模型和java语言的动态性都有很大帮助. 由于关于java类加载的内容较多,所以打算分三篇文章简述一下: 第一篇:java类

Java类加载器( CLassLoader ) 死磕 3: 揭秘 ClassLoader抽象基类

[正文]Java类加载器(  CLassLoader ) 死磕3:  揭秘 ClassLoader抽象基类 3.1. 揭秘ClassLoader抽象基类 3.1.1. 类的加载分类:隐式加载和显示加载 java中类是动态加载的,jvm启动的时候,并不会一次性加载所有的class文件,而是根据需要去动态加载.一是加快启动的速度,二是节约内存.如果一次性加载全部jar包的所有class,速度会很慢. 动态载入一个class类,有两种方式: (1) implicit隐式加载 即通过实例化才载入的特性来

曹工说Spring Boot源码(4)-- 我是怎么自定义ApplicationContext,从json文件读取bean definition的?

写在前面的话 相关背景及资源: 曹工说Spring Boot源码系列开讲了(1)-- Bean Definition到底是什么,附spring思维导图分享 工程代码地址 思维导图地址 工程结构图: 大体思路 总体来说,bean definition是什么,我们前面几讲,说了个大概了:目前,我们将聚焦于怎么获取bean definition. 我们这次做个实验,就是将bean definition(一共两个bean,有依赖关系,依赖是手动指定的)定义在json文件内,然后自定义一个applicat

曹工说Spring Boot源码(6)-- Spring怎么从xml文件里解析bean的

写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享 曹工说Spring Boot源码(2)-- Bean Definition到底是什么,咱们对着接口,逐个方法讲解 曹工说Spring Boot源码(3)-- 手动注册Bean Definition不比游戏好玩吗,我们来试一下 曹工说Spring Boot源码(4)-- 我是怎么自定义ApplicationContext,从json文件读取bean de