zoukankan      html  css  js  c++  java
  • ClassLoader 机制

    JAVA启动后,是经过JVM各级ClassLoader来加载各个类到内存。为了更加了解加载过程,我通过分析和写了一个简单的ClassLoader来粗浅的分析它的原理。

    JVM的ClassLoader分三层,分别为Bootstrap ClassLoader,Extension ClassLoader,System ClassLoader,他们不是类继承的父子关系,是逻辑上的上下级关系。

    Bootstrap ClassLoader是启动类加载器,它是用C++编写的,从%jre%/lib目录中加载类,或者运行时用-Xbootclasspath指定目录来加载。

    Extension ClassLoader是扩展类加载器,从%jre%/lib/ext目录加载类,或者运行时用-Djava.ext.dirs制定目录来加载。

    System ClassLoader,系统类加载器,它会从系统环境变量配置的classpath来查找路径,环境变量里的.表示当前目录,是通过运行时-classpath或-Djava.class.path指定的目录来加载类。

    一般自定义的Class Loader可以从java.lang.ClassLoader继承,不同classloader加载相同的类,他们在内存也不是相等的,即它们不能互相转换,会直接抛异常。java.lang.ClassLoader的核心加载方法是loadClass方法,如:

    protected synchronized Class<?> loadClass(String name, boolean resolve)
    
             throws ClassNotFoundException
    
        {
    
             // First, check if the class has already been loaded
    
             Class c = findLoadedClass(name);
    
             if (c == null) {
    
                 try {
    
                       if (parent != null) {
    
                           c = parent.loadClass(name, false);
    
                       } else {
    
                           c = findBootstrapClass0(name);
    
                       }
    
                 } catch (ClassNotFoundException e) {
    
                     // If still not found, then invoke findClass in order
    
                     // to find the class.
    
                     c = findClass(name);
    
                 }
    
             }
    
             if (resolve) {
    
                 resolveClass(c);
    
             }
    
             return c;
    
        }
    

      通过上面加载过程,我们能知道JVM默认是双亲委托加载机制,即首先判断缓存是否有已加载的类,如果缓存没有,但存在父加载器,则让父加载器加载,如果不存在父加载器,则让Bootstrap ClassLoader去加载,如果父类加载失败,则调用本地的findClass方法去加载。

          双亲委托机制的作用是防止系统jar包被本地替换,因为查找方法过程都是从最底层开始查找。 因此,一般我们自定义的classloader都需要采用这种机制,我们只需要继承java.lang.ClassLoader实现findclass即可,如果需要更多控制,自定义的classloader就需要重写loadClass方法了,比如tomcat的加载过程,这个比较复杂,可以通过其他文档资料查看相关介绍。

          各个ClassLoader加载相同的类后,他们是不互等的,这个当涉及多个ClassLoader,并且有通过当前线程上线文获取ClassLoader后转换特别需要注意,可以通过线程的setContextClassLoader设置一个ClassLoader线程上下文,然后再通过Thread.currentThread().getContextClassLoader()获取当前线程保存的Classloader。但是自定义的类文件,放到Bootstrap ClassLoader加载目录,是不会被Bootstrap ClassLoader加载的,因为作为启动类加载器,它不会加载自己不熟悉的jar包的,并且类文件必须打包成jar包放到加载器加载的根目录,才可能被扩展类加载器所加载。

    Bootstrap ClassLoader、Extension ClassLoader、App ClassLoader三者的关系如下:

    Bootstrap ClassLoader是Extension ClassLoader的parent,Extension ClassLoader是App ClassLoader的parent。

    但是这并不是继承关系,只是语义上的定义,基本上,每一个ClassLoader实现,都有一个Parent ClassLoader。

    可以通过ClassLoader的getParent方法得到当前ClassLoader的parent。Bootstrap ClassLoader比较特殊,因为它不是java class所以Extension ClassLoader的getParent方法返回的是NULL。

    由于一些特殊的需求,我们可能需要定制ClassLoader的加载行为,这时候就需要自定义ClassLoader了.

    自定义ClassLoader需要继承ClassLoader抽象类,重写findClass方法,这个方法定义了ClassLoader查找class的方式。

    主要可以扩展的方法有:

    findClass          定义查找Class的方式

    defineClass       将类文件字节码加载为jvm中的class

    findResource    定义查找资源的方式

    现已有的ClassLoader实现有如下几种:

    • java.net.URLClassLoader
    • java.security.SecureClassLoader
    • java.rmi.server.RMIClassLoader
    • sun.applet.AppletClassLoader

    Extension ClassLoader 和 App ClassLoader都是java.net.URLClassLoader的子类。

    这个是URLClassLoader的构造方法:

    public URLClassLoader(URL[] urls, ClassLoader parent)
    
    public URLClassLoader(URL[] urls)
    

    urls参数是需要加载的ClassPath url数组,可以指定parent ClassLoader,不指定的话默认以当前调用类的ClassLoader为parent。

    由于classloader 加载类用的是全盘负责委托机制。所谓全盘负责,即是当一个classloader加载一个Class的时候,这个Class所依赖的和引用的所有 Class也由这个classloader负责载入,除非是显式的使用另外一个classloader载入。

    所以,当我们自定义的classloader加载成功了com.company.MyClass以后,MyClass里所有依赖的class都由这个classLoader来加载完成。


    Class.forName() 与 load.loadClass()的区别:

    Class.forName("xx.xx")等同于Class.forName("xx.xx",true,CALLClass.class.getClassLoader()),第二个参数(bool)表示装载类的时候是否初始化该类,即调用类的静态块的语句及初始化静态成员变量。

    ClassLoader loader = Thread.currentThread.getContextClassLoader(); //也可以用(ClassLoader.getSystemClassLoader())

    Class cls = loader.loadClass("xx.xx"); //这句话没有执行初始化,其实与Class.forName("xx.xx",false,loader)是一致的,只是loader.loadClass("xx.xx")执行的是更底层的操作。

    只有执行cls.NewInstance()才能够初始化类,得到该类的一个实例

      Class的装载分了三个阶段,loading,linking和initializing,分别定义在The Java Language Specification的12.2,12.3和12.4。
    Class.forName(className) 实际上是调用Class.forName(className, true, this.getClass().getClassLoader())。注意第二个参数,是指Class被loading后是不是必须被初始化。
    ClassLoader.loadClass(className)实际上调用的是ClassLoader.loadClass(name, false),第二个参数指出Class是否被link。
    区别就出来了。Class.forName(className)装载的class已经被初始化,而ClassLoader.loadClass(className)装载的class还没有被link。
    forName支持数组类型,loadClass不支持数组

      一般情况下,这两个方法效果一样,都能装载Class。但如果程序依赖于Class是否被初始化,就必须用Class.forName(name)了。
    例如,在JDBC编程中,常看到这样的用法,Class.forName("com.mysql.jdbc.Driver"),如果换成了 getClass().getClassLoader().loadClass("com.mysql.jdbc.Driver"),就不行。
    为什么呢?打开com.mysql.jdbc.Driver的源代码看看,

    //
    // Register ourselves with the DriverManager
    //
    static {
      try {
        java.sql.DriverManager.registerDriver(new Driver());
      } catch (SQLException E) {
        throw new RuntimeException("Can't register driver!");
      }
    }

      原来,Driver在static块中会注册自己到java.sql.DriverManager。而static块就是在Class的初始化中被执行。所以这个地方就只能用Class.forName(className)。

  • 相关阅读:
    程序修炼之道——从小工到专家(3)
    组合
    子类重用父类的功能
    对象之间的交互
    属性查找与绑定方法
    类与对象的定义与使用
    hashlib模块subprocess模块
    configerparser模块
    shelve模块 xml模块
    sys模块 json pickle模块
  • 原文地址:https://www.cnblogs.com/mywy/p/5150680.html
Copyright © 2011-2022 走看看