典型的一个类中,主要是static字段,类字段,static方法,类方法这四种存在。
至于static字段和类字段的初始化赋值语句,看似有点特别,其实在编译后归入

方法符号引用

在class文件中,对于方法调用时是符号引用,并没有解析为方法地址。静态方法和对象方法的符号规则并没有区别,他们的区别在于调用的操作指令,一个是invokestatic,另一个是invokevirtual。后者在调用指令之前会把this对象压入操作数栈。在解析类方法或者接口方法符号的时候,符号中包含类名,但是为了实现多态应该不是根据这个类名,而是查找this所指对象的Class对象定位到的符号表,根据符号所包含类名找到对应方法表,找到特定方法签名,如果找不到则递归寻找父类的方法表,还有更详细的安全检查,接口方法也是根据this去查找,找不到时也会做更多检查。

如果是invokestatic的符号,那么只会根据符号中的类名知道的Class查找方法表。

类或者接口的符号引用

很奇怪对于代码
#62 = Class #63 // java/util/Set
#63 = Utf8 java/util/Set
……
29: ldc #62 // class java/util/Set
31: astore 5
33: aload 5
35: invokevirtual #33 // Method java/lang/Class.getName:()Ljava/lang/String;
offset=29的指令ldc只是把#62所代表的字符串常量压栈(操作数栈,后文不加说明皆是此意)
offset=31的指令astore只是把栈顶弹出保存到index=5的局部变量表
offset=33就是在载入变量压栈
offset=35的指令是调用虚方法了,此时栈顶代表的含义必须是对象方法所需的this参数,但是此时栈顶貌似只是字符串常量啊?
问题就在这里,从字节码看起来局部变量保存的也是字符串常量,也不符合源码中变量类型是Class对象引用。实际上这里JVM悄悄做了符号解析,JLS没有规定JVM必须在加载字节码时对常量池中符号进行解析或者执行到相关字节码时进行解析。但是真正执行到时,必须是符号已经被解析。
====
题外话,这样也说明Class cls = String.class这样的语句,cls变量其实是解析出来的地址。
而Class clssss = obj.getClass()是调用Java方法,传递真实引用到局部变量。
====
说到常量池,主要是因为多个class中的常量池会有相同的字符串,因此,设计为不可变,合并到JVM统一的常量池有利于节省空间。

字段符号解析

     9: sipush        888
    12: putfield      #17                 // Field df:I

常量池索引17代表的是字段符号,看起来putfield指令直接把栈顶的数字888保存到了#17的常量池,其实当然不可能,putfield的真实含义就是保存到一个field,字段的符号引用在执行之前已经被解析为字段地址。如果是putstatic,那么JVM可以根据类的字段变量表解析出字段符号,如果是对象字段,也可以。

本文希望是,理解JVM解析符号的概念,为什么要解析符号?有哪些类型的符号需要解析?JVM解析符号的大致过程是什么?

10-11 09:30