JVM之加载java类
【什么是类加载?】
工程文件中编写的.java文件存储着需要计算机执行的程序逻辑,.java文件经过Java编译器编译成拓展名为.class文件,.class文件中保存着Java代码经转换后的字节码指令。
当需要使用某个类时,java虚拟机将会加载它的.class文件,并创建对应的Class对象,将class文件加载到虚拟机的内存,这个过程就是类加载。
【类加载的基本过程】
【类加载过程详解】
【加载阶段】
加载概念
加载,是指查找字节流,并且据此创建类的过程。
注意:字节流最常见的形式要属由 Java 编译器生成的 class 文件。除此之外,我们也可以在程序内部直接生成,或者从网络中获取例如网页中内嵌的小程序 Java applet)字节流等。这些不同形式的字节流,都会被加载到 Java 虚拟机中,成为类或接口。
类加载器
对于普通的类来说,JVM需要通过类加载器来完成查找字节流的过程。
所有的类加载器都继承于启动类加载器(java.lang.ClassLoader),而这些类加载器本身又需要另一个类加载器(如启动类加载器)加载至java虚拟机中,然后才能执行类加载。
注意:启动类加载器是有C++实现的,没有对应的java对象,因此在java中以null来指代。而其他的类加载器都有对应的java对象。
加载过程
类加载在执行加载请求时,采用双亲委派模型。
即当一个类加载器接收到加载请求时,它会先将请求转发到父类加载器。在父类加载器没有找到所请求的类的情况下,该类加载器才会尝试去加载。
各类加载器负责范围
jdk9之前:
启动类加载器 负责加载最基础,最重要的类。比如JRE的lib目录下jar包中的类
扩展类加载器 负责加载相对次要但又通用的类,比如JRE的lib/ext目录下的jar包中的类(以及由系统变量 java.ext.dirs 指定的类)。
应用类加载器 负责加载应用程序路径下的类。默认情况下,应用程序中包含的类便是由应用类加载器加载的。
注意:应用类加载器的父类为扩展类加载器,扩展类加载器的父类为启动类加载器。
【链接阶段】
验证阶段
目的:保被加载类能够满足 Java 虚拟机的约束条件
通常来说,java编译器生成的类文件必然满足JVM的约束条件。
准备阶段
目的:
1.为被加载类的静态字段分配内存。
2.部分JVM还会在此阶段构造其他跟类层次相关的数据结构,比如用来实现虚方法的动态绑定的方法表。
解析阶段
在class文件被加载到JVM之前,这个类无法知道自己以及其他类的方法、字段所对应的具体地址。因此,每当需要引用这些成员时,java编译器会生成一个符号引用。(在运行阶段,这些符号引用一般能无歧义地定位到具体目标上)
解析阶段的目的,就是将这些符号引用解析成为实际引用。(如果符号引用指向一个未被加载的类,或字段、方法,那么解析将触发这个类的加载阶段)
【初始化阶段】
目的:为标记为常量值的字段赋值,以及执行方法。JVM会通过加锁确保类的方法只执行一次。当初始化完成,类才正式变成可执行状态。
补充:
常量值:static final 字段,且其类型为基本类型或String类型。(其初始化有JVM直接完成)
<clinit>:除常量值以外的所有赋值操作(new),以及所有静态代码块中的代码,会被java编译器置于同一方法中,此方法即为<clinit>方法。
类初始化阶段触发汇总
- 当虚拟机启动时,初始化用户指定的主类
- 当遇到用以新建目标类实例的 new 指令时,初始化 new 指令的目标类
- 当遇到用以新建目标类实例的 new 指令时,初始化 new 指令的目标类
- 当遇到调用静态方法的指令时,初始化该静态方法所在的类
- 当遇到访问静态字段的指令时,初始化该静态字段所在的类
- 子类的初始化会触发父类的初始化
- 如果一个接口定义了 default 方法,那么直接实现或者间接实现该接口的类的初始化,会触发该接口的初始化
- 使用反射 API 对某个类进行反射调用时,初始化这个类
- 当初次调用 MethodHandle 实例时,初始化该MethodHandle 指向的方法所在的类