JAVA语言之深入理解Java Class文件格式(五)
Vivian 2018-06-26 来源 : 阅读 547 评论 0

摘要:本文主要向大家介绍了JAVA语言的Java Class文件格式,通过具体的内容向大家展示,希望对大家学习JAVA语言有所帮助。

本文主要向大家介绍了JAVA语言的Java Class文件格式,通过具体的内容向大家展示,希望对大家学习JAVA语言有所帮助。 

常量池中各数据项类型详解(续)

(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 Class文件格式(二) 中将结果, 不熟悉的同学可以先阅读这篇文章。  

 

此外要说明的是, 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的存储布局:

JAVA语言之深入理解Java Class文件格式(五)

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

JAVA语言之深入理解Java Class文件格式(五)

 

(9) CONSTANT_Fieldref_info

 

常量池中的一个CONSTANT_Fieldref_info, 可以看做是CONSTANT_Field数据类型的一个实例。 该数据项表示对一个字段的符号引用, 可以是对本类中的字段的符号引用, 也可以是对其他类中的字段的符号引用, 可以是对成员变量字段的符号引用, 也可以是对静态变量的符号引用, 其中ref三个字母就是reference的简写。 之前的文章中, “符号引用”这个名词出现了很多次, 可能有的同学一直不是很明白, 等介绍完CONSTANT_Fieldref_info, 就可以很清晰的了解什么是符号引用。 下面分析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前面的博客中已经解释过了, 不明白的朋友可以先看前面的博客:深入理解Java Class文件格式(三) 。 这个CONSTANT_NameAndType_info描述的是被引用的字段的名称和描述符。 我们在前面的博客中也提到过, CONSTANT_NameAndType_info可以作为字段符号引用的一部分。

 

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

 

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

 

[java] view plain copy
1. package com.jg.zhang;  
2.   
3. public class TestInt {  
4.     int a = 10;  
5.     void print(){  
6.         System.out.println(a);  
7.     }  
8. }

 


在print方法中, 引用了本类中的字段a。 代码很简单, 我们一眼就可以看到print方法中是如何引用本类中定义的字段a的。 那么在class文件中, 对字段a的引用是如何描述的呢? 下面我们将这段代码使用javap反编译, 给出简化后的反编译结果:

 

[plain] view plain copy
1. Constant pool:  
2.    #1 = Class              #2             //  com/jg/zhang/TestInt  
3.    #2 = Utf8               com/jg/zhang/TestInt  
4.   
5.    ......  
6.   
7.    #5 = Utf8               a  
8.    #6 = Utf8               I  
9.   
10.    ......  
11.   
12.   #12 = Fieldref           #1.#13         //  com/jg/zhang/TestInt.a:I  
13.   #13 = NameAndType        #5:#6          //  a:I  
14.   
15.   ......  
16.   
17. {  
18.   
19.   void print();  
20.     flags:  
21.     Code:  
22.       stack=2, locals=1, args_size=1  
23.          0: getstatic     #19                 // Field java/lang/System.out:Ljava/io/PrintStream;  
24.          3: aload_0  
25.          4: getfield      #12                 // Field a:I  
26.          7: invokevirtual #25                 // Method java/io/PrintStream.println:(I)V  
27.         10: return  
28. }

 

 

 

可以看到, 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 。 

 

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

JAVA语言之深入理解Java Class文件格式(五)

(10) CONSTANT_Methodref_info

 

常量池中的一个CONSTANT_Methodref_info, 可以看做是CONSTANT_Methodref数据类型的一个实例。 该数据项表示对一个类中方法的符号引用, 可以是对本类中的方法的符号引用, 也可以是对其他类中的方法的符号引用, 可以是对成员方法字段的符号引用, 也可以是对静态方法的符号引用,但是不会是对接口中的方法的符号引用。 其中ref三个字母就是reference的简写。 在上一小节中介绍了CONSTANT_Fieldref_info, 它是对字段的符号引用, 本节中介绍的CONSTANT_Methodref_info和CONSTANT_Fieldref_info很相似。既然是符号“引用”, 那么只有在原文件中调用了一个方法, 常量池中才有和这个被调用方法的相对应的符号引用, 即存在一个CONSTANT_Methodref_info。 如果只是在类中定义了一个方法, 但是没调用它, 则不会在常量池中出现和这个方法对应的CONSTANT_Methodref_info 。 

 

和其他类型的常量池数据项一样, 它的第一个字节也必然是tag, 它的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前面的博客中已经解释过了, 不明白的朋友可以先看前面的博客:深入理解Java Class文件格式(三) 。 这个CONSTANT_NameAndType_info描述的是被引用的方法的名称和描述符。 我们在前面的博客中也提到过, CONSTANT_NameAndType_info可以作为方法符号引用的一部分。

 

到此, 我们可以知道, CONSTANT_Methodref_info就是对一个字段的符号引用, 这个符号引用包括两部分, 一部分是该方法所在的类, 另一部分是该方法的方法名和描述符。 这就是所谓的 “对方法的符号引用” 。

 

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

[java] view plain copy
1. package com.jg.zhang;  
2.   
3. public class Programer {  
4.   
5.     Computer computer;  
6.       
7.     public Programer(Computer computer){  
8.         this.computer = computer;  
9.     }  
10.       
11.     public void doWork(){  
12.         computer.calculate();  
13.     }  
14. }  
 
 
[java] view plain copy
1. package com.jg.zhang;  
2.   
3. public class Computer {  
4.   
5.     public void calculate() {  
6.         System.out.println("working...");  
7.           
8.     }  
9. }

 

上面的代码包括两个类, 其中Programer类引用了Computer类, 在Programer类的doWork方法中引用(调用)了Computer类的calculate方法。源码中对一个方法的描述形式我们再熟悉不过了, 现在我们就反编译Programer, 看看Programer中对Computer的doWork方法的引用, 在class文件中是如何描述的。 

 

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

[java] view plain copy
1. Constant pool:  
2. .........  
3.   
4.   
5.   #12 = Utf8               ()V  
6.   
7.   
8.   #20 = Methodref          #21.#23        //  com/jg/zhang/Computer.calculate:()V  
9.   #21 = Class              #22            //  com/jg/zhang/Computer  
10.   #22 = Utf8               com/jg/zhang/Computer  
11.   #23 = NameAndType        #24:#12        //  calculate:()V  
12.   #24 = Utf8               calculate  
13.   
14. {  
15.   
16.   com.jg.zhang.Computer computer;       
17.     flags:  
18.   
19. .........  
20.   
21.   public void doWork();  
22.     flags: ACC_PUBLIC  
23.     Code:  
24.       stack=1, locals=1, args_size=1  
25.          0: aload_0  
26.          1: getfield      #13                 // Field computer:Lcom/jg/zhang/Computer;  
27.          4: invokevirtual #20                 // Method com/jg/zhang/Computer.calculate:()V  
28.          7: return  
29. }

 


可以看到, 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 。 

JAVA语言之深入理解Java Class文件格式(五)

(11) CONSTANT_InterfaceMethodref_info

 

常量池中的一个CONSTANT_InterfaceMethodref_info, 可以看做是CONSTANT_InterfaceMethodref数据类型的一个实例。 该数据项表示对一个接口方法的符号引用, 不能是对类中的方法的符号引用。 其中ref三个字母就是reference的简写。 在上一小节中介绍了CONSTANT_Methodref_info, 它是对类中的方法的符号引用, 本节中介绍的CONSTANT_InterfaceMethodref和CONSTANT_Methodref_info很相似。既然是符号“引用”, 那么只有在原文件中调用了一个接口中的方法, 常量池中才有和这个被调用方法的相对应的符号引用, 即存在一个CONSTANT_InterfaceMethodref。 如果只是在接口中定义了一个方法, 但是没调用它, 则不会在常量池中出现和这个方法对应的CONSTANT_InterfaceMethodref 。 

 

和其他类型的常量池数据项一样, 它的第一个字节也必然是tag, 它的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前面的博客中已经解释过了, 不明白的朋友可以先看前面的博客:深入理解Java Class文件格式(三) 。 这个CONSTANT_NameAndType_info描述的是被引用的方法的名称和描述符。 我们在前面的博客中也提到过, CONSTANT_NameAndType_info可以作为方法符号引用的一部分。

 

到此, 我们可以知道, CONSTANT_InterfaceMethodref就是对一个接口中的方法的符号引用, 这个符号引用包括两部分, 一部分是该方法所在的接口, 另一部分是该方法的方法名和描述符。 这就是所谓的 “对接口中的方法的符号引用” 。

 

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

[java] view plain copy
1. package com.jg.zhang;  
2.   
3. public class Plane {  
4.   
5.     IFlyable flyable;  
6.       
7.     void flyToSky(){  
8.         flyable.fly();  
9.     }  
10. }  
 
[java] view plain copy
1. package com.jg.zhang;  
2.   
3. public interface IFlyable {  
4.   
5.     void fly();  
6. }


在上面的代码中, 定义可一个类Plane, 在这个类中有一个IFlyable接口类型的字段flyable, 然后在Plane的flyToSky方法中调用了IFlyable中的fly方法。 这就是源代码中对一个接口中的方法的引用方式, 下面我们反编译Plane, 看看在class文件层面, 对一个接口中的方法的引用是如何描述的。

下面给出反编译结果, 为了简洁期间, 省略了一些不相关的内容:

[plain] view plain copy
1. Constant pool:  
2. .........  
3.   
4.   #8 = Utf8               ()V  
5.   
6.   #19 = InterfaceMethodref #20.#22        //  com/jg/zhang/IFlyable.fly:()V  
7.   #20 = Class              #21            //  com/jg/zhang/IFlyable  
8.   #21 = Utf8               com/jg/zhang/IFlyable  
9.   #22 = NameAndType        #23:#8         //  fly:()V  
10.   #23 = Utf8               fly  
11.   
12. {  
13.   
14. .........  
15.   
16.   com.jg.zhang.IFlyable flyable;  
17.     flags:  
18.   
19. .........  
20.   
21.   void flyToSky();  
22.     flags:  
23.     Code:  
24.       stack=1, locals=1, args_size=1  
25.          0: aload_0  
26.          1: getfield      #17                 // Field flyable:Lcom/jg/zhang/IFlyable;  
27.          4: invokeinterface #19,  1           // InterfaceMethod com/jg/zhang/IFlyable.fly:()V  
28.          9: return  
29.   
30. }

 


可以看到, 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 。 

JAVA语言之深入理解Java Class文件格式(五)

 

总结

 

到此为止, class文件中的常量池部分就已经讲解完了。 进行一下总结。对于深入理解Java和JVM , 理解class文件的格式至关重要, 而在class文件中, 常量池是一项非常重要的信息。 常量池中有11种数据项, 这个11种数据项存储了各种信息, 包括常量字符串, 类的信息, 方法的符号引用, 字段的符号引用等等。 常量池中的数据项通过索引来访问, 访问形式类似于数组。 常量池中的各个数据项之前会通过索引相互引用, class文件的其他地方也会引用常量池中的数据项 , 如方法的字节码指令。 

 

在下面的文章中, 会继续介绍class文件中, 位于常量池以下的其他信息。 这些信息包括:对本类的描述, 对父类的描述, 对实现的接口的描述, 本类中声明的字段的描述, 本类汇总定义的方法的描述,还有各种属性。 

希望对JAVA有兴趣的朋友有所帮助。了解更多内容,请关注职坐标编程语言JAVA频道!

本文由 @Vivian 发布于职坐标。未经许可,禁止转载。
喜欢 | 2 不喜欢 | 0
看完这篇文章有何感觉?已经有2人表态,100%的人喜欢 快给朋友分享吧~
评论(0)
后参与评论

您输入的评论内容中包含违禁敏感词

我知道了

助您圆梦职场 匹配合适岗位
验证码手机号,获得海同独家IT培训资料
选择就业方向:
人工智能物联网
大数据开发/分析
人工智能Python
Java全栈开发
WEB前端+H5

请输入正确的手机号码

请输入正确的验证码

获取验证码

您今天的短信下发次数太多了,明天再试试吧!

提交

我们会在第一时间安排职业规划师联系您!

您也可以联系我们的职业规划师咨询:

小职老师的微信号:z_zhizuobiao
小职老师的微信号:z_zhizuobiao

版权所有 职坐标-一站式IT培训就业服务领导者 沪ICP备13042190号-4
上海海同信息科技有限公司 Copyright ©2015 www.zhizuobiao.com,All Rights Reserved.
 沪公网安备 31011502005948号    

©2015 www.zhizuobiao.com All Rights Reserved

208小时内训课程