JVM类加载机制

PS:以下所有内容来自《深入理解Java虚拟机》

1. 什么是类加载机制

    Java虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的java类型,这个过程被称作虚拟机的类加载机制

2. 类加载的时机

    一个类型被加载到虚拟机内存 => 到卸载出内存的整个生命周期分为7个阶段:加载(Loading)验证(Verification)准备(Preparation)解析(Resolution)初始化(Initialization)使用(Using)卸载(Uploading)。其中验证、准备、解析统称为连接阶段(Linking)。

关于解析:

其他步骤的顺序都是确定的,解析却可以“灵活处理”。为了支持java语言的运行时绑定特性(动态绑定或晚期绑定),解析的过程在某种情况下可以在初始化阶段之后再开始。

关于初始化:

对于初始化,《Java虚拟机规范》规定了有且只有6种情况必须立即对类初始化。

1)遇到new、getstatic、putstatic或invokestatic这四条指令码时。

(个人理解:实例化对象和对被static修饰的方法或变量进行操作时,如果没有被初始化需要提前进行初始化。)

2)使用java.lang.reflect包的方法对类型进行反射调用时

3)初始化类时,其父类没有被初始化,需要先初始化父类。

(个人提示:而接口就不同,调用接口的方法时,如果没有调用到父类的方法,则父类不需要先初始化。)

4)虚拟机启动时,会先初始化含有main()方法的主类。

(个人理解:main()方法是程序的入口,如果没有入口就不能正常启动,所有用户需要指定一个执行的主类。)

5)使用JDK7新加入的动态语言支持,如果一个java.lang.invoke.MethodHandle实例最后的解析结果为REF_getStatic、REF_PutStatic、REF_invokeStatic、REF_newInvokeSpecialStatic、四种类型的方法句柄,并且这个方法句柄对应的类没有进行过初始化,则需要先触发其初始化。

(不是很了解这个动态语言的使用,但不难看出,只要涉及到了static,在使用它或者说引用到它之前,需要注意它有没有被初始化)

6)当一个接口定义了JDK8新加入的默认方法时,如果有这个接口的实现类发生了初始化,那该接口要在其之前被初始化。还没有深究其原因,留个标记,以后有时间想一想

总结:这6个行为被称为对一个类的主动引用,即 会触发初始化的引用。

记忆关键点:1.主动new和使用static  2. 反射  3. 父类先初始化  4.有运行入口的类首先要被初始化  5.对static的触发(可以和1合并记忆)  6.被defult修饰的接口方法在实例之前初始化

被动引用的例子(即不会触发初始化的引用):

1. 通过子类引用父类的静态字段,不会导致子类初始化(非主动使用类字段)

2. 通过数组定义来引用类,不会触发此类的初始化(只会在用到数组中引用元素的内容时才会初始化)

3. 常量在编译阶段会存入调用类的常量池中,本质上没有直接引用到定义常量的类,因此不会触发定义常量的类的初始化。(例如被final修饰的常量)

2. 类加载过程

一、加载(loading)

加载阶段,虚拟机需要完成三件事:

1)通过一个类的全限定名来获取定义此类的二进制字节流(这一步并没有规定字节流的获取方式和途径,因此增添了java应用的灵活性,让java语言可以用多种使用途径,例如压缩包、动态代理技术、jsp生成class文件、把程序安装到数据库中、通过加密文件增添保护机制等)

2)将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构

3)在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口

关于加载的灵活性,对于非数组类型的可控性很强。但数组类型要另当别论。因为数组类本身不通过类加载器创建,它是由Java虚拟机直接在内存中动态构造出来的。但数组内的元素类型最终还是要靠类加载器来完成加载。创建过程的具体规则,直接看书P365。

二、验证(Verification)

这一阶段的目的是确保Class文件的字节流中包含的信息符合《java虚拟机规范》的全部约束要求,保证这些信息被当作代码运行后不会危害虚拟机自身的安全。

验证阶段大致分为四个检验动作:文件格式验证、元数据验证、字节码验证、符号引用验证。

文件格式验证:主要是验证字节流是否符合class文件的规范,例如开始一定是咖啡宝贝0xCAFEBABE,版本号是否符合规范等。目的是保证输入的字节流能正确地解析并存储与方法区之内,格式上符合描述一个Java类型信息的要求。

元数据验证:验证点:是否有父类,父类是否继承了不被允许继承的类,对于不是抽象类的类是否实现了接口的所有方法等。目的是对类的元数据信息进行语义校验。

字节码校验:对类的方法体进行校验分析(class文件中的code属性)。主要目的是通过数据流分析和控制流分析,确定语义是合法的、符合逻辑的。

符号引用校验:该校验行为发生在虚拟机将符号引用转化为直接引用的时候。这个转化在解析阶段中发生。

三、准备(Preparation

准备阶段是正式为类中定义的变量(即被static修饰的变量)分配内存并设置类变量初始值的阶段。(此初始值指的是,自身的默认零值,只有被final修饰的初始值才是后期人为赋的值)

四、解析(Resolution

解析阶段是Java虚拟机将常量池内的符号引用替换为直接引用的过程。(具体解析方法很复杂,看书P371)

五、初始化(initialization)

进行准备阶段时,变量已经赋过一次系统要求的初始零值了。在初始化阶段,则会根据程序员通过程序编码指定的主管计划去初始化类型变量和其它资源。

3. 类加载器

    Java虚拟机设计团队有意把类加载阶段中的“通过一个类的全限定名来获取描述该类的二进制字节流”这个动作放到Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需的类。实现这个动作的代码被称为“类加载器”(Class Loader)。

首先要知道两个类相等的一个前提条件,必须要是在同一个类加载器中加载的。所以拥有一个能使Java程序稳定性高的类加载方法很重要。双亲委派模型因此出现。

站在Java虚拟机的角度,只存在两种类加载器:

1.启动类加载器(Bootstrap ClassLoader),用C++实现,它是虚拟机自身的一部分 

2.其他所有的类加载器,用Java语言实现,独立于虚拟机外部,全部继承于抽象类java.lang.ClassLoader

站在Java开发人员的角度看,JDK1.2以后就开始使用三层类加载器、双亲委派的类加载架构。


三层类加载器

双亲委派模型用自己的理解说,一个类在收到加载请求后,会首先向上寻找父类加载器,直到找到最顶部的启动类加载器后再询问该加载器能不能找到需要加载的类,找不到再向下一级加载器询问,知道找到为止,就开始加载。换句话说就是,向上委派,向下加载。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容