引言
虚拟机的类加载机制主要是讲虚拟机将class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机使用的java类。类加载的生命周期如下:加载,验证,准备,解析,初始化,使用,卸载。验证、准备、解析这三步又被称为连接,加载、验证、准备、初始化和卸载这五个阶段的顺序是固定的,但是解析可能会在初始化后再开始,这里的“开始”指的是可能会交叉运行。
类加载过程
- 加载
这个加载只是类加载的一个部分,加载过程主要有以下三个部分:
①通过一个类的全限定名来获取定义此类的二进制字节码。
可以从zip包中获取,例如之后的jar、ear、war格式;从网络获取;运行时生成,例如动态代理;由其他文件生成,例如jsp;从数据库读取等等。
②将这个字节流所代表的静态存储结构转换为方法区的运行时数据结构。
③在内存中生成class类对象。
在内存中实例化一个java.lang.Class对象,这个对象作为程序访问方法区中类型数据的接口。
加载和验证阶段的部分内容是交叉运行的。 - 验证
验证是确保class文件中包含的字节流符合虚拟机要求。
①文件格式验证
这个阶段是对class二进制字节流的验证,通过验证后会把字节流放入方法区,在之后的验证则是基于方法区的存储结构进行验证,而不是直接操作字节流。
②元数据验证
这个阶段是对字节码描述的信息进行语义分析,保证符合虚拟机规范。
③字节码验证
会对类的方法体进行校验分析,保证方法运行时不会做出危险行为。之前有提到过StackMapTable这个编译时就生成的属性,这个属性描述了方法体中所有块开始时本地变量表和操作栈应该有的状态,所以程序运行后虚拟机不需要再进行这一部分的推导,从而节约了一部分时间。但是这个不是很安全,因为字节码是可以被修改的,StackMapTable这个属性就会被修改。
④符号引用验证
这就校验阶段是在解析时将符号引用转换为直接引用的时候,校验符号引用是否正确。 - 准备
准备阶段是为类变量分配内存并设置初始值的阶段。类变量也就是static修饰的变量,不需要实例化就能引用,会将类变量赋值为初值。下面为各类型初值列表。
如果修饰为static final常量,那么就会直接根据ConstantValue属性赋值为该值。 - 解析
解析阶段是将常量池中的符号引用替换成直接引用的过程,所谓的符号引用就是字面量跟内存布局无关,只是在class文件 中常量池的位置,而直接引用跟内存挂钩,也就是内存的地址,也就是把引用内存中存在的东西。解析的时间是不定的。 - 初始化
初始化的过程就是调用’<clinit>
’方法的过程。这个方法是由编译器自动收集static变量的赋值和静态代码块合并产生的,静态代码块只能访问到静态代码块之前的静态变量,在前面的静态代码块可以赋值,但是不能访问。虚拟机不会显式调用父类的类构造器,它会保证在类初始化之前,已经将父类的该方法执行完毕了。不过如果类中没有静态语句,也没有常量的赋值,那么就不会生成clinit方法。
初始化时机:
如果没有进行初始化
①遇到new,getstatic,putstatic或invokestatic这四条字节码指令时。
②对类进行反射调用时。
③如果父类还没有进行初始化,那么触发父类初始化。
④启动时的执行主类。
⑤jdk1.7后动态语言的相关操作,如REF_getStatic,REF_putStatic,REF_invokeStatic
。
以上主动引用的方式会进行初始化,但是被动引用不会进行初始化。
例如子类读取父类的静态变量;实例化数组;引用另一个类的常量,另一个类不会进行初始化,实际上那个常量已经进入了本类的常量池。
类加载器
类加载器完成了类加载的加载过程,将二进制的字节码加载到内存的方法区中,并生成class类。如果同一个类被不同的类加载器加载,那么这两个类也是不相等的。
双亲委派模型
java类加载器主要分为两类:一种是由c++实现的类加载器,只有一个启动类加载器,这是虚拟机的一部分;还有一种是java实现的类加载器,是独立于虚拟机之外的。
细分的话虚拟机又可以分为:
- 启动类加载器
加载对象是java核心库,由c/c++实现,不继承java.lang.ClassLoader,它上市所有类加载器的最终父加载器,也就是祖先类加载器。它会将JAVA_HOME/jre/lib下的类库加载到内存中。
- 扩展类加载器
由java编写,加载JAVA_HOME/jre/lib/ext下的类,该类有启动类加载器加载,因为启动类加载器不是由java编写,所以无法取得父类加载器,调用getParent()方法会得到null,也可以说null是启动类加速器的代表。
- 应用程序类加载器
负责加载用户类路径指定的类库,如果用户没有自定义类加载器,就默认使用应用程序类加载器。它的父类是扩展类加载器,但是它由启动类加载器加载。
- 用户自定义类加载器
是用户自己定义的类加载器,由应用程序类加载器加载。
双亲委派模型的工作过程是:当加载一个类的时候,类加载器不会自己去加载,而是请求父类去加载,所有的请求最终会传到启动类加载器。如果父类加载器无法加载,子类才会自己去加载该类。下面是双亲委派模型:
类加载器由全盘负责机制,当一个类加载器加载一个类,同时会加载该类所有依赖的类。
总结
我们来初步回顾一下类加载过程:
- 加载:类加载器将二进制流存入到方法区,接着生成class类作为对外的数据访问接口。
- 验证:验证class文件中内容的合法性。
- 准备:为类变量分配内存,并赋初值。
- 解析:将符号引用转换为直接引用。
- 初始化:调用clinit方法。