深入理解Java Class文件格式(六)

写在前面

前文讲解了常量池中的7种数据项, 它们分别是:

CONSTANT_Utf8_info
CONSTANT_NameAndType_info
CONSTANT_Integer_info
CONSTANT_Float_info
CONSTANT_Long_info
CONSTANT_Double_info
CONSTANT_String_info

8)、CONSTANT_Class_info

        常量池中的一个 CONSTANT_Class_info,可以看做是CONSTANT_Class数据类型的一个实例。他是对类或者接口的符号引用。它描述的可以是当前类型的信息,也可以描述对当前类的引用,还可以描述对其他类的引用。也就是说,如果访问了一个类字段或者调用了一个类的方法, 对这些字段或方法的符号引用, 必须包含它们所在的类型的信息, CONSTANT_Class_info就是对字段或方法符号引用中类型信息的描述。 
         CONSTANT_Class_info的第一个字节是tag值为7,也就是当虚拟机访问到一个常量池中的数据项,如果发现tag值为7,就可判断这是一个CONSTANT_Class_info。

        tag下面的两个字节一个叫做name_index的索引值,它指向一个 CONSTANT_Utf8_info, 这个CONSTANT_Utf8_info中存储了CONSTANT_Class_info要描述的类型的全限定名

        此外要说明的是, java中数组变量也是对象, 那么数组也就有相应的类型, 并且数组的类型也是使用CONSTANT_Class_info描述的, 并且数组类型和普通类型的描述有些区别。 普通类型的CONSTANT_Class_info中存储的是全限定名, 而数组类型对应的CONSTANT_Class_info中存储的是数组类型相对应的描述符字符串。

举例说明:

与Object类型对应的CONSTANT_Class_info中存储的是: java/lang/Object 
与Object[]类型对应的CONSTANT_Class_info中存储的是: [Ljava/lang/Object; 
下面看CONSTANT_Class_info的存储布局

例如, 如果在一个类中引用了System这个类, 那么就会在这个类的常量池中出现以下信息:

 9)、CONSTANT_Fieldref_info

        常量池中的一个 CONSTANT_Fieldref_info , 可以看做是CONSTANT_Field数据类型的一个实例。该数据项表示对一个字段的符号引用,可以是对本类中的字段的符号引用,也可以是对其他类中的字段的符号引用,可以是对成员变量字段的符号引用,也可以是对静态变量的符号引用

其中ref三个字母就是reference的简写。

下面分析CONSTANT_Fieldref_info中的内容都存放了什么信息。 

        和其他类型的常量池数据项一样, 它的第一个字节也必然是tag, 它的tag值为9。也就是说, 当虚拟机访问到一个常量池中的一项数据, 如果发现这个数据的tag值为9, 就可以确定这个被访问的数据项是一个CONSTANT_Fieldref_info, 并且知道这个数据项表示对一个字段的符号引用。 

        tag值下面的两个字节一个叫做class_index的索引值,它指向一个CONSTANT_Class_info数据项,这个数据项表示被引用的字段所在的类型包括接口。 所以说CONSTANT_Class_info可以作为字段符号引用的一部分。 

        class_index以下的两个字节一个叫做name_and_type_index的索引, 它指向一个CONSTANT_NameAndType_info,这个CONSTANT_NameAndType_info描述的是被引用的字段的名称和描述符。CONSTANT_NameAndType_info可以作为字段符号引用的一部分。

        CONSTANT_Fieldref_info就是对一个字段的符号引用, 这个符号引用包括两部分, 一部分是该字段所在的类, 另一部分是该字段的字段名和描述符,这就是所谓的 “对字段的符号引用” 。

下面结合实际代码来说明, 代码如下:


package com.jg.zhang;
public class TestInt {
    int a = 10;
    void print(){
        System.out.println(a);
    }
}

        在print方法中, 引用了本类中的字段a。在class文件中, 对字段a的引用是如何描述的呢? 下面我们将这段代码使用javap反编译, 给出简化后的反编译结果:

Constant pool:
   #1 = Class              #2             //  com/jg/zhang/TestInt
   #2 = Utf8               com/jg/zhang/TestInt

   ......

   #5 = Utf8               a
   #6 = Utf8               I

   ......

  #12 = Fieldref           #1.#13         //  com/jg/zhang/TestInt.a:I
  #13 = NameAndType        #5:#6          //  a:I

  ......

{

  void print();
    flags:
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #19                 // Field java/lang/System.out:Ljava/io/PrintStream;
         3: aload_0
         4: getfield      #12                 // Field a:I
         7: invokevirtual #25                 // Method java/io/PrintStream.println:(I)V
        10: return
}

         print方法的位置为4的字节码指令getfield引用了索引为12的常量池数据项,常量池中索引为12的数据项是一个CONSTANT_Fieldref_info,这个CONSTANT_Fieldref_info又引用了索引为1和13的两个数据项,索引为1的数据项是一个CONSTANT_Class_info, 这个CONSTANT_Class_info数据项又引用了索引为2的数据项, 索引为2的数据项是一个CONSTANT_Utf8_info,他存储了字段a所在的类的全限定名com/jg/zhang/TestInt 。 而CONSTANT_Fieldref_info所引用的索引为13的数据项是一个CONSTANT_NameAndType_info, 它又引用了两个数据项, 分别为第5项和第6项, 这是两个CONSTANT_Utf8_info , 分别存储了字段a的字段名a, 和字段a的描述符I 。 

        下面内存布局图,ONSTANT_Fieldref_info引用了CONSTANT_Class_info和CONSTANT_NameAndType_info, CONSTANT_Class_info又引用了一个CONSTANT_Utf8_info , 而CONSTANT_NameAndType_info又引用了两个CONSTANT_Utf8_info 。 

10)、CONSTANT_Methodref_info

        常量池中的一个 CONSTANT_Methodref_info , 可以看做是CONSTANT_Methodref数据类型的一个实例。 该数据项表示对一个类中方法的符号引用, 可以是对本类中的方法的符号引用, 也可以是对其他类中的方法的符号引用, 可以是对成员方法字段的符号引用, 也可以是对静态方法的符号引用,但是不会是对接口中的方法的符号引用

        其中ref三个字母就是reference的简写。

        既然是符号“引用”, 那么只有在原文件中调用了一个方法, 常量池中才有和这个被调用方法的相对应的符号引用, 即存在一个CONSTANT_Methodref_info。 如果只是在类中定义了一个方法, 但是没调用它, 则不会在常量池中出现和这个方法对应的CONSTANT_Methodref_info 。 

         它的第一个字节是tag值为10 。 也就是说当虚拟机访问到一个常量池中的一项数据, 如果发现这个数据的tag值为10, 就可以确定这个被访问的数据项是一个CONSTANT_Methodref_info, 并且知道这个数据项表示对一个方法的符号引用。 

        tag值下面的两个字节是一个叫做class_index的索引值, 它指向一个CONSTANT_Class_info数据项, 这个数据项表示被引用的方法所在的类型。 所以说, CONSTANT_Class_info可以作为方法符号引用的一部分。 

        class_index以下的两个字节是一个叫做name_and_type_index的索引, 它指向一个CONSTANT_NameAndType_info, 这个CONSTANT_NameAndType_info描述的是被引用的方法的名称和描述符。 CONSTANT_NameAndType_info可以作为方法符号引用的一部分。

         CONSTANT_Methodref_info就是对一个字段的符号引用,这个符号引用包括两部分,一部分是该方法所在的类,另一部分是该方法的方法名和描述符。 这就是 “对方法的符号引用” 。

下面结合实际代码来说明, 代码如下:

package com.jg.zhang; 
public class Programer {
 
	Computer computer;
	
	public Programer(Computer computer){
		this.computer = computer;
	}
	
	public void doWork(){
		computer.calculate();
	}
}

package com.jg.zhang;
public class Computer {
 
	public void calculate() {
		System.out.println("working...");
		
	}
}

        上面的代码包括两个类, 其中Programer类引用了Computer类, 在Programer类的doWork方法中引用(调用)了Computer类的calculate方法。反编译Programer, 看看Programer中对Computer的doWork方法的引用, 在class文件中是如何描述的。 

下面给出Programer的反编译结果, 其中省去了一些不相关的信息:

Constant pool:
.........
 
 
  #12 = Utf8               ()V
 
 
  #20 = Methodref          #21.#23        //  com/jg/zhang/Computer.calculate:()V
  #21 = Class              #22            //  com/jg/zhang/Computer
  #22 = Utf8               com/jg/zhang/Computer
  #23 = NameAndType        #24:#12        //  calculate:()V
  #24 = Utf8               calculate
 
{
 
  com.jg.zhang.Computer computer;     
    flags:
 
.........
 
  public void doWork();
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: getfield      #13                 // Field computer:Lcom/jg/zhang/Computer;
         4: invokevirtual #20                 // Method com/jg/zhang/Computer.calculate:()V
         7: return
}

        可以看到, doWork方法的位置为4的字节码指令invokevirtual引用了索引为20的常量池数据项, 常量池中索引为20的数据项是一个 CONSTANT_Methodref_info, 这个CONSTANT_Methodref_info又引用了索引为21和23的两个数据项, 索引为21的数据项是一个CONSTANT_Class_info, 这个CONSTANT_Class_info数据项又引用了索引为22的数据项, 索引为22的数据项是一个CONSTANT_Utf8_info , 他存储了被引用的Computer类中的calculate方法所在的类的全限定名com/jg/zhang/Computer 。 而CONSTANT_Methodref_info所引用的索引为23的数据项是一个CONSTANT_NameAndType_info, 它又引用了两个数据项, 分别为第24项和第12项, 这是两个CONSTANT_Utf8_info , 分别存储了被引用的方法calculate的方法名calculate, 和该方法的描述符()V 。 

下面给出内存布局图, 这个图中涉及的东西同样有点多, 因为CONSTANT_Methodref_info引用了CONSTANT_Class_info和CONSTANT_NameAndType_info, CONSTANT_Class_info又引用了一个CONSTANT_Utf8_info , 而CONSTANT_NameAndType_info又引用了两个CONSTANT_Utf8_info 。 

11)、CONSTANT_InterfaceMethodref_info

        常量池中的一个 CONSTANT_InterfaceMethodref_info , 可以看做是CONSTANT_InterfaceMethodref数据类型的一个实例。 该数据项表示对一个接口方法的符号引用, 不能是对类中的方法的符号引用。 其中ref三个字母就是reference的简写。

        既然是符号“引用”, 那么只有在原文件中调用了一个接口中的方法, 常量池中才有和这个被调用方法的相对应的符号引用, 即存在一个CONSTANT_InterfaceMethodref。 如果只是在接口中定义了一个方法, 但是没调用它, 则不会在常量池中出现和这个方法对应的CONSTANT_InterfaceMethodref 。 

         它的第一个字节也必然是tag值为11。 当虚拟机访问到一个常量池中的一项数据, 如果发现这个数据的tag值为11, 就可以确定这个被访问的数据项是一个CONSTANT_InterfaceMethodref, 并且知道这个数据项表示对一个接口中的方法的符号引用。 

        tag值下面的两个字节是一个叫做class_index的索引值, 它指向一个CONSTANT_Class_info数据项, 这个数据项表示被引用的方法所在的接口。 所以说, CONSTANT_Class_info可以作为方法符号引用的一部分。 

        class_index以下的两个字节是一个叫做name_and_type_index的索引,它指向一个CONSTANT_NameAndType_info,CONSTANT_NameAndType_info可以作为方法符号引用的一部分。

         CONSTANT_InterfaceMethodref就是对一个接口中的方法的符号引用,这个符号引用包括两部分, 一部分是该方法所在的接口,另一部分是该方法的方法名和描述符。 这就是 “对接口中的方法的符号引用”

下面结合实际代码来说明, 代码如下:


package com.jg.zhang;
 
public class Plane {
 
    IFlyable flyable;
    
    void flyToSky(){
        flyable.fly();
    }
}

package com.jg.zhang;
 
public interface IFlyable {
 
    void fly();
}

        在上面的代码中, 定义可一个类Plane, 在这个类中有一个IFlyable接口类型的字段flyable, 然后在 Plane的flyToSky方法中调用了IFlyable中的fly方法。 这就是源代码中对一个接口中的方法的引用方式, 下面我们反编译Plane, 看看在class文件层面, 对一个接口中的方法的引用是如何描述的。
下面给出反编译结果, 为了简洁期间, 省略了一些不相关的内容:

Constant pool:
.........

  #8 = Utf8               ()V

  #19 = InterfaceMethodref #20.#22        //  com/jg/zhang/IFlyable.fly:()V
  #20 = Class              #21            //  com/jg/zhang/IFlyable
  #21 = Utf8               com/jg/zhang/IFlyable
  #22 = NameAndType        #23:#8         //  fly:()V
  #23 = Utf8               fly

{

.........

  com.jg.zhang.IFlyable flyable;
    flags:

.........

  void flyToSky();
    flags:
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: getfield      #17                 // Field flyable:Lcom/jg/zhang/IFlyable;
         4: invokeinterface #19,  1           // InterfaceMethod com/jg/zhang/IFlyable.fly:()V
         9: return

}

        可以看到, flyToSky方法的位置为4的字节码指令invokeinterface引用了索引为19的常量池数据项, 常量池中索引为19的数据项是一个CONSTANT_InterfaceMethodref_info, 这个CONSTANT_InterfaceMethodref_info又引用了索引为20和22的两个数据项, 索引为20的数据项是一个CONSTANT_Class_info, 这个CONSTANT_Class_info数据项又引用了索引为21的数据项, 索引为21的数据项是一个CONSTANT_Utf8_info , 他存储了被引用的方法fly所在的接口的全限定名com/jg/zhang/IFlyable 。 而CONSTANT_InterfaceMethodref_info所引用的索引为22的数据项是一个CONSTANT_NameAndType_info, 它又引用了两个数据项, 分别为第23项和第8项, 这是两个CONSTANT_Utf8_info , 分别存储了被引用的方法fly的方法名fly, 和该方法的描述符()V 。 

        下面给出内存布局图, 这个图中涉及的东西同样有点多, 因为CONSTANT_InterfaceMethodref_info引用了CONSTANT_Class_info和CONSTANT_NameAndType_info, CONSTANT_Class_info又引用了一个CONSTANT_Utf8_info , 而CONSTANT_NameAndType_info又引用了两个CONSTANT_Utf8_info 。 


原文链接:https://blog.csdn.net/zhangjg_blog/article/details/21781021

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值