java虚拟机动态加载、链接和初始化类和接口。
加载是根据具体的名称查找类或接口的二进制表示(binary representation),并由此二进制表示来创建类或接口的过程。
链接是为了让类或接口可以被Java虚拟机执行,而将类或接口并入Java虚拟机运行时状态的过程。
初始化是指类或接口通过执行初始化方法clinit。
1、类加载器类型
类加载器主要分为两类,一类是JDK默认提供的,一类是用户自定义的。 JDK 默认提供三种类加载器:
类加载器 | 说明 |
Bootstrap ClassLoader | 启动类加载器,每次执行 java 命令时都会使用该加载器为虚拟机加载核心类。该加载器是由 native code 实现,而不是 Java 代码,加载类的路径为 <JAVA_HOME>/jre/lib。特别的 <JAVA_HOME>/jre/lib/rt.jar 中包含了 sun.misc.Launcher 类, 而 sun.misc.Launcher.ExtClassLoader 和sun.misc.Launcher.AppClassLoader 都是 sun.misc.Launcher 的内部类,所以拓展类加载器和系统类加载器都是由启动类加载器加载的。 |
Extension ClassLoader | 拓展类加载器,用于加载拓展库中的类。拓展库路径为 <JAVA_HOME>/jre/lib/ext/。实现类为 sun.misc.Launcher.ExtClassLoader。 |
System ClassLoader | 系统类加载器,用于加载 CLASSPATH 中的类。实现类为 sun.misc.Launcher.AppClassLoader。 |
Custom ClassLoader | 自定义类加载器,一般都是 java.lang.ClassLoder 的子类。 |
图中,四个具体的类加载器间均有delegate关系,即双亲委派。正统的类加载机制是基于双亲委派的,也就是当调用类加载器加载类时,首先将加载任务委派给双亲,若双亲无法加载成功时,自己才进行类加载。
2、自定义类加载器
使用自定义类加载器来加载类型过程图解:
1、首先Java虚拟机检查L类加载器依据具体类型的二进制名称,查找是否已经存在,存在则不需要重复创建类型。
2、不存在的话,则需要创建该类型,L类加载器调用classloader的defineClass方法,输入参数为该类型的byte[](类型的二进制表示binary representation),defineClass方法将类型的二进制文件转换为实力对象。
3、classloader调用loadClass(String name)方法,返回刚刚创建好的实例对象。
更多classloader的方法说明参考java api文档
类加载过程中关注几个要点词汇:
1、类或接口的二进制表示(byte[],二进制的class文件)。
2、类或接口二进制名,来自类型维护的常量池。
3、值对,二进制名称+定义类加载器确定具体的类。
4、触发,类或接口可以由自己定义的类加载器创建,也可以出发其他的类加载器创建需要的引用类。
3、class文件二进制表示验证
一个class二进制表示能否正确被创建为实例,需要经过验证环节。
4、链接
链接类或接口包括验证和准备类或接口、它的直接父类、直接父接口、元素类型。而解析这个类或接口中的符号引用则是链接过程中可选的环节。
Java虚拟机规范会灵活的选择链接时机,但也必须遵守以下规则:
1、在类或接口被链接之前,它必须成功的被加载过。
2、在类或接口初始化之前,它必须成功的被验证和准备过。
3、若程序执行了某种可能需要直接或间接链接一个类或接口的动作,而在链接该类或接口过程中又检测到了错误,则错误的抛出点是在执行动作的那个点。
环节 | 说明 |
验证 | 该环节用于确保类或接口的二进制表示在结构上是正确的。 |
准备 | 该阶段的任务是创建类或接口的静态字段,并用默认值初始化这些字段。这个阶段不会执行任何虚拟机字节码指令。在初始化阶段会有显式的初始化器来初始化这些静态字段,所有准备阶段不做这些事情。 |
解析 | 该环节是根据运行时常量池里的符号引用来动态决定具体值的过程 |
5、初始化
对于类或接口来说,就是执行初始化方法。在类或接口初始化前,它必须被链接过,也就是经过验证、准备阶段,且可能依据解析完成。