java 类加载机制简析

作者: 蕪園樓主香獨秀 | 来源:发表于2016-08-21 00:47 被阅读718次

    java 类加载机制简析

    类的加载:指的是 JVM 将类的 .class 文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个这个类的 java.lang.Class 对象,用来封装类在方法区类的对象,加载的最终目标是堆中的 Class 对象

    Class 对象封装了类在方法区内的数据结构,并且向 Java 程序员提供了访问方法区内的数据结构的接口

    JVM 将类加载过程分为三个步骤:装载(Load)链接(Link)初始化(Initialize)

    classload.gif

    类装载(不同加载器的主要不同点)

    来源(.class文件来源):

    • 从本地系统直接加载.class
    • 网络下载.class
    • zip,jar 等归档文件中加载.class
    • 从专有数据库中提取.class
    • 将 Java 源文件动态编译为 .class 文件(服务器)

    类链接

    1. 验证:确保被加载类的正确性;

    2. 准备:为类的静态变量分配内存,并将其初始化为默认值;

    3. 解析:把类中的符号引用转换为直接引用;

    类初始化

    • WHEN:

      1. 创建类的实例,也就是 new 一个对象
      2. 访问某个类或接口的静态变量,或者对该静态变量赋值
      3. 调用类的静态方法
      4. 反射Class.forName()
      5. 初始化一个类的子类(会首先初始化子类的父类)
      6. JVM 启动时标明的启动类,即文件名和类名相同的那个类
    • HOW:

      1. 如果这个类还没有被装载和链接,那先进行装载和链接

      2. 假如这个类存在直接父类,并且这个类还没有被初始化(注意:在一个类加载器中,类只能初始化一次),那就初始化直接的父类(不适用于接口)

      3. 加入类中存在初始化语句(如static变量和static块),那就依次执行这些初始化语句

    load-init.PNG load-init order.PNG
     /**
     * 初始化伪代码
     */
     init (Class child) {
       if (child.hasParent()) {
         Class<?> parent = child.getParent();
         loadAndLink(parent);
         init(parent);
       }
       /**
       * 初始化静态成员代码。。。。
       */
       return;
     }
     
    
     ​
    

    类加载器

    JVM 的类加载是通过 ClassLoader 及其子类来完成

    • WHAT:实际上,各种类加载器其实主要对 .class 源进行操作,并将其转化成字节序列
    • WHY:在 JVM 中,一个类由其全限定名和一个加载类 ClassLoader 的实例作为唯一标识,不同类加载器加载的类将被置于不同的命名空间
    • HOW:组成了如下图所示的 ,以 ”双亲委托机制“为”桥梁“的 ClassLoader Architecture

    注意:

    BootstrapClassLoader: 由 c++ 编写而成,不是ClassLoader的子类

    ExtClassLoaderAppClassLoader 没有类继承关系,却通过 parent 属性使其构成父子类加载器的关系

    classloader.gif

    上图表示出 java 类加载机制中的 “双亲委托机制“

    双亲委托机制

    • WHAT:当前类加载器在调用 loadClass() 加载类时,主要进行下步骤:

      1. 检查类是否已被加载过,调用 findClassLoaded() 查看当前类加载器是否存在 class 的缓存
      2. 若类未被加载过,递归委托父类加载器调用 loadClass() 加载类,若无,则 findBootstrapClassOrNull() 完成类加载
      3. 若以上步骤都不能完成类加载,则调用 findClass() 尝试当前类加载器完成加载,若加载成功则缓存

      特点:

      1. 从子到父共享缓存
      2. 从父到子尝试自加载
    • WHY:双亲委托机制主要是为了 ”类加载器之间共享缓存“"类加载器的优先级问题",保证基础类的类加载器统一的问题

    • HOW:具体代码实现:

      /**
      * //:ClassLoader 类中的 loadClass() 源码
      * loadClass 是加载类的入口,也是双亲委托机制的实现
      * @param name 必须为二进制名,即类的全限定名
      * @param resolve 是否链接类,链接绑定类与加载器
      * @return 类加载完成后返回存储在堆中 Class
      * 方法 loadClass 并未被 final 修饰,代表可以被复写,从而破坏“双亲委托”
      */
      protected Class<?> loadClass(String name, boolean resolve)
             throws ClassNotFoundException
         {
             synchronized (getClassLoadingLock(name)) {
                 // 检查类是否已被加载过
                 Class<?> c = findLoadedClass(name);
                 if (c == null) {
                     long t0 = System.nanoTime();
                     try {
                         if (parent != null) {
                           //递归委托父类加载器加载类
                             c = parent.loadClass(name, false);
                         } else {
                           //无父类加载器,则调用启动类加载器加载
                             c = findBootstrapClassOrNull(name);
                         }
                     } catch (ClassNotFoundException e) {
                         // ClassNotFoundException thrown if class not found
                         // from the non-null parent class loader
                     }
        
                     if (c == null) {
                         long t1 = System.nanoTime();
                        //递归委托后仍然无法完成类加载,则使用当前类加载器加载
                         c = findClass(name);
        
                         // this is the defining class loader; record the stats
                         sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                         sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                         sun.misc.PerfCounter.getFindClasses().increment();
                     }
                 }
                 if (resolve) {
                     resolveClass(c);
                 }
                 return c;
             }
         }
      

    自定义类加载器(extends ClassLoader)的方式:

    • override loadClass() 破化双亲委托机制(不推荐)
    • override findClass() 兼容双亲委托机制,通过复写提供的 findClass() 来实现在双亲委托机制的基础上来自定义类加载器
      • 先内后外,加载类时,遵循先双亲委托,后自定义加载

    ClassLoader 的三个构造器:

        
        private final ClassLoader parent; //父类加载器,只在构造器中一次性指定,不提供public方法设置
        /**
        * ClassLoader 无参构造器
        * 内部调用带参构造器,使用getSystemClassLoader()作为parent形参的实参
        * getSystemClassLoader()返回的是sun.misc.Launcher$AppClassLoader
        */
        protected ClassLoader() {
              this(checkCreateClassLoader(), getSystemClassLoader());
          }
    
        /**
        * 带参构造器
        * @parent 用来指定父类加载器
        */
        protected ClassLoader(ClassLoader parent) {
            this(checkCreateClassLoader(), parent);
        }
    
        /**
        * 带参构造器
        */
        private ClassLoader(Void unused, ClassLoader parent) {
              this.parent = parent;  //设置父类加载器
              if (ParallelLoaders.isRegistered(this.getClass())) {
                  parallelLockMap = new ConcurrentHashMap<>();
                  package2certs = new ConcurrentHashMap<>();
                  domains =
                      Collections.synchronizedSet(new HashSet<ProtectionDomain>());
                  assertionLock = new Object();
              } else {
                  // no finer-grained lock; lock on the classloader instance
                  parallelLockMap = null;
                  package2certs = new Hashtable<>();
                  domains = new HashSet<>();
                  assertionLock = this;
              }
          }
    
    

    实现自定类加载器:

          /**
          * //: findClass 方法源码
          * @name 
          * 该方法只抛出一个 ClassNotFountException(name)
          * 表明此方法被设计来复写,作为自定义加载器类的接口
          */
        protected Class<?> findClass(String name) throws ClassNotFoundException {
              throw new ClassNotFoundException(name);
            // 自定义时,在此处添加代码,将 .class 源解析为 byte[] classData
            //  return defineClass(name, classData, 0, classData.length);  
          }
    
        /**
        * //: defindClass 方法源码
        * final 修饰,不能被子类复写,提供真正意义上 JVM 对类进行加载的接口
        * 将由.class 解析成的字节序列,存储于方法区,然后在堆中生成对应的Class对象
        */
        protected final Class<?> defineClass(String name, byte[] b, int off, int len)
            throws ClassFormatError
        {
            return defineClass(name, b, off, len, null);
        }
    
                            ___$$$$$$$_____________________________
    ________________________$$$$$$$$$$___________________________
    ________________________$$$$$$$$$$$__________________________
    _________________________$$$$$$$$$$$$$$______________________
    __________________________$$$$$$$$$$$________________________
    _____________________________$$$$$$$$$$$$$___________________
    ___________________________$$$$$$$$$$________________________
    _________________________$$$$$$$$$$$$$$$_____________________
    ________________$$$______$$$$$$$$$$$$$$______________________
    ______________$$$$$$$$_____$$$$$$__$$$$$_____________________
    _____________$$$$$$$$$$_____$$$$____$$$$$____________________
    ___________$$$$$$_$$$$$$$$__$$$$______$$$$___________________
    __________$$$$$_____$$$$$$$$_$$$$_______$$$__________________
    ________$$$$$_________$$$$$$$$$$$$_______$$$_________________
    _______$$$_____________$$$$$$$$$$$________$$$________________
    _____$$$________________$$$$$$$$$$________$$$$$$_____________
    __$$$$$$__________________$$$$$$$
    -------------------------------------------------------------
    

    相关文章

      网友评论

      本文标题:java 类加载机制简析

      本文链接:https://www.haomeiwen.com/subject/azwisttx.html