类加载过程可以分为三个阶段
下面这图是类的生命周期图,这里只看初始化以及之前的过程
验证是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,不会威胁到jvm的安全
验证主要包括以下几个方面的验证:
class文件格式的验证 (验证字节流是否符合Class文件的规范,是否能被当前版本的虚拟机处理)
元数据验证 (对字节码描述的信息进行语义分析,确保符合java语言规范)
字节码验证 (通过数据流和控制流分析,确定语义是合法的,符合逻辑的)
符号引用验证 (对除了自身类,以外的各类信息进行匹配性校验,确保解析行为可以正常执行)
准备阶段是正式为类实例变量分配内存并且设置类变量初始值的阶段,这些变量所使用的内存都将在方法区中进行分配。这里的类变量指的是被static修饰的变量,不包括实例变量,实例变量将会在对象实例化的时候随着对象一起分配在java堆中。另外这里的初始值并不是代码中 =
右边的初始值而是变量数据类型对应的零值(int为0,String为null等)
如下面的例子:这里在准备阶段过后的初始值为0,而不是7
public static int a=7
解析阶段是将常量池中的符号引用替换为直接引用的过程,解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用进行,分别对应于常量池的CONSTANT_Class_info、CONSTANT_Fieldref_info、CONSTANT_Methodref_info、CONSTANT_InterfaceMethodref_info、CONSTANT_MethodType_info、CONSTANT_MethodHandle_info和CONSTANT_InvokeDynamic_info
7种常量类型
符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要可以在使用时能无任何歧义的定位到目标时即可
直接引用是指向目标的指针、相对偏移量或者是一个可以间接可以定位到目标的句柄
假设当前代码所处的类为D,如果要把一个从未解析过的符号引用N解析为名叫C的类或接口的直接引用,那虚拟机完成整个解析的过程需要以下3个步骤:
加载成功后:
类的解析是将一个类的符号引用改为指向InstanceKlass对象的直接指针(每个InstanceKlass对象表示一个具体的Java类,这个Java类不包括Java数组)。指向这个对象的开头, 当创建对象的时候,这个指针会赋值给对象头中_kclass指针。 这样就定位到了该类的数据。访问类的元数据信息,是通过描述该类的类的对象实现的,当然 每个类只对应一个InstanceKlass对象。这就是类本身如何被描述的内存形态。
因为对象内部的数据在内存中的连续堆放的,当你访问一个类的某字段,是需要通过元数据InstanceKlass对象 记录的这个字段与对象头的偏移量来获取。 当然调用对象的方法是定位到虚方法表 而不是定位到对象的内存区域
创建对象其实就是仅仅向一块内存区域写入与类元数据对应的各种字段的值,当然对象类型的值是一个引用,访问一个对象的字段的值, 是通过定位这个字段在这个对象起始地址的相对偏移量。确定相对偏移量就是在字段解析阶段完成的。
字段的解析是确定一个对象的字段的访问地址,是计算相对对象起始地址的偏移量
要解析一个未被解析过的字段符号引用,首先会解析所属的类或接口的符号引用。将这个字段所属的类或接口用C表示,如果这个类解析完成,虚拟机规范要求按照如下步骤对C进行后续字段的搜索
出错情况:
The field xx is ambiguous
类方法解析的第一个步骤与字段解析一样,也需要解析所属的类或接口的符号引用,我们依然用C表示这个类,如果解析成功:
最后,如果查找过程成功返回了直接引用,将会对这个方法进行权限验证,如果发现不具备对此方法的访问权限,将抛出java.lang.IllegalAccessError异常
接口方法解析的第一个步骤与字段解析一样,也需要解析所属的类或接口的符号引用,如果解析成功,依然用C表示这个接口,接下来虚拟机将会按照如下步骤进行后续的接口方法搜索:
与类方法解析不同,如果在接口方法表中发现class_index中的索引C是个类而不是接口,那就直接抛出java.lang.IncompatibleClassChangeError异常
否则,在接口C中查找是否有简单名称和描述符都与目标相匹配的方法,如果有则返回这个方法的直接引用,查找结束
否则,在接口C的父接口中递归查找,直到java.lang.Object(查找范围会包括Object类)为止,看是否有简单名称和描述符都与目标相匹配的方法,如果有则返回这个方法的直接引用,查找结束
否则,宣告方法查找失败,抛出java.lang.NoSuchMethodError异常
由于接口中的所有方法默认都是public,所以不存在访问权限的问题,因此接口方法的符号解析应当不会抛出java.lang.IllegalAccessError异常
每一个类加载后,会对应一个虚方法表
当第一次调用方法时,也就是执行invokevirtual指令,指令参数为 该方法的符号引用(包含了参数个数和类型信息,返回值类型,这样就区分了方法重载是不同的方法),也就是对应找到常量表中的methodref类型的项。(class文件中不同类型的项都有标记来标识,从而能够描述并得到这个的项的内部结构 而取到对应的值)。
在虚方法表,根据方法描述符找到对应指向匹配方法的下标,该下标指向methodblock*指针,也就是对应的方法内存地址入口,然后把虚方法表的下标和参数个数 写回到该类型为methodref的常量池项 比如是第二项#2。来取代之前的符号引用。也就是说符号引用变成了虚方法表的下标。这个下标就是一种直接引用的体现
类的直接引用–> ClassClass–> methodtable - 下标 -> methodblock结构体(ClassClass)
第二次调用方法,这时候invokevirtual指令会变成invokevirtual_quick, 该指令的参数为虚方法表的下标(vtable index)和 方法的参数个数。 所以调用方法并不是直接调用方法块,而是先找到虚方法表,再去根据下标调用对应的方法块
初始化阶段就是执行类构造器法<clinit>()的过程
<clinit>()方法是由编译器自动收集类中的所有类变量(静态变量)的赋值动作和静态语句块(static{})中的语句合并产生的。编译器收集的顺序是由语句在源文件中出现的顺序决定的,静态语句块只能访问到定义在静态语句块之前的变量,定义在它之后的变量,静态语句块能进行赋值操作,但是不能进行访问
2021级《合肥工业大学计算机》线上复试经验分享 1.考前准备 1.1个人说明 1.2线上...
鸽了好久,终于有个时间继续写了,继上一篇之后,又写(水)了一篇,有什么不足...
全面了解html.css溢出 XML/HTML Code 复制内容到剪贴板 !DOCTYPEhtml !DOCTYPEht...
第一种:传统的ajax异步请求,后台代码以及效果在最下边 首先我们在eclipse中创...
问题# 在开发微信支付的小微商户进件接口时,需要通过表单来上传身份证图片等数...
serialize() 方法通过序列化表单值,创建 URL 编码文本字符串。代替了一个一个传...
用isset()和empty()判断下面的变量。 $str = ''; $int = 0 ; $arr = array(); is...
市面上有很多自动化工具。我可以举几个例子,例如 Puppet、Chef、CFEngine、Fore...
环境:linux mint 20,一切都是最新的版本。 都知道,PHPSTORM破解和运行都是离不...
一、添加SVN (1)添加svn插件 (2)安装的svn最好是默认路径安装的,不要问我为...