类加载过程包含加载,验证,准备,解析和初始化五个阶段。 这五个过程中,加载,验证,准备和初始化这四个阶段的顺序是确定的,解析过程则是不确定的,也可能发生在初始化过程之后,这是为了支持java的运行时绑定(动态绑定)
绑定指的是把一个方法的调用与方法所在的类(方法主体)关联起来,对java来说,绑定分为动态绑定和静态绑定。
静态绑定:即前期绑定。在程序执行前方法已经被绑定,编译期绑定。如final,static,private和构造方法都是前期绑定。
后期绑定:运行时绑定,程序运行过程中方法调用。几乎所有的方法都是后期绑定。
加载: 加载是类加载过程的第一个阶段,在加载过程中,需要完成三件事情:
-
1.获取可以在JVM虚拟机上编译运行的类的二进制字节流。
-
2.将类的类信息,常量及静态变量加入到JVM方法区。
-
3.在JVM堆内生成这个类的java.lang.Class对象,作为对方法区中这些对象方法的引用入口。
方法区:方法区是各个线程共享的区域,它用于存储已被虚拟机加载的类信息,常量和静态变量,即编辑器编译后的代码。
在类加载过程的几个阶段中,加载阶段(准确来说是加载阶段第一步获取二进制字节流阶段)是可控性最强的阶段,既可以通过系统提供的默认类加载器来完成加载,也可以通过自定义的类加载器来加载。
类加载器虽然只用于对类的加载动作,但是在java程序中,它的作用远远不限于此,对于任意一个类,都需要其类加载器和这个类本身来确定其在JVM虚拟机中的唯一性。即使同一个类,如果加载过程中类加载器不同,对于equals,instanceof,isAssignableFrom等结果也会不同。
站在虚拟机的角度,类加载器分为两种,一种是启动类加载器,C++实现,JDK1.5之后默认加载器hotspot,是虚拟机本身的一部分。另外就统统视为其他加载器,使用java实现,独立于虚拟机之外,独立于虚拟机之外,并且全部继承抽象类ClassLoader,这些类加载器需要由启动类加载器加载到JVM内存之后才能去加载其他的类。
站在开发者角度,类加载器分为三类:
启动类加载器: Bootstrap ClassLoader,负责加载jre/lib目录下jvm所需要的类库,启动类加载器不能被java程序直接引用。
扩展类加载器: Extension ClassLoader, 负责加载jre/lib/ext目录下,可直接引用。
应用程序类加载器:Application ClassLoader,负责加载classpath目录下的类,程序默认类加载器。
这种层次关系称为类加载器的双亲委派模型。我们把每层类加载器的上层加载器称为父加载器。
双亲委派模型的过程是:如果一个类加载器收到了类加载的请求,它不会首先尝试加载这个类,而是传递给父加载器加载,最终每个类的加载都会传到启动类加载器,只有当父加载器无法完成加载时,子加载器才会尝试加载。保证了java程序运行的稳定性。如java.lang.Object类,无论如何都会被启动类加载,因此无论是哪个类加载器要加载Object类,都会交给启动类加载器,保证了Object类在程序中的各种类加载器中都是同一个类。
验证:确保编译后的二进制字节码符合虚拟机的要求,不会危害虚拟机的安全。
准备:准备阶段是为类变量分配内存及设置类变量的初始值的阶段,这些内存都将在方法区中分配。 仅包含类变量(static 静态常量),而不包括实例变量,实例变量会在对象实例化时随着对象一同分配到Java堆中。
解析:解析阶段是虚拟机将常量池中的符号引用转换为直接引用的过程。 类或接口的解析:解析本类,再递归解析其实现接口及其父类字段。 字段的解析:先解析本类的字段,再递归解析其实现接口及其父类字段。 方法解析:先解析本类,再解析其实现接口及父类。
初始化:初始化是类加载过程的最后一步,为类的变量赋值。如public static int value = 3,在准备阶段 value =0,在初始化阶段赋值为3。 执行静态代码块
初始化执行时机:1.new实例化对象,调用类静态方法,读取设置类的静态变量 2.java反射执行1的操作 3.初始化子类,会触发父类初始化 4.虚拟机启动时,首先初始化包含main方法的类,,如springboot的application启动类。 5.