JavaSE 反射——类加载机制

一、类加载机制

  • 当我们用Java命令运行程序时,首先需要通过类加载器(ClassLoader)把类加载到JVM中
  • java.lang.ClassLoader:根据一个指定的类的名称,找到或者生成其对应的字节代码,然后从这些字节代码中定义出一个 Java 类,即 java.lang.Class 类的一个实例,java.lang.ClassLoader有两个核心方法,loadClass(String, boolean)和findClass,前一个实现了双亲委派机制,后一个完成查找和加载类,findClass默认实现是空方法

二、类加载过程

1. 加载

  • 在硬盘上查找并通过I/O读入字节码文件(.class文件),将类的字节码文件中的二进制数据读入内存中,将其放在运行时数据区域的方法区内,使用到类时才会加载,例如调用类的main()方法,new对象等等,在加载阶段会在内存(堆中)生成一个代表这个类的 java.lang.Class对象,作为在方法区这个类的各种数据的访问入口,另外主类在运行过程中如果使用到其它类,会逐步加载这些类,jar包或war包里的类不是一次性全部加载的,是使用到时才加载(Class类的构造方法源码中,构造方法是私有的,只有JVM才能创建这个类的对象

2. 验证

  • 校验字节码文件的正确性

3. 准备

  • 给类的静态变量分配内存,并赋予默认值,如int分配4个字节并赋值为0,long分配8个字节并赋值为0

4. 解析

  • 符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,符号引用的字面量形式明确定义在Java虚拟机规范的Class文件格式中,在编译时,Java类并不知道所引用的类的实际地址,因此只能使用符号引用来代替
  • 直接引用:
  1. 直接指向目标的指针
  2. 相对偏移量
  3. 一个能间接定位到目标的句柄
  • 将符号引用替换为直接引用,即JVM将常量池内的符号引用替换为直接引用的过程,解析主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用进行,如A类中的a方法引用了B类中的b方法,那么它会找到B类的b方法的内存地址,将符号引用替换为直接引用(内存地址),这是所谓的静态链接过程(类加载期间完成),动态链接是在程序运行期间完成的将符号引用替换为直接引用

5. 初始化

  • 对类的静态变量初始化为指定的值,执行静态代码块

5.1 类加载器初始化过程

  • 类运行加载中会创建JVM启动器实例sun.misc.Launcher,sun.misc.Launcher初始化使用了单例的设计模式,保证一个JVM虚拟机内只有一个sun.misc.Launcher实例,在Launcher构造方法内部,创建了两个类加载器,分别是sun.misc.Launcher.ExtClassLoader(扩展类加载器)和sun.misc.Launcher.AppClassLoader(应用类加载器),JVM默认使用Launcher的getClassLoader()方法返回的类加载器AppClassLoader的实例加载应用程序

5.2 类加载进内存后不一定会初始化,触发类加载器主动初始化方式

  1. 创建对象的实例,即new对象时会引发类的初始化(前提是这个类没有被初始化)
  2. 调用类的静态属性或者为静态属性赋值
  3. 调用类的静态方法
  4. 通过class文件反射创建对象
  5. 初始化一个类的子类:使用子类的时候先初始化父类
  6. JVM启动时被标记为启动类的类(即main方法所在的类)
  • 在同一个类加载器下面类只能初始化一次,已初始化过的无需初始化
  • 在编译的时候可以确定下的(编译常量),不会对类进行初始化
  • 在编译时无法确定下的(运行常量),对类进行初始化
  • 类初始化之前需要进行前4步(加载、验证、准备、解析)的操作
  • 如果这个类有父类并且这个父类没有被初始化,则先初始化父类
  • 如果类中存在初始化语句,则依次执行初始化语句

5.3 类初始化步骤

  • 静态代码块和静态属性是同级别加载,即按照代码的编写顺序执行

5.3.1 无父类

  1. 类的静态属性
  2. 类的静态代码块
  3. 类的非静态属性
  4. 类的非静态代码块
  5. 构造方法

5.3.2 有父类

  1. 父类的静态属性
  2. 父类的静态代码块
  3. 子类的静态属性
  4. 子类的静态代码块
  5. 父类的非静态属性
  6. 父类的非静态代码块
  7. 父类构造方法
  8. 子类非静态属性
  9. 子类非静态代码块
  10. 子类构造方法

三、类加载器

  • 类加载过程主要是通过类加载器来实现的,Java中有几种类加载器,每个类加载器在创建时已经指定对应的目录, 即每个类加载器去哪里加载类是确定的,另外类加载器之间不是继承关系,而是一种委托关系
  • 在JVM虚拟机的角度上,分为两种不同的类加载器,启动类加载器与其它所有的类加载器。启动类加载器使用C++实现,是JVM自身的一部分;其它所有的类加载器由Java语言实现,独立于虚拟机外部,并且全部继承自java.lang.ClassLoader
  • 在开发者的角度上,类加载器分为如下几类

1. 引导类加载器

  • Bootstrap ClassLoader即引导类加载器,负责加载支撑JVM运行的位于JRE的lib目录下的核心类库(jre\lib\rt.jar),比如rt.jar、charsets.jar等

2. 扩展类加载器

  • Extension ClassLoader即扩展类加载器,负责加载支撑JVM运行的位于JRE的lib目录下的ext扩展目录中的jar类包(jre\lib\ext*.jar)

3. 应用程序类加载器

  • Application ClassLoader即应用程序类加载器,负责加载指定ClassPath路径下的类包,也就是加载自己写的类

4. 自定义加载器

  • User ClassLoader即自定义加载器,负责加载用户自定义路径下的类包
  • 实现自定义类加载器的方式:继承 java.lang.ClassLoader类,重写findClass() 方法

四、双亲委派机制

  • 加载某个类时会先委托父加载器寻找目标类,找不到再委托上层父加载器加载,如果所有父加载器在自己的加载类路径下都找不到目标类,则在自己的类加载路径中查找并载入目标类

1. 设计双亲委派机制的原因

  1. 沙箱安全机制:如自己写的java.lang.String.class类不会被加载,这样便可以防止核心API库被随意篡改;
  2. 避免类的重复加载:当父亲已经加载了该类时,子类ClassLoader没必要再加载一次,保证被加载类的唯一性

2. 解析ClassLoader的loadClass方法中的双亲委派机制

  1. 首先检查指定名称的类是否已经加载过,若加载过,则不需要加载,直接返回
  2. 若没有加载过,判断父加载器,如果当前加载器父加载器不为空则委托父加载器加载该类,如果当前加载器父加载器为空则委托引导类加载器加载该类
  3. 若父加载器和引导类加载器都没有找到指定类,那么调用URLClassLoader即当前类加载器的findClass方法在加载器的类路径里查找并加载该类
protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
    
    
        synchronized (getClassLoadingLock(name)) {
    
    
            // First, check if the class has already been loaded
            // 检查指定名称的类是否已经加载过
            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) {
    
    
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    // 都会调用URLClassLoader即当前类加载器的findClass方法在加载器的类路径里查找并加载该类
                    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;
        }
    }

猜你喜欢

转载自blog.csdn.net/LvJzzZ/article/details/108520132