1. 类加载机制的层次结构

Java类加载的过程和双亲委派机制

  1. 加载:加载阶段会在内存中生成一个代表该类的Class对象,作为访问方法区该类各种数据的入口。加载阶段,虚拟机完成以下工作:
    • 通过一个类的全限定名来获取其定义的二进制字节流。
    • 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
    • 在Java堆中生成一个代表这个类的java.lang.Class对象,作为对方法区中这些数据的访问入口。

注意:虚拟机规范并没有指明二进制字节流要从一个Class文件获取,也可以从jar包或war包中读取,或从网络中获取(最典型的应用就是Applet),也可以在运行时生成(动态代理),也可以由其它文件生成(比如将JSP文件转换成对应的Class类)。

  1. 验证:验证阶段目的在于确保Class文件的字节流中包含信息符合当前虚拟机要求,并且不会危害虚拟机自身安全。主要包括四种验证:文件格式验证,元数据验证,字节码验证,符号引用验证。

  2. 准备:准备阶段是正式为类变量分配内存并设置类变量的初始值,即在方法区中分配这些变量所使用的内存空间。

  3. 解析:解析阶段是指虚拟机将常量池中的符号引用替换为直接引用的过程。

    • 符号引用:在Java中,在编译时,java类并不知道所引用的类的实际地址,因此只能使用符号引用来代替。
    • 直接引用:直接指向目标的指针(比如指向Class对象、类变量、类方法的直接引用);相对偏移量(比如,指向实例变量、实例方法的直接引用都是偏移量);一个能间接定位到目标的句柄。
  4. 初始化:初始化阶段是执行类构造器方法的过程。方法是由编译器自动收集类中的类变量的赋值操作和静态语句块中的语句。虚拟机会保证方法执行之前,父类的方法已经执行完毕。

注意以下几种情况不会执行类初始化:

  • 通过子类引用父类的静态字段,只会触发父类的初始化,而不会触发子类的初始化。
  • 定义对象数组,不会触发该类的初始化。
  • 常量在编译期间会存入调用类的常量池中,本质上并没有直接引用定义常量的类,不会触发定义常量所在的类。
  • 通过类名获取Class对象,不会触发类的初始化。
  • 通过Class.forName加载指定类时,如果指定参数initialize为false时,也不会触发类初始化,其实这个参数是告诉虚拟机,是否要对类进行初始化。
  • 通过ClassLoader默认的loadClass方法,也不会触发初始化动作。

2. 类加载器
虚拟机设计团队把加载动作放到JVM外部实现,以便让应用程序决定如何获取所需的类,JVM提供了3种类加载器:

  • 启动类加载器(Bootstrap ClassLoader):负责加载JAVA_HOME\lib目录中的,或通过-Xbootclasspath参数指定路径中的,且被虚拟机认可的类(按文件名识别,如rt.jar,所有java.*开头的类)。
  • 扩展类加载器(Extension ClassLoader):负责加载JAVA_HOME\lib\ext目录中的,或通过java.ext.dirs系统变量指定路径中的类库(如javax.*开头的类)。
  • 应用程序类加载器(Application ClassLoader):负责加载用户路径(classpath)上的类库。

注意:可以通过继承java.lang.ClassLoader实现自定义的类加载器。

这几种类加载器的层次关系如下图所示:
Java类加载的过程和双亲委派机制

3. 双亲委派模型的过程以及优势。

双亲委派模型的过程:某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才由自己加载。

双亲委派模型的系统实现

在java.lang.ClassLoader的loadClass()方法中,先检查是否已经被加载过,若没有加载则调用父类加载器的loadClass()方法(若父加载器为空则默认使用启动类加载器作为父加载器)。如果父加载失败,则捕获ClassNotFoundException异常后,再调用自己的findClass()方法进行加载。

protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
    synchronized (getClassLoadingLock(name)) {
        //check the class has been loaded or not
        Class c = findLoadedClass(name);
        if (c == null) {
            try {
                if (parent != null) {
                    c = parent.loadClass(name, false);
                } else {
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                //if throws the exception ,the father can not complete the load
            }
            if (c == null) {
                c = findClass(name);
            }
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
}

双亲委派模型的优势

Java类随着它的类加载器一起具备了带有优先级的层次关系。例如类java.lang.Object,它存在于rt.jar中,无论哪一个类加载器要加载这个类,最终都是委派给处于模型最顶端的Bootstrap ClassLoader进行加载,因此Object类在程序的各种类加载器环境中都是同一个类。相反,如果没有双亲委派模型而是由各个类加载器自行加载的话,如果用户编写了一个java.lang.Object的同名类并放在ClassPath中,那系统中将会出现多个不同的Object类,程序将混乱。

相关文章:

  • 2021-02-02
  • 2021-09-27
  • 2021-04-05
  • 2021-04-24
  • 2021-06-19
  • 2018-09-02
  • 2018-10-30
  • 2021-09-21
猜你喜欢
  • 2020-02-12
  • 2021-09-21
  • 2021-10-04
  • 2020-05-06
  • 2020-03-31
  • 2019-10-29
  • 2020-07-19
  • 2021-11-16
相关资源
相似解决方案