03 JAVA虚拟机是如何加载java类的

JAVA虚拟机是如何加载java类的:

双亲委派模型

类加载器:

  • 启动类加载器,由C++实现,没有对应的java对象,所以由null指代,除了启动类加载器,其他都是java.lang.ClassLoader的子类;
    • 启动类加载器负责加载最为重要、基础的类,比如存放在JRE的lib目录下jar包中的类(-XBootclasspath指定的类);
  • 扩展类加载器,其父、类加载器(非继承)是启动类加载器,负责加载相对次要但又通用的类,比如存放在lib/ext目录下jar包中的类(系统变量java.ext.dirs指定的类)
  • 应用类加载器,其父、类加载器(非继承)是扩展类加载器,负责加载应用程序路径下的类,-cp/-classpath、系统变量java.class.path或环境变量CLASSPATH所指定的路径;JDK9中,它命名为平台类加载器,除了少数几个关键模块,如java.base是由启动类加载器加载以外,其他模块均由应用类加载器加载;
  • 自定义类加载器,由用户实现;

双亲委派模型:

  • 双亲委派模型:每当一个类加载器接收到加载请求时,它会先将请求转发给父加载器,在父类加载器没有找到所请求的类的情况下,才会尝试去加载;

加载器的命名空间作用:

  • 如果同一个类被不同的类加载器加载,会被认为是两个不同的类;类的唯一性,由类加载器实例及类的全名共同确定,大型应用程序可以借助这一特性,运行同一个类的不同版本;

类的加载分三个步骤:

  1. 加载:
    • 加载是指查找字节流,并据此创建类的过程,则涉及上面提到的双亲委派模型和类加载器相关知识;
  2. 链接:
    • 链接是将创建的类合并至java虚拟机中,使其能够执行的过程;他分为三个阶段:
      1. 验证:验证的目的在于确保加载的类满足java虚拟机的约束条件;一般来说java编译器生成的class文件必然满足java虚拟机的约束条件
      2. 准备:准备阶段则是为加载类的静态字段分配内存;部分java虚拟机在此阶段还会构建其他跟类层次相关的数据结构,如用来实现虚方法的动态绑定的方法表;
      3. 解析:在class文件在被加载至虚拟机之前,该类都无法知道其他类及其方法、字段所对应的具体地址,甚至不知道自己的方法、字段地址,因此,在需要引用这些成员时,java编译器则会生成一个符号引用,在运行阶段,这些符号引用一般都能无歧义的定位到具体目标上,解析阶段的目的,就是将这些符号引用解析为实际引用,如果符号引用指向一个未被加载的类或者未被加载的类的字段或方法,那么解析将触发这个类的加载;
    • 其中,解析是非必须的,虚拟机规范只规定在使用了符号引用的字节码调用前完成解析即可,不强制要求链接时完成解析;
  3. 初始化:
    1. 如果直接赋值的静态字段被final修饰,并且它的类型是基本类型或者字符串,那么该字段便会被java编译器标记为常量,其初始化直接由虚拟机完成,除此之外的直接赋值操作,及静态代码块中的代码,则会被java编译器置于同一方法中,并把它命名为< clinit >;Java虚拟机会通过加锁来保证clinit方法仅执行一次,只有初始化完成之后,该类才正式成为可执行的状态;

类的加载时机

不是每次加载类都会触发类的加载,如Class clazz=xxx.class及Classloader.loadClass("xxx"),或者class.forName在参数中明确指定不初始化,这些操作都不会触发类的初始化,仅加载类;JVM虚拟机规范规定了下列情况会触发类的初始化:

1. 当虚拟机启动时,初始化用户指定的主类;
2. 当遇到new指令时,初始化目标类;
3. 当遇到调用静态方法的指令时,初始化该静态方法所在的类;
4. 当遇到访问静态字段的指令时,初始化该静态字段所在的类;
5. 子类初始化会触发父类的初始化;
6. 如果一个接口定义了default方法,那么直接实现或间接实现该接口的类的初始化会触发该接口的初始化;
7. 当初次调用MethodHandle时,初始化该MethodHandle指向的方法所在的类;
8. 当使用反射API对某个类进行反射调用时,初始化这个类;

新建对象数组会加载类,但不会执行链接和初始化;

你可能感兴趣的:(03 JAVA虚拟机是如何加载java类的)