JVM系列之类加载机制

何为JVM(Java Virtual Machine)的类加载机制?简单来说,JVM把类的描述数据从class文件(也就是通常我们所说的Java编译后的字节码文件)加载到内存,然后对数据进行验证、准备、解析和初始化处理后直接可被JVM直接使用的这个过程叫做类加载机制。

类从class文件加载到内存,然后从内存再卸载掉,整个类的生命周期可分为以下7个阶段:加载验证准备解析初始化使用卸载,其中验证、准备和解析可以统称为连接,整个类的加载过程必须严格按照这个顺序进行开始执行,而执行的过程则可以进行交叉进行。

什么时候类要进行初始化呢?虚拟机规范里面严格的规定了有且仅有五种情况要对类进行初始化:

1.遇到new、getstatic、putstatic和invokestatic这4条字节码指令的时候,如果类没有进行初始化,则必须进行初始化。具体场景可以总结为:使用new实例化对象的时候,读取或者设置一个类的静态字段(final修饰放在常量池的静态字段除外)的时候,调用类的静态方法的时候。

2.通过反射机制对类进行调用的时候,如果类没有进行初始化,则必须进行初始化。

3.初始化一个类的时候,其父类还没有进行初始化,则必须进行初始化。

4.虚拟机启动时,带有main方法的主类会被进行初始化。

5.jdk1.7后,一个java.lang.invoke.MethodHandle视力最后的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄所对应的类没有进行初始化,则必须进行初始化。

除此之外其他引用都被称之为被动引用,并不会触发类的初始化操作。

接口和类初始化的区别:

类在进行初始化的时候,需要其父类全部已经初始化完毕,而接口在初始化的时候,只有在使用到父接口的时候,比如引用到了父接口中定义的变量才会进行初始化,并不要求父接口全部进行初始化。

加载:

聊完了类加载的时机,开始正式聊一下类的加载过程,从加载开始谈起,加载阶段,虚拟机需要完成3项工作:

1.通过一个类的权限定名字来获取定义此类的二进制字节流。

比如说从zip包中读取,常见的jar包,从网络中获取,已经过时的Applet,运行时计算产生,动态代理技术,从其他文件生成,jsp其实就是一个servlet,从数据库中获取,中间件服务器,程序安装到服务器在集群间分发。

2.将此而进行字节流对应的静态数据结构转化为方法区的运行时数据结构。

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

另外类的加载阶段分两部分来讨论,分为非数组类的加载和数组类的加载,非数组类的加载阶段既可以使用系统提供的类加载器来完成,又可以用户自定义类加载器来完成,开发者可以通过自定义类加载器重写loadClass方法去控制字节流的获取方式,因此,此阶段开发人员的控制力是最强的。数组类是由Java虚拟机直接创建的,不需要通过类加载器来加载,但是数组类的元素的类型,还是要通过类加载器来进行加载。

数组类的加载过程遵循的原则:

1.组件类型(数组去掉维度后的类型)是引用类型,递归加载这个组件类型,数组将在加载该组件类型的类加载器的类名称空间上被唯一标识。

2.组件类型不是引用 类型,Java虚拟机标记数组与引导类加载器相关。

3.数组类的可见性和它的组件类型可见性保持一致,组件类型不是引用类型,默认的可见性为public。

验证:

验证的目的何在?验证主要是为了确保class文件的字节流的信息符合当前虚拟机的要求,并不会危害虚拟机的自身安全。我们知道通过一个类的全限定名来获取定义此类的二进制字节流的方式是不唯一的,如果我们不利用java源码来产生class文件,而通过其他手段诸如16进制编辑器直接编写生成class文件的话,那如果被虚拟机执行,后果是不可猜想的,虚拟机很有可能会导致崩溃等一系列严重后果,所以,验证的重要性有多么的重要。

验证的4个阶段:

1.文件格式验证:是否以魔数0xCAFEBABE开头,主次版本号是否在当前虚拟机处理范围内,常量池的常量中是否有不被支持的常量类型,指向常量的各种索引值中是否有指向不存在的常量或不符合类型的常量等等很多很多的验证项。

文件格式验证的目的:保证输入的字节流能够正确的解析并且存储到方法区之内,在格式上符合一个Java类型信息描述的要求。

2,元数据验证:这个类是否有父类(Object没有父类),这个类是否继承了不允许被继承的类(final修饰),这个类如果不是抽象类,是否实现了抽象父类或者接口中要实现的所有方法,类中字段、方法是否与父类矛盾(final修饰的字段,方法,非法的方法重载)。

元数据验证的目的:对字节码描述的信息进行语义分析,保证描述的信息符合Java语言规范。

3.字节码验证:保证任意时间操作数栈的数据类型与指令代码序列能配合工作,比如在操作数栈放置了一个int类型的数据,使用时按double类型加载到本地变量表,保证跳转指令不会跳转到方法体之外,保证方法体中的类型转换时有效的。

字节码验证的目的:通过数据流和控制流进行分析,确定程序语义是合法的、符合逻辑的。

4.符号引用验证(解析阶段进行):符号引用中通过字符串描述的全限定名是否能找到对应的类,制定类中是否存在符合方法的字段描述符及简单名称所描述的方法和字段,符合引用中的类、方法、字段的访问性是否能被当前类访问。

符号引用的目的:确保解析动作能正常执行。

验证阶段是类加载机制顺利进行的一个重要保障,如果你足够自信你的代码不需要验证,可通过-Xverify:none跳过验证阶段,缩短Java虚拟机的类加载的时间。

准备:

准备阶段为类变量(static修饰的变量)在方法区上分配内存并设置类变量的初始值的阶段。
上面有一个细节需要指明:此处的类变量注意是static修饰的变量,非static修饰的变量和static修饰的常量分别是在Java的堆上和常量池中进行分配的。初始化的过程其实是初始化的是数据类型的零值,比如public static int a = 1,第一个过程是a设置为0,这就是你准备阶段设置的初始值,而a被赋值为1,是在类构造器<clinit>(区别于实例构造器<init>)中完成的。
解析:
解析阶段是虚拟机把常量池内的符号引用替换为直接引用的过程。

神马是符号引用?神马又是直接引用?



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值