虚拟机类加载机制
7.1 概述
- 虚拟机的类加载机制:虚拟机把描述类的Class文件中的数据读取到内存中,然后对数据进行校验、转换解析和初始化,最终形成可被虚拟机直接使用的Java类型。
- Java语言中,类型的加载、连接和初始化都是在运行期间完成的,缺点是增加了性能开销,优点是为Java程序提供了高度的灵活性,Java语言的动态扩展性依赖于动态加载和动态连接。
- 例如:如果一个Java程序是面向接口的,那么对于接口实现的类可以在运行期间才指定。
- 例如2:用户可以通过Java预定义的或自定义的类加载器,让一个本地的程序在运行期间从网络或其他地方加载一个二进制流作为程序代码的一部分。
7.2 类加载的时机
- 类从被加载到虚拟机内存中,到从内存中卸载共经过加载、验证、准备、解析、初始化、使用、卸载7个过程。其中验证、准备和解析统称为连接。
- 类加载的过程中,加载、验证、准备、初始化和卸载的过程是不可变的,类的加载过程必须按照这个顺序按部就班地开始。对于解析阶段:它在某些情况下可以在初始化阶段之后再开始,为了为支持Java语言的运行时绑定。类加载的各个阶段通常是互相交叉的混合式进行。会在一个阶段调用另外一个阶段。
- 各阶段开始的时机:
- 加载:加载开始的时间是由虚拟机自定的。
- 初始化:有且只有以下5种情况才会进行初始化。
- 当遇到new、getstatic、putstatic、invokestatic中4条字节码指令时,如果类没有初始化,则要首先触发初始化。当使用new实例化对象时,读取或设置一个类的静态字段(被final修饰,已在编译期把结果放入常量池中除外)以及调用一个类的静态方法时候。
- 使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有初始化,则触发类的初始化。
- 当初始化一个类的时候,如果发现其父类还没有初始化,则需要触发类的初始化。
- 当虚拟机启动时,用于需要指定一个要执行的主类(包含main方法)的哪个类,虚拟机首先对此类进行初始化。
- 当使用JDK1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类还没有初始化,则需要触发初始化。
- 特殊情况:
- 只有直接定义静态字段的类才会被初始化。
- 通过定义数组来引用类,不会触发类的初始化。
- 常量在编译阶段会存入调用类的常量池中,不会触发类的初始化。
- 接口加载和类加载的区别:
- 接口中不能使用static{}代码块
- 当类在初始化的时候要求其父类必须已经初始化,但是对于接口在初始化时,不要求其父接口全部都完成了初始化,只有在真正用到的时候才会初始化。
7.3 类加载的过程
- 加载阶段虚拟机完成的事情:
- 通过类的全限定名获取到描述类的二进制字节流
- 从ZIP包中获取
- 从网络中获取
- 运行时计算生成
- 其他文件生成
- 从数据库中读取
- 将这个字节流代表的静态存储结构转换为方法区的运行时数据结构
- 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
- 通过类的全限定名获取到描述类的二进制字节流
- 数组类和非数组类的加载区别:
- 非数组类的加载是由类加载器完成的,可以使用虚拟机定义的类加载器,也可以使用自定义的类加载器(重写loadClass()方法)
- 数组类是由虚拟机直接创建的,但数组类的元素类型最终是要靠类加载器创建
- 数组类的创建过程:
- 如果数组类的组件类型是引用类型,递归采用加载过程去加载这个组件类型数组将在加载该组建类型的类加载器的类名称空间上被标识。
- 如果数组类的组件类型不是引用类型,Java虚拟机会把数组标记为与引导类加载器关联。
- 数组类的可见性与它的组件类型的可见性一致,如果组件类型不是引用类型,那数组类的可见性默认为public。
- 加载阶段和连接阶段是交叉进行的,但是在时间上是由严格的先后顺序的。