7.1 概述
虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析、初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制. 在Java语言里,类型的加载、连接、初始化过程都是在程序运行期间完成的.
7.2 类加载的时机
一个类的生命周期概括可分为类的加载、连接、初始化、使用、卸载,其中连接又可分为3步,验证、准备、解析.
虚拟机规范严格规定了5中情况必须立即对类进行“初始化”
- 遇到new、getstatic、putstatic、invokestatic这4条字节码时,分别是new关键字实例化对象、都区设置一个类的静态字段、调用一个类的静态方法的时候。
- 使用java.lang.relect包的方法进行反射调用的时候
- 当初始化一个类的时候,发现其父类还没有进行过初始化,则先初始化父类
- 当虚拟机启动,用户指定一个执行的主类(包含main方法的类)
- 当使用JDK1.7动态语言支持时
不会发生初始话的情况:
- 通过子类引用父类的静态字段,不会导致子类初始化
- 通过数组定义来引用类,不会触发此类的初始化
- 常量咋编译阶段会存入调用类的常量池中,本质上并没有直接引用到定义常量的类,因此不会触发定义常量的类的初始化
7.3 类的加载过程
- 加载
1)通过一个类的全限定名来获取此类的二进制字节流
2)将此字节流所代表的静态存储结构转化为方法区的运行时数据结构
3)在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数的访问
其中比较特殊的是数组类,数组类本身不是通过ClassLoader创建,它是又Java虚拟机直接创建
验证
1)文件格式验证
2)元数据验证
3)字节码验证
4)符号引用验证准备
是正式为变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在方法区中进行分配。(仅被static修饰的变量)
public static int value = 123 在此时会被赋值为0,在后面初始化阶段才被赋值为123.
public static final int value = 123 在此刻会被赋值为123.解析
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程.符号引用,是任何形式的字面量。直接引用可以是直接指向目标的指针、相对偏移量或者一个能间接定位到目标的句柄。
1.类或接口的解析:假设当前代码所处的类为D,如果要把一个从未解析过的符号引用N解析为一个类或接口C的直接引用,那需要三步:
1)如果C不是数组,虚拟机会用C的类加载器加载C,这样又会触发C的类加载过程。
2)如果C是数组,并且数组的元素类型为对象,那也将会按照1)点规则去加载,接着虚拟机生成一个代表此数组维度和元素的数组对象。
3)进行符号引用验证,确认D是否具备对C的访问权限。
2.字段解析:就是解析字段所属的类或者接口的符号引用,如果解析完成,那将这个字段所属的类或接口用C表示,对C进行后续字段搜索。
1)如果C本身就包含了简单名称和字段描述符都匹配的字段,则返回这个字段的直接引用,查找结束。
2)否则,如果在C中实现了接口,将会按照继承关系从下往上递归搜索各个接口和他的父接口,如果找到则返回这个字段的直接引用。
3)否则,如果C不是java.lang.Object的话,将会按照继承关系从下往上搜索,找到返回,否则抛出java.lang.NoSuchFieldError异常-
初始化
1. 初始化阶段是执行类构造器<client>()方法的过程。<client>()方法是由编译器自动收集类中的所有类的变量的复制动作和静态语句块中的语句合并产生,编译器收集顺序是由语句在源文件中出现的顺序决定2. <client>()方法与类的构造函数(<init>()方法)不同,它不需要显示的调用父类构造器,因此虚拟机第一个执行java.lang.Object的<client>()方法。
3. 由于父类的<client>()方法先执行,也就意味着父类中定义的静态语句块要优先于子类的变量赋值操作。
4. <client>()方法是非必需的,如果一个类中没有静态语句块,也没有对变量的赋值操作,就不生成<client>()方法。
5. 接口中不会先执行父类的<client>()方法,只有当父类的变量使用时父类的接口才会初始化,接口的实现类在初始化时,不会执行接口的<client>()方法。
6. <client>()方法在多线程环境中是同步的,如果<client>()方法中有耗时长的操作,就可能造成多个进程的阻塞。
7.4 类的加载
7.4.1 类与类的加载器
对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在Java虚拟机中的唯一性,每一个类加载器,都拥有一个独立的类命名空间
7.4.2 双亲委派模型
1. Bootstrap ClassLoader(是启动类加载器),由C++实现,是虚拟机的一部分。负责将存放在 <JAVA_HOME>\lib 目录中,或者被 -Xbootclasspath参数所指定的路径中的,并且是虚拟机识别(仅按照文件识别,如rt.jar 名字不符的类库即使放在lib目录中也不会被加载)的类库加载到虚拟机中
2. Extension ClassLoader (扩展类加载器),由sum.misc.Launcher$ExtClassLoader实现,它负责加载 <JAVA_HOME>\lib\ext 目录中的类,或被java.ext.dirs系统变量所指定路径中的类,开发者可直接使用。
3. Application ClassLoader (应用程序类加载器),由sum.misc.Launcher$AppClassLoader实现, 一般称为系统类加载器,它负责加载用户路径 ClassPath 上所指定的类库,开发者可直接使用。
这种层次关系称为类加载器的双亲委派模型(Parents Delegation Model)
工作过程:
如果一个类加载器收到了类加载的请求,它首先不会自己尝试加载此类, 而是把这个请求委派给父类加载器去完成,所有的加载请求最终都会传到顶层BootstrapClassLoader去尝试加载,只有父类无法加载时,子类才会尝试加载。
为什么要这样设计?
Java类随着它的类加载器一起具备了优先级的层次关系.
源码如下:先检查是否已经被加载过,若没有加载则调用父加载器的loadClass()方法,若父加载器为空则默认使用启动类加载器作为父加载器。如果父加载器加载失败,抛出ClassNotFoundException异常后,再调用自己的findClass()方法进行加载。