JVM 符号引用和直接引用

10,269 阅读3分钟

数据格式

在说具体的内容之前,我们需要先理解一个概念,数据格式。

所谓数据格式,就是数据按照约定好的格式写。

比如:

字节码

字节码的数据格式就是严格规定好,前0-3字节 是魔数,4-5字节 次版本号,6-7版本号,8-9常量池数量等。

为什么0-3一定是魔术,4-7是版本号,因为这就是约定好的,你按照这个格式写,我按照这个格式读。

JSON

{} 代表对象,[]代表数组,为什么,因为我们就这么约定。

开发

我们开发中可能约定, I 开头代表接口, impl结尾 代表实现类,等等。都是一种约定好的规则。

符号引用和直接引用

  1. 符号引用:字符串,能根据这个字符串定位到指定的数据,比如java/lang/StringBuilder
  2. 直接引用:内存地址

这里说一下我的理解

首先:JVM 和 class 都有属于自己的数据结构

其次:class文件在被加载到jvm内存中时候,JVM根据class文件的数据结构规则,
拆分读取class文件,然后把对应的数据放入JVM中

参考:

  • 周志明老师--《深入理解Java虚拟机》,P215,倒数第六行

加载阶段完成后,虚拟机外部的二进制字节流就按照虚拟机所需的格式存储在方法区之中。

  • 知乎 R大的文章

知乎传送门

理解:

class字节码的数据结构从前至后,包括 魔/版本/常量池/访问标志/类索引/父类索引/接口索引/字段表/方法表/属性表

而常量池包括:

  1. 字面量

  2. 符号引用

    • 类和接口的全限定名
    • 字段名称和描述符
    • 方法名称和描述符
类加载之后,常量池的内容会进入运行时常量池,这时候里面的数据也许还保持着符号引用。
(因为解析的时机由JVM自己设定)

如果在虚拟机栈的 栈帧中,我准备调用 main() 函数,那么会通过栈帧中持有的动态连接,找到运行时常量池,
然后找到main函数的常量 比如 #2 ,如果这个常量没有被解析过,那么就通过这个常量进行解析过程,
其中包括,通过常量 找到 类名 和 nameAndType,通过 nameAndType 找到方法名和返回值。

这时候 我手里有 类名/方法名/方法返回值,下一步,我通过类名和方法名,通过JVM记录的方法列表,找到对应的方法体。

而这个方法体实际上是一段内存地址,那么这时候我就把这段内存地址复制给 #2,并且给 #2设定一个已经解析的 flag。

这样就完成了 符号引用到直接引用的过程。

说回虚拟机加载

虚拟机加载经历 加载--验证--准备--解析--初始化--使用--卸载,《深入理解Java虚拟机》-周志明,书中说,加载过程中也可以验证,我觉得没问题,比如,我读取了class的前四个字段,加载进来了,然后就直接验证,看看魔数对不对,不对我就不再加载。这个很OK。

比如魔数对了,那么我再加载四个字节,然后验证,看看版本号能不能被JVM解释,不能就报错,也很OK。