Java学习笔记

《深入理解Java虚拟机》学习笔记(五)(虚拟机类加载机制)

2017-03-09  本文已影响42人  码梦的一生

虚拟机类加载机制

类加载的时机

类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括:加载(Loading)、 验证(Verification)、 准备(Preparation)、 解析(Resolution)、 初始化(Initialization)、 使用(Using)和卸载(Unloading)7个阶段。 其中验证、 准备、 解析3个部分统称为连接(Linking)。其中,加载、 验证、 准备、 初始化和卸载这5个阶段的顺序是确定的。

图1 类的生命周期

类的加载

需要做什么?

数组类的加载

数组类本身不通过类加载器创建,它是由Java虚拟机直接创建的。 但数组类的元素类型需要靠类加载器去创建。

类的验证

目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。包括四种:

类的准备

为类变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在方法区中进行分配。这时候进行内存分配的仅包括类变量(被static修饰的变量),而不包括实例变量,实例变量将会在对象实例化时随着对象一起分配在Java堆中。

解析

将常量池内的符号引用替换为直接引用的过程

如果查找过程成功返回了引用,将会对这个字段进行权限验证,如果发现不具备对字段的访问权限,将抛出java.lang.IllegalAccessError异常。

最后,如果查找过程成功返回了直接引用,将会对这个方法进行权限验证,如果发现不
具备对此方法的访问权限,将抛出java.lang.IllegalAccessError异常。

由于接口中的所有方法默认都是public的,所以不存在访问权限的问题,因此接口方法的符号解析应当不会抛出java.lang.IllegalAccessError异常。

初始化

初始化阶段是执行类构造器<clinit>()方法的过程。

不需要显式地调用父类构造器,虚拟机会保证在子类的<clinit>()方法执行之前,父类的<clinit>()方法已经执行完毕。 因此在虚拟机中第一个被执行的<clinit>()方法的类肯定是java.lang.Object。

由于父类的<clinit>()方法先执行,也就意味着父类中定义的静态语句块要优先于子类的变量赋值操作

如果一个类中没有静态语句块,也没有对变量的赋值操作,那么编译器可以不为这个类生成<clinit>()方法。

执行接口的<clinit>()方法不需要先执行父接口的<clinit>()方法。 只有当父接口中定义的变量使用时,父接口才会初始化。

如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的<clinit>()方法

类的初始化

什么时候开始?(对一个类进行主动引用。 除此以下5种情况之外,所有引用类的方式都不会触发初始化,称为被动引用。 )

接口的初始化

与类的初始化相同,除了第三点,一个接口在初始化时,并不要求其父接口全部都完成了初始化,只有在真正使用到父接口的时候(如引用接口中定义的常量)才会初始化。而且接口中,无法使用静态语句块。

类加载器

每一个类加载器,都拥有一个独立的类名称空间

双亲委派模型

从Java虚拟机的角度来讲,只存在两种不同的类加载器:启动类加载器和所有其他类的加载器

图2 类加载器双亲委派模型

双亲委派模型机制

双亲委派模型要求除了顶层的启动类加载器外,其余的类加载器都应当有自己的父类加载器。 这里类加载器之间的父子关系一般不会以继(Inheritance)的关系来实现,而是都使用组合(Composition)关系来复用父加载器的代码。

如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。

上一篇下一篇

猜你喜欢

热点阅读