jvm类加载和解析过程(总结性质)

类加载机制的定义:

把class文件加载到内存,对数据进行校验解析和初始化最终形成虚拟机直接使用的java类型。

具体来说:
1 通过一个类的全限定名来获取定义此类的二进制字节流。
2 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构(jdk7 中就是基于InstanceKlass类的描述链)
3 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。

类的加载的时机:
虚拟机规范严格规定了如下5种情况的时候才去加载。

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的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则会先触发其初始化。

几个概念:
全限定名:就是完整类名把.改为/。
描述符:字段的类型,方法的返回类型和参数列表(参数列表又包含每个参数的类型)。
符号引用:就是以一组字面量来描述所引用的目标。是相对于直接引用的。符号引用包括三类常量,分别是类和接口的全限定名,字段的名称和描述符,方法的名称和描述符。

类加载过程中的 加载和解析过程:
加载其实和解析过程是交叉进行的。
1、加载:
先通过类的全限定名来获取该类,然后把一个类的class文件字节流变为运行时的特定的数据结构,这个数据结构是jvm特有的实现,比如 HotSpot 中使用InstanceKlass,这个类记录了类的元数据信息。 创建一个对象后,对象头中的klass字段会指向这个VM内部用来记录类的元数据用的InstanceKlass对象。

2、解析:
解析是将符号引用替换为直接引用,解析动作针对类或接口,字段,类或接口的方法进行解析。
首先是用类加载器加载这个类。在加载的过程中逐步解析类中的字段和方法。
符号引用是以字面量的实形式明确定义在常量池中,直接引用是指向目标的指针,或者相对偏移量。

类的解析:
类的解析是将一个类的符号引用变为指向InstanceKlass对象的直接指针。指向这个对象的开头, 当创建对象的时候,这个指针会赋值给对象头中_kclass指针。 这样就定位到了该类的数据。访问类的元数据信息,是通过描述该类的类的对象实现的,当然 每个类只对应一个InstanceKlass对象。这就是类本身如何被描述的内存形态。

因为对象内部的数据在内存中的连续堆放的,当你访问一个类的某字段,是需要通过元数据InstanceKlass对象 来记录这个字段的与对象头的偏移量来获取。 当然调用对象的方法是定位到虚方法表 而不是定位到对象的内存区域
创建对象其实就是仅仅向一块内存区域写入与类元数据对应的各种字段的值,当然对象类型的值是一个引用,访问一个对象的字段的值, 是通过定位这个字段在这个对象起始地址的相对偏移量。确定相对偏移量就是在字段解析阶段完成的。

字段的解析:
字段的解析是确定一个对象的字段的访问地址,是计算相对对象起始地址的偏移量。
当第一次用getfield指令访问一个字段,字段的fieldref常量会最终解析成偏移量信息,放入cpc中,然后指令会被修改成fast_bgetfield来避免重复解析而是直接使用偏移量 以使用正确的类型 来访问字段。

方法的解析:
这里单独从元组jvm来分析 就是生成一个描述元数据的methodtable结构体。类似虚方法表。每一个类加载后,会对应一个虚方法表。
当第一次调用方法时,也就是执行invokevirtual指令,指令参数为 该方法的符号引用(包含了参数个数和类型信息,返回值类型,这样就区分了方法重载是不同的方法),也就是对应找到常量表中的methodref类型的项。(class文件中不同类型的项都有标记来标识,从而能够描述并得到这个的项的内部结构 而取到对应的值)。
解析methodref类型的项, 解析的过程通过该项的class_index项找到类信息,通过name_and_type_index项找到方法名和方法描述符,然后在ClassClass对象(类的元数据信息)中找到虚方法表,根据方法描述符找到对应指向匹配方法的下标,该下标指向methodblock*指针,也就是对应的方法内存地址入口,然后把虚方法表的下标和参数个数 写回到该类型为methodref的常量池项 比如是第二项#2。来取代之前的符号引用。也就是说符号引用变成了虚方法表的下标。这个下标就是一种直接引用的体现。
类的直接引用--> ClassClass--> methodtable - 下标 -> methodblock结构体(ClassClass)
第二次调用方法,这时候invokevirtual指令会变成invokevirtual_quick, 该指令的参数为虚方法表的下标(vtable index)和 方法的参数个数。 所以调用方法并不是直接调用方法块,而是先找到虚方法表,再去根据下标调用对应的方法块。
弄清类的加载机制必须知道class文件结构。

class文件结构简单介绍:
class文件结构 对应一个类或者接口定义的信息。它的格式是被约定好的。

两个概念:
字面量类似于字符串和常量的值。符号引用包括三类常量,分别是类和接口的全限定名,字段的名称和描述符,方法的名称和描述符。

常量池项类型:
常量有14种类型。 有些是字面量,有些有结构的常量类型 也叫表类型,表类型就是组合常量池中的其他字面量组合而成的。不同类型的项都有标记tag来标识,通过tag从而能够解析并得到这个的项的内部结构 而取到正确的值。
句柄和引用,句柄就类似于引用,表示的都是内存地址,当有多层应用时候会把其中的一层引用叫做句柄。

方法表中的属性:
code属性有几个比较重要,最大操作数栈。局部变量表的总存储空间,字节码指令长度,code属性用于描述代码,而其他所有
都是来描述元数据。所以也是字节码执行引擎内容的基础。
本地变量表属性,是用来描述局部变量表的变量与代码中定义的变量之间的关系。
每个方法至少有一个this的隐式的参数来指向调用该方法所属的对象。
(部分参考 RednaxelaFX在知乎上相关回答。部分参考深入理解java虚拟机一书)


评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值