简单的来说,就是虚拟机把 class 文件加载到内存中,放在方法区,并且进行加载,链接,初始化的过程,其中链接又有验证,准备,解析的三个过程。最终变成虚拟机能使用的 java 类。
加载
也就是文件到内存的过程,把文件转化未字节流,并且依此来创建类。其中类加载过程中,需要类加载器来完成。
类加载器分为三种
- 启动类加载器(BootStrap ClassLoader)
- 常用来加载最为基础的类(lib 目录下的 jar 包中的类)
- 扩展类加载器(ExtClassLoader)
- 常用来加载次要、通用的类(lib/ext 目录下的 jar包)
- 应用类加载器(AppClassLoader)
- 加载应用程序路径下的类,加载 classpath 指定目录下的类。
- 自定义类加载器(CustomClassLoader)
- 自定义类加载器
这里的关系是,启动类加载器是共同的祖师爷,而且启动类加载器是没有自己的对象实例的。而其他的类加载器在此基础下,都是 java.lang.ClassLoader 的子类。
扩展类加载器的父类是启动类加载器,应用类加载器的父类是扩展类加载器。
那么者几个类的加载顺序如何?这里要提到双亲委派模式
双亲委派模式
一个类加载器在接受到请求之后,会将请求转发给自己的父类加载器,如果自己的父类加载器发现没要找到所请求的类的情况下,子类加载器才会尝试加载。(这里的父类会一层一层向上委托,直到顶层,在顶层不行的情况下,交给下来的一层一层子类看是否能加载)
好处
这样能防止类被重复加载,并且防止 Java 的 核心 API 被篡改,毕竟能自定义类加载器。
链接
将创建成的类合并到 Java 虚拟机,使之能够执行,供虚拟机使用。
其中分为三个过程。
- 验证
- 准备
- 解析
验证
确保被加载的类符合 Java 虚拟机的约束条件。不会危害虚拟机的本身安全。其中包括
- 文件格式校验
- 元数据校验
- 字节码校验
- 符号引用校验
准备
对变量进行内存分配,注意这里针对的是 static 修饰的静态变量,对于 final 修饰的变量,在编译期间,就已经分配了内存。
- 为被加载类的静态字段分配内存
- 为静态字段设置初始值,注意这里初始值是 0 或 null,不是自己赋值的值
- 构造其他根类层次结构的数据结构
- 比如说实现虚方法的动态绑定的方法表
解析
将符号引用解析为实际引用。
那么符号引用是什么呢?
在类加载到 java 虚拟机之前,它是不知道它调用的其他类的方法,字段的具体地址为多少,甚至不知道自己的方法、字段,所用需要有一个引用暂时代替这一部分的具体地址。Java 虚拟机将会生成符号引号,在运行过程能够无歧义的定义未知。
举例来说,对于一个方法调用,编译器会生成一个包含目标方法所在类的名字、目标方法的名字、接收参数类型以及返回值类型的符号引用,来指代所要调用的方法。
- 符号引用执行一个未被加载的类,或者未被加载的方法或字段。解析将会触发该类的加载。
- 注意
- JVM 规范中,并没有规定在链接阶段就得完成解析的过程。它是这样规定的
- 如果某些字节码使用了符号引用,那么在执行这些字节码之前,需要完成对这些符号引用的解析。
- JVM 规范中,并没有规定在链接阶段就得完成解析的过程。它是这样规定的
初始化
为标记为常量值的字段赋值,以及执行 方法的过程,该过程有加锁。
也就是执行静态代码块和为静态变量赋值。这是类加载阶段的最后过程。
注意
- 直接赋值的静态字段被 final 所修饰,它的类型又是基本类型或字符串时。
- 标记成常量值
- 初始化直接由 JVM 完成
- 什么是
- 除了直接赋值的操作,以及所有静态代码块的代码,则会被 Java 编译器置为同一方法中。并且命名为
只有完成初始化,类才是可执行的状态。并且只用在对类的主动使用的时候,才会触发初始化。
那么如何触发初始化?
- 虚拟机启动,初始化用户指定
- new 指定的类
- 调用静态方法,初始化静态方法的类
- 访问静态字段,初始化静态字段的类
- 子类初始化触发父类初始化
- 接口定义了一个 default 方法,如果直接或间接实现的类初始化,触发接口初始化。
- 使用反射 API 对某个类反射调用,初始化该类
- 初次调用 MethodHandle 实例时,初始化该 MethodHandle 指向方法所在的类。