一个Java应用程序在运行时,Java虚拟机内部保存了一个运行时常量池,它不同于class文件的常量池,是class文件的常量池被映射到虚拟机数据结构中。关于class文件常量池的部分,可以参考之前的博文实例探索Class文件。1.CONSTANT_Class条目分析数组类的符号分析比较特殊。如果是基本类型的数组,那么虚拟机会新建一个基本类型的数组类,并创建一个Class实例来表示该类型,数组类的定义类加载器就是启动类加载器。如果是引用类型的数组,那么会先解析引用类型,数组类的定义类加载器就是引用类型的定义类加载器。非数组类和接口的解析会经过以下步骤:(1).加载类型及其所有超类型。如果该类型之前已经加载到虚拟机的当前命名空间中,那么直接使用加载的类型即可,否则将由引用的原始类的初始类加载器加载。加载目标类型的超类必须在加载当前类型的基础上进行,因为只有加载当前类型后,才能从class文件的super_class字段中找到其直接超类的符号引用,然后递归解析并加载到java.lang.Object类。在递归返回的过程中,会检查interfaces字段,看看实现或扩展了哪些接口,并再次递归遍历接口的符号引用。(2).检查访问权限之后是目标类型的连接和初始化,以便该类型可以正常使用。如前所述,目标类型的初始化需要它的所有超类都被初始化(超接口不是必需的),并且由于它的超类已经被加载,所以不再需要依赖对象类从这个类的解析顺序,而是从Object类到类的初始化。该类型的连接和初始化步骤如下:(3).类型验证(4)。类型准备(5)。类型解析(可以推迟)注意这个过程是对被引用类型及其超类的符号引用的解析,因为被引用类型的一些符号引用不会立即被使用,所以这一步之前有一个符号引用的过程分辨率严格属于启动引用的类型。仅当被引用类型的这些符号引用所指向的类型被主动使用时,被引用类型的这些符号引用才会被解析、加载、链接和初始化。(6).类型初始化2.CONSTANT_Fieldref入口分析由于一个类型不包含其超类型定义的字段,因此查找目标字段会从该字段指向的类型开始,从该类型开始查找,然后递归查找其实现或扩展接口,然后递归查找其父类,直到找到目标字段,并将运行时常量池的字段入口标记为已解析,并将常量池的数据更改为该字段的直接引用。3.CONSTANT_Methodrefentry的解析与fieldsearch相似但不同。搜索顺序会从类型开始,然后递归搜索它的超类,再递归搜索它实现或扩展的接口。4.CONSTANT_InterfaceMethodRef入口解析查找接口方法是从要分析的接口开始,递归查找到它的超接口。5、CONSTANT_String表项分析Java虚拟机将字符串作为字符串对象进行维护,虚拟机维护的是一个字符串池,里面包含了所有“被扣留”的字符串对象的引用。分析CONSTANT_String常量池,首先需要检查字符串对象在字符串池中的引用是否存在。如果存在,则常量池数据直接解析为字符串对象的引用。如果不存在,那么就需要根据这个字符。字符串序列创建一个字符串对象,将其引用添加到字符串池,并将常量池数据解析为该引用。也可以使用String对象的intern对象扣留一个字符串(注意不是字符串对象)。如果字符串池中有对字符串序列对象的引用,那么直接返回引用即可,否则会被扣留该字符串,但注意扣留返回的字符串对象引用不会指向原来的String对象,因为原始String对象位于Java堆中,而字符串池中的对象是由虚拟机创建和维护的。packagecom.ice.intern;publicclassInternTest{publicstaticvoidmain(Stringargs[]){Stringa=newString("123");Stringb=a;Stringc=newString("123");;System.out.println("beforeintern:");System.out.println("a=b?:"+(a==b));System.out.println("a=c?:"+(a==c));a=a.intern();c=c.intern();System.out.println("afterintern:");System.out.println("a=b?:"+(a==b));System.out.println("a=c?:"+(a==c));}}结果如下:(6).其他类型(基本数据类型)入口分析可以直接使用常量池中包含的常量值6.直接引用常量池解析最终用直接引用代替符号引用。对类型、类变量和类方法的直接引用可能是方法区中的指针。而对实例变量和实例方法的直接引用是从对象映像的开头到该实例变量或方法表的偏移量。实例变量的组织方式是:从Object类到实例的类型,在类中声明的实例变量按照在类文件中出现的先后顺序放在对象映像中。实例方法的组织方式比较类似:从Object类到实例的类型,类中声明的实例方法指针按照在class文件中出现的先后顺序放在对象映像中。但是被重写的方法会出现在超类的相应位置(方法第一次出现的位置)。但是,访问接口方法不能简单地通过方法表的偏移量来访问,必须查找对象所属类的方法表才能找到方法。例如Factory接口的Produce()方法分别由A和B实现,但是由于不能保证A和B派生自同一个实现Factory接口的超类,即它们具有相同的produce()方法偏移量,那么它们就不能通过方法表偏移量来访问工厂的produce()方法。7.加载约束对于一个类型指向另一个类型的符号引用,如果被引用类型和被引用类型不是由同一个初始加载器加载的(可能由用户自定义的ClassLoader实现),那么虚拟机必须确保它被引用的类型在命名空间之间是一致的。这样通过自定义ClassLoader加载不可信类型后,在解析引用类型的符号引用时,就不会把可信类型当作已经解析过的不可信类型(因为对方法的符号引用只能被许可命名。和描述符,不知道也不能知道它的初始类加载器),因此调用不受信任类型的方法来访问受信任类型的受保护成员。
