深入理解java语言的class文件格式(五)

转载 2015年11月19日 11:43:15

前情回顾


本专栏的前几篇博文, 对class文件中的常量池进行了详细的解释。 前文讲解了常量池中的7种数据项, 它们分别是:

  1. CONSTANT_Utf8_info
  2. CONSTANT_NameAndType_info
  3. CONSTANT_Integer_info
  4. CONSTANT_Float_info
  5. CONSTANT_Long_info
  6. CONSTANT_Double_info
  7. CONSTANT_String_info

关于这七种数据项, 前面的文章已经讲得很详细了, 不了解的同学请先参阅前面的博文。 此外, 如果想要全面的了解JVM和Class文件格式, 建议按顺序阅读我专栏中的博客。此外,本文是建立在前几篇博客的基础之上的, 是接着前面的的博客写的,所以, 同样建议先阅读专栏前面的博客, 以保证知识的完整性。 专栏地址:

http://blog.csdn.net/column/details/zhangjg-java-blog.html  

 更多关于深入理解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的存储布局:



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

 




(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 plaincopy在CODE上查看代码片派生到我的代码片
  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 plaincopy在CODE上查看代码片派生到我的代码片
  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 。 





(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 plaincopy在CODE上查看代码片派生到我的代码片
  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 plaincopy在CODE上查看代码片派生到我的代码片
  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 plaincopy在CODE上查看代码片派生到我的代码片
  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.          7return  
  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 。 





(11) CONSTANT_InterfaceMethodref_info


常量池中的一个CONSTANT_InterfaceMethodref_info, 可以看做是CONSTANT_InterfaceMethodref数据类型的一个实例。 该数据项表示对一个接口方法的符号引用, 不能是对类中的方法的符号引用。 其中ref三个字母就是reference的简写。 在上一小节中介绍了CONSTANT_Methodref_info, 它是对类中的方法的符号引用, 本节中介绍的CONSTANT_InterfaceMethodrefCONSTANT_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 plaincopy在CODE上查看代码片派生到我的代码片
  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 plaincopy在CODE上查看代码片派生到我的代码片
  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 plaincopy在CODE上查看代码片派生到我的代码片
  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 。 





总结


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

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

===========================================================================================

原文出自:http://blog.csdn.net/zhangjg_blog/article/details/21781021

===========================================================================================


相关文章推荐

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

在上一篇博客中, 大致讲解了Java虚拟机的体系结构和执行原理。 本篇博客主要讲解能够被JVM识别, 加载并执行的class文件的格式。 对于理解JVM和深入理解Java语言, 学习并了解class...

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

首先, 让我们回顾一下关于class文件格式的之前两篇博客的主要内容。 在 深入理解Java Class文件格式(一) 中, 讲解了class文件在整个java体系结构中的位置和作用, 讲解了clas...

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

经过前几篇文章, 终于将常量池介绍完了, 之所以花这么大的功夫介绍常量池, 是因为对于理解class文件格式,常量池是必须要了解的, 因为class文件中其他地方,大量引用了常量池中的数据项。  对于...

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

在上一篇文章 深入理解Java Class文件格式(一) 中, 介绍了class文件在整个java体系结构中的位置和作用, 并对class文件的整体格式做了说明, 介绍了其中的魔数和版本号的相关内容,...

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

在本专栏的第一篇文章 深入理解Java虚拟机到底是什么 中, 我们主要讲解了什么是虚拟机, 这篇博客是对JVM的一个概述。 在随后的几篇文章中,一直在讲解class文件格式。 在今天这篇博客中, ...

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

在上一篇博客深入理解Java Class文件格式(三) 中, 介绍了常量池中的两种类型的数据项, 分别是CONSTANT_Utf8_info和CONSTANT_NameAndType_info 。 C...

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

经过前八篇关于class文件的博客, 关于class文件格式的内容也基本上讲完了。 本文是关于class文件格式的最后一篇。 在这篇博客中, 将会讲解关于方法的几个属性。 理解这篇博客的内容, 对于理...

Java Class文件格式解析及实例

JAVA无关性概述 Java语言从刚诞生开始曾提出一个非常著名的宣言:“一次编写,到处运行(Write Once, Run Anywhere)”。Sun公司和其他虚拟机公司发布了许多可以运行在不同操...

JAVA的class文件格式例解

JAVA程序从源代码到运行的一般过程为: JAVA源文件编译之后形成class文件,也就是java字节码文件,然后java虚拟机解释执行 java字节码文件。我们来简单看看java字节码文件的格式。下...

浅析java class文件格式

本文是对Java字节码文件的理解,希望对大家有帮助
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

(最多只允许输入30个字)