今天学习了《深入理解java虚拟机》的类加载机制,描述类如何被虚拟机加载的具体步骤。
- byte、short、char和boolean在java指令中是不被支持的,均会转换为int类型统一运算。
- 虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。
- 类型的加载、连接和初始化过程都是在程序运行期间完成的,会令类加载时稍微增加一些性能开销,但是会为Java应用程序提供高度的灵活性,可以动态扩展的语言特性就是依赖运行期动态加载和动态连接这个特点实现的。
- 类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括:加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和卸载(Unloading)7个阶段。其中验证、准备、解析3个部分统称为连接(Linking)。一般是按照顺序执行的,但是解析阶段在某些情况下可以在初始化阶段之后再开始。
- 加载过程:1)通过一个类的全限定名来获取定义此类的二进制字节流。2)将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。3)在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
- 验证阶段大致上会完成下面4个阶段的检验动作:1)文件格式验证,验证字节流是否符合Class文件格式的规范,并且能被当前版本的虚拟机处理;2)元数据验证,对字节码描述的信息进行语义分析,以保证其描述的信息符合Java语言规范的要求,主要目的是对类的元数据信息进行语义校验,保证不存在不符合Java语言规范的元数据信息;3)字节码验证,通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。对类的方法体进行校验分析,保证被校验类的方法在运行时不会做出危害虚拟机安全的事件;4)符号引用验证,是对类自身以外(常量池中的各种符号引用)的信息进行匹配性校验,是为了确保解析动作能正常执行。
- 准备阶段正式为类变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在方法区中进行分配。这时候进行内存分配的仅包括类变量(被static修饰的变量),而不包括实例变量。这里所说的初始值“通常情况”下是数据类型的零值,例如public static int value = 123;实际value的初始值为0,分配123需要在后边在执行。如果类字段的字段属性表中存在ConstantValue属性,那在准备阶段变量value就会被初始化为ConstantValue属性所指定的值,假如public static final int vallue = 123;则会直接设置初始值为123。
- 解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能够无歧义的定位到目标即可,比如org.simple.People类引用了org.simple.Language类,在编译时People类并不知道Language类的实际内存地址,因此只能使用符号org.simple.Language(假设是这个,当然实际中是由类似于CONSTANT_Class_info的常量来表示的)来表示Language类的地址。直接引用则是直接指向了Language类的指针地址或者间接引用的句柄。解析步骤如图所示:
- 类初始化阶段真正开始执行类中定义的Java程序代码(或者说是字节码)。执行类构造器<clinit>()方法的过程。<clinit>()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static{}块)中的语句合并产生的。<clinit>()方法与类的构造函数(或者说实例构造器<init>()方法)不同,它不需要显式地调用父类构造器,虚拟机会保证在子类的<clinit>()方法执行之前,父类的<clinit>()方法已经执行完毕。<clinit>()方法对于类或接口来说并不是必需的,如果一个类中没有静态语句块,也没有对变量的赋值操作,那么编译器可以不为这个类生成<clinit>()方法。执行接口的<clinit>()方法不需要先执行父接口的<clinit>()方法,类的则必须先执行父类的。<clinit>()方法只能执行一次,多线程中会自动加锁同步。
- 类加载器是实现把类加载阶段中的“通过一个类的全限定名来获取描述此类的二进制字节流”这个动作放到Java虚拟机外部去的动作。双亲委派模式:先检查是否已经被加载过,若没有加载则调用父加载器的loadClass()方法,若父加载器为空则默认使用启动类加载器作为父加载器。如果父类加载失败,抛出ClassNotFoundException异常后,再调用自己的findClass()方法进行加载。如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。
网友评论