JVM学习笔记(四)之类加载机制

来源:互联网 发布:ios10下载bt软件 编辑:程序博客网 时间:2024/06/11 00:10

一、    Class文件

Class文件是一组以8个字节为基础单位的二进制流;

Class文件格式:无符号数和表。无符号数用来描述数字、索引引用、数量值或按照UTF-8编码构成字符串值。由多个无符号数或者其他表构成的复合数据类型,class文件本质上就是一张表。

Class的结构没有任何分隔符,无论是顺序还是数量,甚至是数据存储的字节序(Big-Endian)都是被严格规定好的。

Java重载(override):和原方法的方法名一样并且必须有一个与原方法不同的特征签名。特征签名是一个方法中的各个参数在常量池中的字段符号引用的集合。所以不能仅仅根据返回值类型不同来对一个方法进行重载。

二、    类加载机制

类从加载到卸载经过7个过程:加载、验证、准备、解析、初始化、使用、卸载。其中解析在某些情况下也可以在初始化之后(支持java动态绑定)。

有且只有在以下五种情况下,必须对类进行初始化:

1、 new、getStatic(读取类静态字段)、putStatic(设置类的静态字段)、invokeStatic(调用类的静态方法);

2、 使用java.lang.reflect包的方法对类进行反射调用的时候;

3、 初始化子类时,父类未初始化;

4、 执行主类时(main方法所在的函数)

5、 使用JDK1.7动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果为REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,且这个方法的句柄所对应的类未初始化。

除了这5种之外的所有引用类的方式都不会触发初始化(被动引用),例如:

1)      对于调用静态字段,只有直接定义这个字段的类才会被初始化。如通过子类来引用父类中定义的静态字段,只会触发父类的初始化而不会初始化子类;

2)      引用类的常量时,常量在编译阶段会存入调用类的常量池中,本质上并没有直接引用到定义常量的类,因此不会触发定义常量的类的初始化;

3)      通过数组定义来引用类,不会触发此类的初始化。例如定义了App类,在初始化App[] app = new App[]不会初始化App类。

 

加载

加载主要完成三件事:

1、 通过一个类的全限定名来获取定义此类的二进制字节流;(从zip包读取,从网络中获取(Applet),由jsp文件生成对应的Class类)

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

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

加载阶段完成后,虚拟机外部的二进制字节流就按照虚拟机所需的格式存储在方法区,方法区的数据存储格式有虚拟机实现自行定义,虚拟机规范并未规定此区域的具体数据结构。然后在内存中实例化一个java.lang.Class类的对象。对象并不一定保存在堆中,像Class对象,保存在方法区中。

验证

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

1、 文件格式验证:保证输入的字节流能正确地解析并存储于方法区之内

2、 元数据验证:(针对数据类型)对字节码描述的信息进行语义分析,保证其描述的信息符合java语言规范的要求

3、 字节码验证:(针对方法体)通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。主要是对类的方法体进行校验分析,保证被校验类的方法在运行时不会做出危害虚拟机安全的事件。

4、 符号引用验证:确保解析动作能正常执行。

准备

正式为类变量分配内存并设置类变量初始值的阶段,都是“零值”。这些变量所使用的内存都将在方法区中进行分配。这里所进行的内存分配仅包括类变量,不包括实例变量(实例变量将在对象实例化时随对象一起被分配在java堆中)。但是如果类字段的字段属性表中存在ConstantValue属性,即常量,那么准备阶段变量的value值就会被初始化为ConstantValue属性所指定的值。例如public staticfinal intvalue = 123;此时value初始值就已经为123。

解析

解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。

符号引用:能无歧义的定位到目标的一组符号。它与虚拟机实现的内存布局无关,引用的目标不一定已经加载在内存中。

直接引用:可以直接指向目标、相对偏移量或是一个能间接定位到目标的句柄,它和虚拟机实现的内存分布是相关的,如果有了直接引用,那么引用的目标必定已经存在于内存中。

解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符。

初始化

初始化阶段,根据程序员制定的主观计划去初始化类变量和其他成员。话句话就是说:初始化阶段是执行类构造器<clinit>()方法的过程。虚拟机会保证在子类的<clinit>()方法执行之前,父类的<clinit>()方法已经执行完毕,意味着父类中定义的静态语句块要优先于子类中的变量赋值操作。但是执行接口的<clinit>()方法不需要先执行父类接口的<clinit>()方法,而且接口的实现类在初始化时也一样不会执行接口的<clinit>()方法。


ps:关于<clinit>()的补充:类变量的赋值和静态语句块的执行

1 0
原创粉丝点击