Do You Really Know ClassLoader?

主要是对ClassLoader相关的知识进行一个总结,讨论和解决以下问题:

  • Class和ClassLoader是什么关系,Class.forName 和ClassLoader.loadClass的联系与区别?
  • new的过程是什么,在jvm中如何执行,new和classLoader有什么关系,被new的对象的classLoader是什么,为什么new如此重要?
  • 什么是类型隔离,为什么要类型隔离,如何隔离,和ClassLoader有什么关系?

这些问题你确定能很深入和很体系化的回答吗,而不是只大致的知道一个表面,如果不能,那就一起来看看吧!

1、Class.forName & ClassLoader.loadClass


Class.forName(String anme)和ClassLoader.loadClass(String name)都可以用来进行类型加载,而在java进行类型加载时,一般会有多个ClassLoader可以使用,并可以使用多种方式进行类型加载。

class A {
      public void m() {
           A.class.getClassLoader().loadClass(“B”);
      }
 }

在A.class.getClassLoader().loadClass(“B”);代码执行B的加载过程时,一般会有三个概念上的ClassLoader提供使用:

  • CurrentClassLoader,称之为当前类加载器,简称CCL,在代码中对应的就是类型A的类加载器;
  • SpecificClassLoader,称之为指定类加载器,简称SCL,在代码中对应的是 A.class.getClassLoader(),如果使用任意的ClassLoader进行加载,这个ClassLoader都可以称之为SCL;
  • ThreadContextClassLoader,称之为线程上下文类加载器,简称TCCL,每个线程都会拥有一个ClassLoader引用,而且可以通过Thread.currentThread().setContextClassLoader(ClassLoader classLoader)进行切换。

1.1 Class.forName与SystemDictionary

Class.forName(static方法)是根据给定的类型全名从CCL中加载指定的类型。

public static Class forName(String className)
            throws ClassNotFoundException {
    Class caller = Reflection.getCallerClass();
    return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}

Reflection.getCallerClass(),获取到调用Class.forName方法的类,隐含意义就是CCL。
具体的加载的逻辑在native方法forName0中定义。

Class.c中对应实现的逻辑:

JNIEXPORT jclass JNICALL
Java_java_lang_Class_forName0(JNIEnv *env, jclass this, jstring classname,
                          jboolean initialize, jobject loader)
{
    // 略
    cls = JVM_FindClassFromClassLoader(env, clname, initialize,
                                   loader, JNI_FALSE);
    done:
    if (clname != buf) {
        free(clname);
    }
    return cls;
}

实现细节在JVM_FindClassFromClassLoader中定义,可以看到调用Class.forName会使用JVM_FindClassFromClassLoader这个函数来进行类型加载,我们需要注意的时clname和loader这两个变量,一个是类的全限定名,另一个是ClassLoader,而Class.forName所使用的ClassLoader是CCL。

在jvm.cpp中FindClassFromClassLoader的对应实现是:

jclass find_class_from_class_loader(JNIEnv* env, Symbol* name, jboolean init, Handle loader, Handle protection_domain, jboolean throwError, TRAPS) {
    // Security Note:
    // The Java level wrapper will perform the necessary security check allowing
    // us to pass the NULL as the initiating class loader.
    klassOop klass = SystemDictionary::resolve_or_fail(name, loader, protection_domain, throwError != 0, CHECK_NULL);
    // 略
}

SystemDictionary,系统字典,这个数据结构是保存Java加载类型的数据结构,以类的全限定名再加上类加载器作为key,进而确定Class引用。类型加载时,以ClassLoader和需要加载的类型全限定名作为参数在SystemDictionary中进行查询,如果能够查询到则返回。如果无法找到,则调用loader.loadClass(className)进行加载,这一步将进入到Java代码中。

Do You Really Know ClassLoader?_第1张图片
SystemDictionary

klassOop SystemDictionary::resolve_or_fail(Symbol* class_name, Handle class_loader, Handle protection_domain, bool throw_error, TRAPS) {
    klassOop klass = resolve_or_null(class_name, class_loader, protection_domain, THREAD);
    if (HAS_PENDING_EXCEPTION || klass == NULL) {
        KlassHandle k_h(THREAD, klass);
        // can return a null klass
        klass = handle_resolution_exception(class_name, class_loader, protection_domain, throw_error, k_h, THREAD);
    }
    return klass;
}   

resolve_or_null在SystemDictionary.c中会调用到resolve_ instance_ class_ or_ null,其中关键的地方是,如果class还没有被加载,则会先进行“load_ instance_class”

klassOop SystemDictionary::resolve_instance_class_or_null(Symbol* name, Handle class_loader, Handle protection_domain, TRAPS) {
    class_loader = Handle(THREAD, java_lang_ClassLoader::non_reflection_class_loader(class_loader()));

    //......

    if (!class_has_been_loaded) {
        k = load_instance_class(name, class_loader, THREAD);  
    }

    // Verify protection domain. If it fails an exception is thrown
       validate_protection_domain(k, class_loader, protection_domain, CHECK_(klassOop(NULL)));

    return k();
}

而load_instance_class进一步调用java代码(JavaCalls::call_virtual),classLoader.loadClass(String name),classLoader.loadClass则依据双亲委派原则进行类型的加载。

instanceKlassHandle SystemDictionary::load_instance_class(Symbol* class_name, Handle class_loader, TRAPS) {
    instanceKlassHandle nh = instanceKlassHandle(); // null Handle

    ResourceMark rm(THREAD);

    JavaThread* jt = (JavaThread*) THREAD;

    PerfClassTraceTime vmtimer(ClassLoader::perf_app_classload_time(),
                           ClassLoader::perf_app_classload_selftime(),
                           ClassLoader::perf_app_classload_count(),
                           jt->get_thread_stat()->perf_recursion_counts_addr(),
                           jt->get_thread_stat()->perf_timers_addr(),
                           PerfClassTraceTime::CLASS_LOAD);

    Handle s = java_lang_String::create_from_symbol(class_name, CHECK_(nh));
    // Translate to external class name format, i.e., convert '/' chars to '.'
    Handle string = java_lang_String::externalize_classname(s, CHECK_(nh));

    JavaValue result(T_OBJECT);

    KlassHandle spec_klass (THREAD, SystemDictionary::ClassLoader_klass());

    if (MustCallLoadClassInternal && has_loadClassInternal()) {
    JavaCalls::call_special(&result,
                          class_loader,
                          spec_klass,
                          vmSymbols::loadClassInternal_name(),
                          vmSymbols::string_class_signature(),
                          string,
                          CHECK_(nh));
    } else {
    JavaCalls::call_virtual(&result,
                          class_loader,
                          spec_klass,
                          vmSymbols::loadClass_name(),
                          vmSymbols::string_class_signature(),
                          string,
                          CHECK_(nh));
    }

    oop obj = (oop) result.get_jobject();

    if ((obj != NULL) && !(java_lang_Class::is_primitive(obj))) {
    instanceKlassHandle k = instanceKlassHandle(THREAD, java_lang_Class::as_klassOop(obj));

    if (class_name == k->name()) {
        return k;
    }
    }
    
    // Class is not found or has the wrong name, return NULL
    return nh;
    }
}

整个class.forName(String name)加载过程,以及与classLoader.loadClass(String name)的交互关系如下图:

Do You Really Know ClassLoader?_第2张图片

对以上过程做一个简要的梳理与总结:

步骤 说明
1 调用Class.forName(className)方法,该方法会调用native的JVM实现,调用前该方法会确定准备好需要加载的类名以及ClassLoader,将其传递给native方法
2 进入到JVM实现后,首先会在SystemDictionary中根据类名和ClassLoader组成hash,进行查询,如果能够命中,则返回
3 如果加载到则返回
4 如果在SystemDictionary中无法命中,将会调用Java代码:ClassLoader.loadClass(类名),这一步将委派给Java代码,让传递的ClassLoader进行类型加载
5 以URLClassLoader为例,ClassLoader确定了类文件的字节流,但是该字节流如何按照规范生成Class对象,这个过程在Java代码中是没有体现的,其实也就是要求调用ClassLoader.defineClass(byte[])进行解析类型,该方法将会再次调用native方法,因为字节流对应Class对象的规范是定义在JVM实现中的
6 进入JVM实现,调用SystemDictionary的resolve_stream方法,接受byte[],使用ClassFileParser进行解析
7 SystemDictionary::define_instance_class
8 如果类型被加载了,将类名、ClassLoader和类型的实例引用添加到SystemDictionary中
9 返回
10 返回
11 从Java实现返回到Java代码的defineClass,返回Class对象
12 返回给loadClass(Classname)方法
13 返回给Java实现的SystemDictionary,因为在resolve_class中调用的ClassLoader.loadClass。这里会做出一个判断,如果加载Class的ClassLoader并非传递给resolve_class的ClassLoader,那么会将类名、传递给resolve_class的ClassLoader以及类型的实例引用添加到SystemDictionary中
14 返回给Class.forName类型实例

1.2 ClassLoader.loadClass

分析了Class.forName之后,再看ClassLoader.loadClass()就会变得简单很多,这个ClassLoader就是一个SCL,而ClassLoader.loadClass()只是相当于一个简单的方法调用。

2、new & class.newInstance & Constructor


2.1 new & newInstance & Constructor 的比较

类的加载方式不同

在使用class.newInstance()方法的时候,必须保证这个类已经加载并且已经连接了,而这可以通过Class的静态方法forName()来完成的。

使用关键字new创建一个类的时候,这个类可以没有被加载,一般也不需要该类在classpath中设定,但可能需要通过classlaoder来加载。

所调用的构造方法不尽相同

  • new关键字能调用任何构造方法。
  • class.newInstance()只能调用无参构造方法。

执行效率不同

  • new关键字是强类型的,效率相对较高。
  • class.newInstance()是弱类型的,效率相对较低。

综上分析可以得出:
new A() = Class.forName("A").newInstance();

class.newInstance() & Constructor.newInstance(...)

  • Class.newInstance() 只能够调用无参的构造函数,即默认的构造函数。
  • Class.newInstance() 抛出所有由被调用构造函数抛出的异常。
  • Class.newInstance() 要求被调用的构造函数是可见的,也即必须是public类型的。
  • Constructor.newInstance() 在特定的情况下,可以调用私有的构造函数。
  • Constructor.newInstance() 可以根据传入的参数,调用任意构造构造函数。

2.2 new的jvm实现过程

new关键字在jvm中被映射为_new指令,具体实现逻辑在bytecodeInterpreter.cpp中。

 CASE(_new): {
    u2 index = Bytes::get_Java_u2(pc+1);
    constantPoolOop constants = istate->method()->constants();
    if (!constants->tag_at(index).is_unresolved_klass()) {
      // Make sure klass is initialized and doesn't have a finalizer
      oop entry = constants->slot_at(index).get_oop();
      klassOop k_entry = (klassOop) entry;
      instanceKlass* ik = (instanceKlass*) k_entry->klass_part();
      if ( ik->is_initialized() && ik->can_be_fastpath_allocated() ) {
        size_t obj_size = ik->size_helper();
        oop result = NULL;
        // If the TLAB isn't pre-zeroed then we'll have to do it
        bool need_zero = !ZeroTLAB;
        if (UseTLAB) {
          result = (oop) THREAD->tlab().allocate(obj_size);
        }
        if (result == NULL) {
          need_zero = true;
          // Try allocate in shared eden
    retry:
          HeapWord* compare_to = *Universe::heap()->top_addr();
          HeapWord* new_top = compare_to + obj_size;
          if (new_top <= *Universe::heap()->end_addr()) {
            if (Atomic::cmpxchg_ptr(new_top, Universe::heap()->top_addr(), compare_to) != compare_to) {
              goto retry;
            }
            result = (oop) compare_to;
          }
        }
        if (result != NULL) {
          // Initialize object (if nonzero size and need) and then the header
          if (need_zero ) {
            HeapWord* to_zero = (HeapWord*) result + sizeof(oopDesc) / oopSize;
            obj_size -= sizeof(oopDesc) / oopSize;
            if (obj_size > 0 ) {
              memset(to_zero, 0, obj_size * HeapWordSize);
            }
          }
          if (UseBiasedLocking) {
            result->set_mark(ik->prototype_header());
          } else {
            result->set_mark(markOopDesc::prototype());
          }
          result->set_klass_gap(0);
          result->set_klass(k_entry);
          SET_STACK_OBJECT(result, 0);
          UPDATE_PC_AND_TOS_AND_CONTINUE(3, 1);
        }
      }
    }
    // Slow case allocation
    CALL_VM(InterpreterRuntime::_new(THREAD, METHOD->constants(), index),
            handle_exception);
    SET_STACK_OBJECT(THREAD->vm_result(), 0);
    THREAD->set_vm_result(NULL);
    UPDATE_PC_AND_TOS_AND_CONTINUE(3, 1);
  }

_new的逻辑主要分为两大块:

  • fast case allocation,要创建对象的类已经加载
  • slow case allocation,要创建对象的类未加载

slow case allocation

slow case allocation 会调用 InterpreterRuntime::_new 方法,调用的时候传入了三个参数:

  • THREAD,线程
  • METHOD->constants,方法对应的常量池
  • index,要创建的类在常量池中的索引

InterpreterRuntime::_new

// interpreterRuntime.cpp
IRT_ENTRY(void, InterpreterRuntime::_new(JavaThread* thread, ConstantPool* pool, int index))
    Klass* k_oop = pool->klass_at(index, CHECK);
    instanceKlassHandle klass (THREAD, k_oop);

    // Make sure we are not instantiating an abstract klass
    klass->check_valid_for_instantiation(true, CHECK);

    // Make sure klass is initialized
    klass->initialize(CHECK);

    oop obj = klass->allocate_instance(CHECK);
    thread->set_vm_result(obj);
IRT_END

加载Klass的代码在pool->klass_at函数里面:

klassOop constantPoolOopDesc::klass_at_impl(constantPoolHandle this_oop, int which, TRAPS) {
    CPSlot entry = this_oop->slot_at(which);
    if (entry.is_oop()) {
        return (klassOop)entry.get_oop();
    }

    //......

    if (do_resolve) {
        Handle loader = Handle(THREAD, instanceKlass::cast(this_oop->pool_holder())->class_loader());
        // this_oop must be unlocked during resolve_or_fail
        oop protection_domain = Klass::cast(this_oop->pool_holder())->protection_domain();
    
        Handle h_prot (THREAD, protection_domain);
        klassOop k_oop = SystemDictionary::resolve_or_fail(name, loader, h_prot, true, THREAD);
        KlassHandle k = KlassHandle(THREAD, k_oop);

        return k();    
    }

    //......
}

最终会调用klass_at_impl,从SystemDictionary中加载对应的klass(SystemDictionary::resolve_or_fail)。注意,SystemDictionary::resolve_or_fail所使用classLoader为instanceKlass::cast(this_oop->pool_holder())->class_loader()),this_oop->pool_holder()即当前对象,所以在使用new的时候,new对象的classLoader和CCL有关。

那么被new对象的classLoader是什么?是CCL?也不一定,只能说new的过程会调用到CCL(ClassLoader.loadClass(...))。但是在CCL的loadClass中具体怎么加载一个对象,具体是那个ClassLoader加载的还不确定,比如pandora中的mouduleClassLoader.loadClass(...)则需要看被加载的对象具体是什么来选择不同的classLoader来进行隔离加载。

总结:使用new初始化一个对象时,如果被初始化的对象类型还没有加载则,需要先进行加载,而在加载类型的过程中会走到SystemDictionary::resolve_or_fail,根据前面的分析SystemDictionary::resolve_or_fail会调用到java方法classLoader::loadClass(String name),这里的classLoader是CCL,但是具体类型怎么load还是要看CCL::loadClass的具体实现。

最后为什么new的过程如此重要,因为一般加载对象的不会全部使用classLoader,而new是一个非常常用的手段。深入的了解new的过程可以加深对java的ClassLoader和SystemDictionary体系的认识,同时也和下面内容有关系,java中为什么要进行类型隔离,如果进行类型隔离,类型隔离和classLoader有什么关系,如果使用classLoader进行隔离那么new出来的对象是否也进行了隔离。

3、类型隔离


3.1 隔离背景&原因

在大型分布式应用中,一般都会涉及到多个中间件应用,比如:PRC中间件、消息中间件、配置管理、数据库等,而众多中间件依赖的jar可能有冲突,比如RPC依赖log4j:1.1,而消息中间件依赖log4j:2.1,但是maven的jar管理机制只会加载其中的一个版本,比如加载了log4j:1.1,那么消息中间件就有可能出现不可预计的问题。随着系统的复杂,jar的管理与加载问题就如同潘多拉一样变得不可控制,如果不做控制和隔离,一个中间件的升级就可能影响到其他的中间件。因此迫切需要解决以下问题:

  • 中间件与中间件之间依赖的jar包之间的冲突;
  • 中间件与应用系统之间依赖的jar包之间的冲突;
  • 中间件的升级管理

3.2 pandora隔离机制的实现

pandora的总体结构如下图:


Do You Really Know ClassLoader?_第3张图片

隔离机制

使用不同的classLoader来实现隔离,pandora中涉及到的ModuleClassLoader中相关的ClassLoader

  • moduleClassLoader:pandora插件类,用于加载插件的导出类和内部类
  • extClassLoader:java核心ClassLoder用于加载java核心类
  • pandoraClassLoader:pandora的ClassLoader用于加载pandora的容器的类(不是插件类)
  • systemClassLoader:系统ClassLoader用于加载系统路径下的类
  • bizClassLoader:用于加载pandora的导入类

pandora内部的加载体系如下图:

Do You Really Know ClassLoader?_第4张图片

隔离级别

pandora中需要考虑哪些类需要被隔离和共用

  • pandora容器类、插件导出类和应用容器(spring)隔离
  • pandora容器的类与插件的类隔离
  • pandora内部插件之间需要隔离,导出类、导入类、非导出类(包内部类)、内部共用类
  • pandora内部插件的类与系统类隔离,插件自己的类直接不需要隔离

隔离实现

pandora插件导出类与应用容器的隔离

pandora的每个插件都有自己专属的moduleClassLoader,其导出类也是使用moduleClassLoader加载,应用容器(spring)的加载则是有webAppClassLoader来完成,这样就可以实现pandora插件导出类与应用容器的隔离。

PandoraContainer::getExportedClasses()会获取所有pandora插件的所有导出类,被输出到AliTomcat的MapRepositoryImpl中。

    public Map> getExportedClasses() {
        SharedClassService sharedClassService = serviceContainer.getService(SharedClassService.class);
        Map> sharedClassMap = sharedClassService.getSharedClassMap();
        if (sharedClassMap == null || sharedClassMap.isEmpty()) {
            System.err.println("Pandora export empty classes! please check pandora config.");
        }
        return sharedClassMap;
    }

AliTomcat中继承了webAppClassLoaderBase,实现了AliWebappClassLoaderBase,其findClass和loadClass被重写,在加载类的过程中,先从pandora的导出类中查找和加载,如果能加载到则使用pandora的导出类(webAppClassLoader所加载),如果查找和加载不到则继续交由webAppClassLoaderBase,这样就实现pandora插件导出类与应用容器的隔离。

pandora的类加载和隔离方式实际上打破的classLoader的双亲委派原则。

整体的classLoader加载体系如下图:

Do You Really Know ClassLoader?_第5张图片
pandora插件与pandora容器的隔离

ModuleClassLoader::loadClassInternal中会根据不同的加载类类型使用不同的classLoader,从而进行类型的隔离。

    private Class loadClassInternal(String name, boolean resolve) throws PandoraLoaderException {
    if (StringUtils.isEmpty(name)) {throw new PandoraLoaderException("class name is blank.");}

    // 1. 已经加载的类
    Class clazz = resolveLoaded(name);
    if (clazz != null) {return clazz;}

    // 2. 加载JDK相关的类
    // 隔离pandora插件类与java核心类 - extClassLoader
    clazz = resolveBootstrap(name);
    if (clazz != null) {return clazz;}

    // 3. com.taobao.pandora开头的选择从PandoraClassLoader中加载
    // pandora的类与插件类隔离 - pandoraClassLoader
    clazz = resolvePandoraClass(name);
    if (clazz != null) {return clazz;}

    // 4. 从共享缓存加载
    // pandora插件之间的共享类隔离 - moduleClassLoader
    clazz = resolveShared(name);
    if (clazz != null) {return clazz;}

    // 5. 根据import语义从bizClassLoader中加载
    // pandora插件类与导入类隔离 - bizClassLoader
    clazz = resolveImport(name);
    if (clazz != null) {return clazz;}

    // 6. 从当前classpath下加载
    // 插件内部的类不隔离,直接使用同一个ModuleClassLoader
    clazz = resolveClassPath(name);
    if (clazz != null) {return clazz;}

    // 7. 从bizClassLoader中加载,如果有bizClassloader,或者说usebizclassloader设置成为了true
    clazz = resolveExternal(name);
    if (clazz != null) {return clazz;}

    // 8. 从SystemClassLoader下加载,解决agent加载的问题
    // pandora插件类与系统类隔离 - systemClassLoader
    clazz = resolveSystemClassLoader(name);
    if (clazz != null) {
        if (resolve) {resolveClass(clazz);}
        return clazz;
    } else {
        throw new PandoraLoaderException("[Module-Loader] " + moduleName + ": can not load class {" + name + "} after all phase.");
    }
}
加载策略

为了保证JDK中类加载的安全性、模块之间的隔离性,以上8种类加载方式按照如下的顺序进行尝试:

  1. 首先,要保证JDK类加载的安全性,因此1(resolveLoaded)和2(resolveBootstrap)排在最前面;
  2. 其次,需要保证Pandora容器类加载的正确性,因此5(resolvePandoraClass)排在第3;
  3. 对于模块而言,保证其声明的语义是最重要的,因此3(resolveImport)排在第4;
  4. 然后,需要实现模块间的共享,因此4(resolveShared)排在第5;
  5. 在共享语义均完成的情况下,加载自己的,也就是7(resolveClassPath)排在第6;
  6. 如果步骤均失败,那么尝试从外部加载,也就是8(resolveExternal)排在第7;
  7. 最后,为了解决Agent问题,尝试从SystemClassLoader中加载,也就是6(resolveSystemClassLoader)。

你可能感兴趣的:(Do You Really Know ClassLoader?)