Class文件结构详解(二)

前情回顾

在上一章我们介绍了jvm、class文件和class文件中的魔数、版本号和常量池计数值(见Class文件结构(一))。本章我们来具体介绍常量池。

 

class文件特殊字符串

在介绍常量池之前,我们得先介绍一下在class文件中出场率较高的一些特殊字符串。

1、简单名称

没有类型和参数修饰的方法或字段名称

2、全限定名

假设一个类的全名是com.example.demo,那么它在class文件中的全限定名就是将"."替换为"/"。例如Object的类全名为java.lang.Object,那么它的全限定名就是java/lang/Object

3、描述符

描述符得分为3种类型的描述符

(1)基本类型的描述符

                      基本数据类型和void类型               类型对应字符
                                 byte                      B
                                 char                      C
                                double                      D
                                 float                      F
                                  int                      I
                                  long                      J
                                 short                      S
                               boolean                      Z
                                  void                      V

只要记住除了long对应J和boolean对应Z以外,其他都是对应自己的大写字母

(2)引用类型(类和接口)的描述符

"L"+类型低的全限定名+";"

比如Object的描述符是

Ljava/lang/Object;

com.example.demo对应的就是

Lcom/example/demo;

(3)数组类型的描述符

数组类型中的每一个维度都用一个"["表示,因此整个类型对应字符串就是

若干个"["+数组中元素类型对应的字符串

比如Object[][]的描述符是

[[Ljava/lang/Object;

(4)方法描述符

格式:

(参数类型1参数类型2...)返回值类型

                 方法描述符                             方法声明                 
                 ()I                       int getSize()
                 ()Ljava/lang/String;                    String toString()
                 ([Ljava/lang/String;)V                    void  main(String[] args)
                 ([BII)I                     int  read(byte[]  b,int  off,int  len)    

常量池

常量池是Class文件中的资源仓库,主要存放两大类常量,分别是字面量符号引用。字面量大家可以类比为Java语言层面的常量,而符号引用则包括下面三类常量:

  • 类和接口的全限定名
  • 字段的名称和描述符
  • 方法的名称和描述符

                                         

           类型                           名称                              数量
            u2             constant_pool_count                                 1
          cp_info                  constant_pool              constant_pool_count-1

如上表所示,常量池由一个无符号数和一张表组成。无符号数constant_pool_count代表常量池容量计数值,是常量池入口参数,由于该数是从1开始计数,因此常量池实际数量是该数-1。接下来的就是常量池中的具体数据项了。

常量池的项目类型
                                                    类型 标志 描述
                                        CONSTANT_Utf8_info 1 UTF-8编码的字符串
                                        CONSTANT_Integer_info 3 整型字面量
                                        CONSTANT_Float_info 4 浮点型字面量
                                        CONSTANT_Long_info 5 长整型字面量
                                        CONSTANT_Double_info 6 双精度浮点型字面量
                                        CONSTANT_Class_info 7 类或接口的符号引用
                                        CONSTANT_String_info 8 字符串类型字面量
                                        CONSTANT_Fieldref_info 9 字段的符号引用
                                        CONSTANT_Methodref_info 10 类中方法的的符号引用
                                        CONSTANT_InterfaceMethodref_info 11 接口中方法的符号引用
                                        CONSTANT_NameAndType_info 12 字段或方法的部分符号引用
                                        CONSTANT_MethodHandle_info 15 表示方法句柄
                                        CONSTANT_MethodType_info 16 标识方法句柄
                                        CONSTANT_InvokeDynamic_info 18 表示一个动态方法调用点

上表中表示了常量池中的14种不同类型的常量,这十四种常量各自都有自己的结构。


CONSTANT_Utf8_info的结构:

CONSTANT_Utf8_info的结构
                   类型               名称        数量
                    u1                 tag              1
                    u2                length              1
                    u1                 bytes           length

tag是标志位,用于标志该常量的类型,比如该常量为CONSTANT_Utf8_info,它的tag值就为1,CONSTANT_Class_info就为7。

length表明该字符串的长度是多少字节,而length字节后的连续数据是一个字符串。下面给出一个实例说明该常量的结构。

Comments.java

package com.zust.bean;

public class Comments {
    private String comments_no;
    private String comments_name;
    private String comments_context;
    private String news_no;

    public String getComments_name() {
        return comments_name;
    }

    public void setComments_name(String comments_name) {
        this.comments_name = comments_name;
    }

    public String getComments_context() {
        return comments_context;
    }

    public void setComments_context(String comments_context) {
        this.comments_context = comments_context;
    }

    public String getNews_no() {
        return news_no;
    }

    public void setNews_no(String news_no) {
        this.news_no = news_no;
    }

    public String getComments_no() {
        return comments_no;
    }

    public void setComments_no(String comments_no) {
        this.comments_no = comments_no;
    }
}

用winhex打开Comments.class

Class文件结构详解(二)

第9个和第10个字节说明常量池中有38个常量,我们从第11个字节开始,这个字节是07,表明这个常量是CONSTANT_Class_info,这个类型的常量有3个字节,我们先跳过这个常量(因为还没介绍过它的结构QAQ),我们来到第14个字节,它是01,表明这个常量是CONSTANT_Utf8_info,接下来的2个字节是00 16说明它的长度是22,于是我们可以确定从第17个字节开始到第38个字节中储存的是我们想要的字符串。

Class文件结构详解(二)

这里用蓝色字符标记了第17-38的字节,我们在右侧可以看到这个字符串是com/zust/bean/Comments,是这个类的全限定名,可以验证上述推论是正确的。


下面我们介绍一个符号引用的常量:

CONSTANT_Class_info结构:

CONSTANT_Class_info型常量的结构
              类型                  名称             数量
                u1                    tag               1
                u2                  name_index               1

tag和CONSTANT_Utf8_info中的一样都是标志位。name_index是一个索引值,表示指向常量池中的第几个常量,由于当前常量是类或接口的符号引用,所以name_index是指向一个字符串,而常量池中存放字符串的就是CONSTANT_Utf8_info,所以这里的name_index实际上就是指向CONSTANT_Utf8_info的。

下面我们用一个专门分析Class文件字节码的工具javap来分析上文中的Comments.class。

我们敲入javap -verbose Comments.class命令,得到以下内容,下面只截取了部分需要用到的。

 Last modified 2019-7-4; size 1166 bytes
  MD5 checksum f7b9e128edcf563d8216d36602fda343
  Compiled from "Comments.java"
public class com.zust.bean.Comments
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Class              #2             // com/zust/bean/Comments
   #2 = Utf8               com/zust/bean/Comments
   #3 = Class              #4             // java/lang/Object
   #4 = Utf8               java/lang/Object
   #5 = Utf8               comments_no
   #6 = Utf8               Ljava/lang/String;
   #7 = Utf8               comments_name
   #8 = Utf8               comments_context
   #9 = Utf8               news_no
  #10 = Utf8               <init>
  #11 = Utf8               ()V
  #12 = Utf8               Code
  #13 = Methodref          #3.#14         // java/lang/Object."<init>":()V
  #14 = NameAndType        #10:#11        // "<init>":()V
  #15 = Utf8               LineNumberTable
  #16 = Utf8               LocalVariableTable
  #17 = Utf8               this
  #18 = Utf8               Lcom/zust/bean/Comments;
  #19 = Utf8               getComments_name
  #20 = Utf8               ()Ljava/lang/String;
  #21 = Fieldref           #1.#22         // com/zust/bean/Comments.comments_name:Ljava/lang/String;
  #22 = NameAndType        #7:#6          // comments_name:Ljava/lang/String;
  #23 = Utf8               setComments_name
  #24 = Utf8               (Ljava/lang/String;)V
  #25 = Utf8               getComments_context
  #26 = Fieldref           #1.#27         // com/zust/bean/Comments.comments_context:Ljava/lang/String;
  #27 = NameAndType        #8:#6          // comments_context:Ljava/lang/String;
  #28 = Utf8               setComments_context
  #29 = Utf8               getNews_no
  #30 = Fieldref           #1.#31         // com/zust/bean/Comments.news_no:Ljava/lang/String;
  #31 = NameAndType        #9:#6          // news_no:Ljava/lang/String;
  #32 = Utf8               setNews_no
  #33 = Utf8               getComments_no
  #34 = Fieldref           #1.#35         // com/zust/bean/Comments.comments_no:Ljava/lang/String;
  #35 = NameAndType        #5:#6          // comments_no:Ljava/lang/String;
  #36 = Utf8               setComments_no
  #37 = Utf8               SourceFile
  #38 = Utf8               Comments.java

前情回顾

在上一章我们介绍了jvm、class文件和class文件中的魔数、版本号和常量池计数值。本章我们来具体介绍常量池。

 

class文件特殊字符串

在介绍常量池之前,我们得先介绍一下在class文件中出场率较高的一些特殊字符串。

1、简单名称

没有类型和参水修饰的方法或字段名称

2、全限定名

假设一个类的全名是com.example.demo,那么它在class文件中的全限定名就是将"."替换为"/"。例如Object的类全名为java.lang.Object,那么它的全限定名就是java/lang/Object

3、描述符

描述符得分为3种类型的描述符

(1)基本类型的描述符

                      基本数据类型和void类型               类型对应字符
                                 byte                      B
                                 char                      C
                                double                      D
                                 float                      F
                                  int                      I
                                  long                      J
                                 short                      S
                               boolean                      Z
                                  void                      V

只要记住除了long对应J和boolean对应Z以外,其他都是对应自己的大写字母

(2)引用类型(类和接口)的描述符

"L"+类型低的全限定名+";"

比如Object的描述符是

Ljava/lang/Object;

com.example.demo对应的就是

Lcom/example/demo;

(3)数组类型的描述符

数组类型中的每一个维度都用一个"["表示,因此整个类型对应字符串就是

若干个"["+数组中元素类型对应的字符串

比如Object[][]的描述符是

[[Ljava/lang/Object;

(4)方法描述符

格式:

(参数类型1参数类型2...)返回值类型

                 方法描述符                             方法声明                 
                 ()I                       int getSize()
                 ()Ljava/lang/String;                    String toString()
                 ([Ljava/lang/String;)V                    void  main(String[] args)
                 ([BII)I                     int  read(byte[]  b,int  off,int  len)    

常量池

常量池是Class文件中的资源仓库,主要存放两大类常量,分别是字面量符号引用。字面量大家可以类比为Java语言层面的常量,而符号引用则包括下面三类常量:

  • 类和接口的全限定名
  • 字段的名称和描述符
  • 方法的名称和描述符

                                         

           类型                           名称                              数量
            u2             constant_pool_count                                 1
          cp_info                  constant_pool              constant_pool_count-1

如上表所示,常量池由一个无符号数和一张表组成。无符号数constant_pool_count代表常量池容量计数值,是常量池入口参数,由于该数是从1开始计数,因此常量池实际数量是该数-1。接下来的就是常量池中的具体数据项了。

常量池的项目类型
                                                    类型 标志 描述
                                        CONSTANT_Utf8_info 1 UTF-8编码的字符串
                                        CONSTANT_Integer_info 3 整型字面量
                                        CONSTANT_Float_info 4 浮点型字面量
                                        CONSTANT_Long_info 5 长整型字面量
                                        CONSTANT_Double_info 6 双精度浮点型字面量
                                        CONSTANT_Class_info 7 类或接口的符号引用
                                        CONSTANT_String_info 8 字符串类型字面量
                                        CONSTANT_Fieldref_info 9 字段的符号引用
                                        CONSTANT_Methodref_info 10 类中方法的的符号引用
                                        CONSTANT_InterfaceMethodref_info 11 接口中方法的符号引用
                                        CONSTANT_NameAndType_info 12 字段或方法的部分符号引用
                                        CONSTANT_MethodHandle_info 15 表示方法句柄
                                        CONSTANT_MethodType_info 16 标识方法句柄
                                        CONSTANT_InvokeDynamic_info 18 表示一个动态方法调用点

上表中表示了常量池中的14种不同类型的常量,这十四种常量各自都有自己的结构。


CONSTANT_Utf8_info的结构:

CONSTANT_Utf8_info的结构
                   类型               名称        数量
                    u1                 tag              1
                    u2                length              1
                    u1                 bytes           length

tag是标志位,用于标志该常量的类型,比如该常量为CONSTANT_Utf8_info,它的tag值就为1,CONSTANT_Class_info就为7。

length表明该字符串的长度是多少字节,而length字节后的连续数据是一个字符串。下面给出一个实例说明该常量的结构。

Comments.java

package com.zust.bean;

public class Comments {
    private String comments_no;
    private String comments_name;
    private String comments_context;
    private String news_no;

    public String getComments_name() {
        return comments_name;
    }

    public void setComments_name(String comments_name) {
        this.comments_name = comments_name;
    }

    public String getComments_context() {
        return comments_context;
    }

    public void setComments_context(String comments_context) {
        this.comments_context = comments_context;
    }

    public String getNews_no() {
        return news_no;
    }

    public void setNews_no(String news_no) {
        this.news_no = news_no;
    }

    public String getComments_no() {
        return comments_no;
    }

    public void setComments_no(String comments_no) {
        this.comments_no = comments_no;
    }
}

用winhex打开Comments.class

Class文件结构详解(二)

第9个和第10个字节说明常量池中有38个常量,我们从第11个字节开始,这个字节是07,表明这个常量是CONSTANT_Class_info,这个类型的常量有3个字节,我们先跳过这个常量(因为还没介绍过它的结构QAQ),我们来到第14个字节,它是01,表明这个常量是CONSTANT_Utf8_info,接下来的2个字节是00 16说明它的长度是22,于是我们可以确定从第17个字节开始到第38个字节中储存的是我们想要的字符串。

Class文件结构详解(二)

这里用蓝色字符标记了第17-38的字节,我们在右侧可以看到这个字符串是com/zust/bean/Comments,是这个类的全限定名,可以验证上述推论是正确的。


下面我们介绍一个符号引用的常量:

CONSTANT_Class_info结构:

CONSTANT_Class_info型常量的结构
              类型                  名称             数量
                u1                    tag               1
                u2                  name_index               1

tag和CONSTANT_Utf8_info中的一样都是标志位。name_index是一个索引值,表示指向常量池中的第几个常量,由于当前常量是类或接口的符号引用,所以name_index是指向一个字符串,而常量池中存放字符串的就是CONSTANT_Utf8_info,所以这里的name_index实际上就是指向CONSTANT_Utf8_info的。

下面我们用一个专门分析Class文件字节码的工具javap来分析上文中的Comments.class。

我们敲入javap -verbose Comments.class命令,得到以下内容,下面只截取了部分需要用到的。

 

 Last modified 2019-7-4; size 1166 bytes
  MD5 checksum f7b9e128edcf563d8216d36602fda343
  Compiled from "Comments.java"
public class com.zust.bean.Comments
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Class              #2             // com/zust/bean/Comments
   #2 = Utf8               com/zust/bean/Comments
   #3 = Class              #4             // java/lang/Object
   #4 = Utf8               java/lang/Object
   #5 = Utf8               comments_no
   #6 = Utf8               Ljava/lang/String;
   #7 = Utf8               comments_name
   #8 = Utf8               comments_context
   #9 = Utf8               news_no
  #10 = Utf8               <init>
  #11 = Utf8               ()V
  #12 = Utf8               Code
  #13 = Methodref          #3.#14         // java/lang/Object."<init>":()V
  #14 = NameAndType        #10:#11        // "<init>":()V
  #15 = Utf8               LineNumberTable
  #16 = Utf8               LocalVariableTable
  #17 = Utf8               this
  #18 = Utf8               Lcom/zust/bean/Comments;
  #19 = Utf8               getComments_name
  #20 = Utf8               ()Ljava/lang/String;
  #21 = Fieldref           #1.#22         // com/zust/bean/Comments.comments_name:Ljava/lang/String;
  #22 = NameAndType        #7:#6          // comments_name:Ljava/lang/String;
  #23 = Utf8               setComments_name
  #24 = Utf8               (Ljava/lang/String;)V
  #25 = Utf8               getComments_context
  #26 = Fieldref           #1.#27         // com/zust/bean/Comments.comments_context:Ljava/lang/String;
  #27 = NameAndType        #8:#6          // comments_context:Ljava/lang/String;
  #28 = Utf8               setComments_context
  #29 = Utf8               getNews_no
  #30 = Fieldref           #1.#31         // com/zust/bean/Comments.news_no:Ljava/lang/String;
  #31 = NameAndType        #9:#6          // news_no:Ljava/lang/String;
  #32 = Utf8               setNews_no
  #33 = Utf8               getComments_no
  #34 = Fieldref           #1.#35         // com/zust/bean/Comments.comments_no:Ljava/lang/String;
  #35 = NameAndType        #5:#6          // comments_no:Ljava/lang/String;
  #36 = Utf8               setComments_no
  #37 = Utf8               SourceFile
  #38 = Utf8               Comments.java

                                                                                          图(一)

 

Class文件结构详解(二)

                                                                             图(二)

图(一)和图(二)中的#+数字代表第几个常量,我们可以看到第一个常量是Class,是类的符号引用,字节是 07 00 02,意为指向第二个常量,因此在图(一)中Class文件结构详解(二)后方会跟上一个#2,后面的注释就是它指向的字符串的字面量。第二个常量是utf8类型,图二中用#2标注了出来。第三个依然是Class类型,07 00 04,指向第四个常量,第四个是utf8...,依此类推即可。


常量池中的常量类型我们已经介绍了两个,其余的类型我们皆可通过类似的方法推算,不再详述,下面给出常量池中的14种常量项的结构总表。

Class文件结构详解(二)

Class文件结构详解(二)

 

 

总结

本章详述了Class文件中常用的特殊字符串、常量池中的两类数据项CONSTANT_utf8_info和CONSTANT_Class_info。下篇博客我们将讲述Class文件的访问标志,类索引、父类索引和接口索引。

上一篇:[网易IM通讯]推送小结


下一篇:C语言"initializer element is not constant"错误原因