深入理解JAVA虚拟机学习笔记24——类加载的准备和解析
每天进步一点点!
今天我们一起看一下类加载的准备阶段和解析阶段。
先看一下准备阶段:主要任务是在方法区中为类变量(仅static修饰变量,不包含实例变量)分配内存并设置类变量初始化的阶段。
这里面的区别,我们通过下面的代码来简单了解一下。
我们将上面的代码编译好后,通过字节码工具看一下其中的信息。
首先,从上图可以看出,被final修饰的b是直接赋值的。
我们再打开classlib,如下图所示,b对应的是一个ConstantValue常量,而不是一个引用,对应的,在准备阶段,虚拟机就会将常量值2赋给b。
而对于只有static修饰的变量a,在准备阶段,将初始化为0。如下图所示,在执行构造器方法clinit()的时候才会把把1复制给a。
到了变量c这里,则又不一样。它在准备阶段是不会进行任何操作的。如下图所示,到了对象构造方法init()中,才会把值3赋给c。
类的普通变量在对象初始化的时候随着对象分配到Java堆中。
下面再看一下解析阶段:虚拟机的主要任务是将常量池内的符号引用替换为直接引用。
前面也已经提过了引用的概念,这里让我们再复习一下(以下概念引自《深入理解Java虚拟机》)。
符号引用(Symbolic References):以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可。
直接引用(Direct References):直接引用可以是直接指向目标的指针、相对偏移量或是一个能直接定位到目标的句柄。
另外,符号引用的的目标不一定已经加载到内存中,而直接引用的目标必然已经加载到内存中。
以上是对这两种引用的定义,笔者谈一下自己的理解。
对于虚拟机能直接使用的类型,比如基本数据类型的char,int,long,double等定义的变量引用,都是直接引用,比如定义变量int a = 1。
而当变量的引用指向一个类的时候,比如定义Test test = new Test(),jvm只会存储这个类对应的符号com.xkx.test.Test,而不会存储具体的类信息,这就是符号引用。
掌握了引用的概念之后,其实解析就比较简单了,下面简单地了解一下。
需要解析的内容包含以下四个部分:
1、类或接口的解析
2、字段解析
3、类方法解析
4、接口方法解析
以类或接口的解析为例,包含以下三个步骤:
1. 如果该符号引用不是一个数组类型,那么虚拟机将会把该符号代表的全限定名称传递给调用这个符号引用的类。这个过程由于涉及验证过程所以可能会触发其他相关类的加载。
2. 如果该符号引用是一个数组类型,并且该数组的元素类型是对象。我们知道符号引用是存在方法区的常量池中的,该符号引用的描述符会类似”[java/lang/Integer”的形式,将会按照上面的规则进行加载,虚拟机将会生成一个代表此数组对象的直接引用。
3. 如果上面的步骤都没有出现异常,那么该符号引用已经在虚拟机中产生了一个直接引用,但是在解析完成之前需要对符号引用进行验证,主要是确认当前调用这个符号引用的类是否具有访问权限,如果没有访问权限将抛出java.lang.IllegalAccess异常。
剩下的部分解析步骤和类与接口的解析步骤大同小异,这里就不再赘述了,有兴趣的可以自行百度一下。
另外,有一点需要提一下,除invokedynamic指令以外,虚拟机会对第一次解析结果进行缓存,记录解析状态,避免重复解析。
今天的学习就到这里了,笔者认为初始化阶段是比较贴近于实际的内容,下一篇将单独整理出来和大家来分享。
喜欢文章或想一起学习的朋友可以关注我,给我点赞,我将会持续更新,有什么疑问或文中有不当之处请给我留言,真诚地希望能与大家一起交流探讨,学习进步。