Java Class 字节码文件结构详解

澳门新葡亰3522平台游戏 3

javap -v TestClass.class >./out.txt

3. 哪些字面量会进入常量池中

  • 我们知道class文件存放字面量:如文本字符串、声明为final的常量值等。这里的“等”就挺烦人。
  • 下面我们来看看哪些字面量会进入常量池。(jdk1.8.0环境)

8种基本类型:

测试案例:

  • final类型 FinalTest.java代码

public class FinalTest{

   private final int int_num =12;
   private final char char_num = 'a';
   private final short short_num =30;
   private final float float_num = 45.3f;
   private final double double_num =39.8;
   private final byte byte_num =121;
   private final long long_num = 2323L;
   private final boolean boolean_flage = true;
}
  • 非final类型 test.java代码

public class test{

   private int int_num =12;
   private char char_num = 'a';
   private short short_num =30;
   private float float_num = 45.3f;
   private double double_num =39.8;
   private byte byte_num =121;
   private long long_num = 2323L;
   private long long_delay_num ;
   private boolean boolean_flage = true;

   public void init(){
     this.long_delay_num = 5555L;
   }
}

上面代码测试结果:

  • final类型的8种基本类型的值会进入常量池。
  • 非final类型的8种基本类型的值double、float、long的值会进入常量池,包括long_delay_num的值。

String类型

  • StringTest.java代码:

public class StringTest{

      private String str1 = "zl"+"cook";
      private String str2 = str1+"hello";
      private String str3 = new String("zlcook here?");
      private String str4 = "everybody "+ new String("here?");

      private final String fin1 = "boy";
      private final String fin2 = fin1+ "is boy";
      private final String fin3 = str1+ "is boy";
}
  • StringTest.class的常量池种包含内容:
![](https://upload-images.jianshu.io/upload_images/3458176-adab40db73749fe0.png)

常量池中包含的字符串类型字面量

所有测试数据github:
测试数据

Class字节码中有两种数据类型:

2.3.1 constant_pool_count

  • 常量池容量计数值(u2类型):从1开始,表示常量池中有多少项常量。即constant_pool_count=1表示常量池中有0个常量项。

  • 设计者将第0项常量空出来是有特殊考虑的,这样做的目的在于满足后面某些指向常量池的索引值的数据在特定情况下需要表达“不引用任何一个常量池项目”的含义,这种情况就可以把索引值置为0来表示。Class文件结构中只有常量池的容量计数是从1开始,对于其他集合类型,包括接口索引集合、字段表集合、方法表集合等的容量计数都与一般习惯相同,是从0开始的。

  • TestClass.class文件中constant_pool_count的十进制值为19,表示常量池中有18项常量,索引范围1-18。

澳门新葡亰3522平台游戏 1

TestClass.class文件中constant_pool_count的十进制值为19

ClassParser负责把握Class字节码整体结构的解析。

2.6 class文件中包含的内容

  • 下面我们来看一下class文件中常量池的内容和java源码中的内容。

  • TestClass.java代码内容

package com.zlcook.clazz;

public class TestClass{
  private int m;
  public int inc(){
   return m+1;
  }
}
  • TestClass.class中常量池内容:

Constant pool:
   #1 = Methodref          #4.#15         //  java/lang/Object."<init>":()V
   #2 = Fieldref           #3.#16         //  com/zlcook/clazz/TestClass.m:I
   #3 = Class              #17            //  com/zlcook/clazz/TestClass
   #4 = Class              #18            //  java/lang/Object
   #5 = Utf8               m
   #6 = Utf8               I
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               inc
  #12 = Utf8               ()I
  #13 = Utf8               SourceFile
  #14 = Utf8               TestClass.java
  #15 = NameAndType        #7:#8          //  "<init>":()V
  #16 = NameAndType        #5:#6          //  m:I
  #17 = Utf8               com/zlcook/clazz/TestClass
  #18 = Utf8               java/lang/Object
  • 再复习一下常量池中主要存放字面量:如文本字符串、声明为final的常量值等。和符号引用:类和接口的全限定名、字段的名称和描述符、方法的名称和描述符。
  • 所以出现com/zlcook/clazz/TestClass、java/lang/Object、m、inc都是应该的,那么I、V、<init>、LineNumberTable都是什么?那肯定是字段描述符或者是方法描述符了。这部分是编译时自动生成的,它们会被class文件中其它部分(字段表field_info、方法表method_info、属性表attribute_info)引用到,它们会用来描述一些不方便使用“固定字节”进行表达的内容。譬如描述方法的返回值是什么?有几个参数?每个参数的类型是什么?因为Java中的“类”是无穷无尽的,无法通过简单的无符号字节来描述一个方法用到了什么类,因此在描述方法的这些信息时,需要引用常量表中的符号引用进行表达。

TestClass为待解析的目标类,读者可以任意改写此类来多做实验

  • 字节码查看工具:WinHex

测试方法入口:

2.1 常量池中存放的内容

  • Class文件中包含常量池,那么我就需要知道常量池会包含哪些内容,接下来才是关心class格式文件用什么类型来存放这些内容。

  • 字面量(Literal)

  • 字面量比较接近于Java语言层面的常量概念,如文本字符串、声明为final的常量值等。

  • 符号引用(Symbolic References)

  • 符号引用则属于编译原理方面的概念,包括了下面三类常量:
    类和接口的全限定名(Fully Qualified Name)
    字段的名称和描述符(Descriptor)
    方法的名称和描述符

  • 其它:常量池中主要内容是上面2项,说明还有其它内容,这部分内容,在下面我们看到用来描述常量池内容的14种常量项的介绍时就发现标志为15、16、18的常量项类型是用来支持动态语言调用的(jdk1.7时才加入的)。

  • 常量池:可以理解为Class文件之中的资源仓库,它是Class文件结构中与其他项目关联最多的数据类型(后面的很多数据类型都会指向此处),也是占用Class文件空间最大的数据项目之一。

package com.lixin;

public class TestClass {

    private int a = 5;
    protected char c = 'c';
    double x = 1.1;
    long y = 111;

    public void show() {

    }
}

Class类文件的结构

package com.lixin;

import java.io.InputStream;

/**
 * 程序入口
 * @author lixin
 *
 */
public class App {

    public static void main(String[] args) throws Exception {
        InputStream in = Class.class.getResourceAsStream("/com/lixin/TestClass.class");
        ClassParser parser = new ClassParser(in);
        parser.parse();
    }

}

2.3 Class文件中如何描述常量池中内容

  • 知道Class文件的常量池包含的内容后,我们下面就来看看class格式文件使用了哪些类型数据来存放常量池的内容。

  • 由Class文件格式可得紧接着主版本号的是常量池入口。

类型 名称 数量
u2(无符号数) constant_pool_count 1
cp_info(表) constant_pool constant_pool_count-1
  • 占用的字节数:2+(constant_pool_count-1)个具体表所占字节。

  • 由上表可见,Class文件使用了一个前置的容量计数器(constant_pool_count)加若干个连续的数据项(constant_pool)的形式来描述常量池内容。我们把这一系列连续常量池数据称为常量池集合。

  • 先给看一下TestClass.class文件全局的内容,下面就来分析其中常量池中的内容,其它内容后面的文章在分析。从图片也可以看出常量池内容占据了class文件的很大一部分,当然TestClass类中代码比较少就更显得常量池内容的多了。

![](https://upload-images.jianshu.io/upload_images/3458176-da04f13d9345d534.png)

TestClass.class文件的16进制内容

最后,我们可以使用jdk中的javap进行字节码反编译,来对比我们的读取与反编译结果差别,用于查错。

2.5 采用javap命令分析class文件

  • 根据上面的找法我们就可以找出常量池中包含的内容:字面量和符号引用。java考虑到这种找法太麻烦了,所以提供了一个命令javap来帮助我们分析class文件的内容。

  • javap分析class文件用法:javap -verbose class文件名

$ javap -verbose TestClass.class
Classfile /E:/studytry/com/zlcook/clazz/TestClass.class
  Last modified 2017-4-7; size 292 bytes
  MD5 checksum 486567c6d4d7432fc359230fed9c92c7
  Compiled from "TestClass.java"
public class com.zlcook.clazz.TestClass
  SourceFile: "TestClass.java"
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #4.#15         //  java/lang/Object."<init>":()V
   #2 = Fieldref           #3.#16         //  com/zlcook/clazz/TestClass.m:I
   #3 = Class              #17            //  com/zlcook/clazz/TestClass
   #4 = Class              #18            //  java/lang/Object
   #5 = Utf8               m
   #6 = Utf8               I
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               inc
  #12 = Utf8               ()I
  #13 = Utf8               SourceFile
  #14 = Utf8               TestClass.java
  #15 = NameAndType        #7:#8          //  "<init>":()V
  #16 = NameAndType        #5:#6          //  m:I
  #17 = Utf8               com/zlcook/clazz/TestClass
  #18 = Utf8               java/lang/Object
{
  public com.zlcook.clazz.TestClass();
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 2: 0

  public int inc();
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: getfield      #2                  // Field m:I
         4: iconst_1
         5: iadd
         6: ireturn
      LineNumberTable:
        line 6: 0
}
  • 上面通过javap命令得到的结果,该结果显示的很友好,由过上面的理论我们可以很清楚的看到常量池一共18项:其中第一项如下:

 #1 = Methodref          #4.#15       //  java/lang/Object."<init>":()V
  • 和我们通过手动方式查看第一个常量项CONSTANT_Methodref_info对比一下就知道javap显示的内容是多么友好了。
第一个常量项 第几个 tag index index 最终代表的内容
class中16进制值 0X0A 0X004 0X000F
转换成10进制值 10 4 15 查完4和15才知道
javap分析显示的友好值 #1 Methodref #4 #15 java/lang/Object."<init>":()V

StreamUtils负责从输入字节流中读取数据

14种常量项结构
  • 常量池中每一项常量都是一个表,JDK1.7之后共有14种不同的表结构数据。一个常量池中的每个常量项都逃不脱这14种结构。根据下图每个类型的描述我们也可以知道每个类型是用来描述常量池中哪些内容(主要是字面量、符号引用)的。比如:CONSTANT_Integer_info是用来描述常量池中字面量信息的,而且只是整型字面量信息。而标志为15、16、18的常量项类型是用来支持动态语言调用的(jdk1.7时才加入的)。

  • 澳门新葡亰3522平台游戏 2

    常量池中的14种项目类型

  • 澳门新葡亰3522平台游戏 3

    常量池中的14种常量项的结构总表

![](https://upload-images.jianshu.io/upload_images/3458176-878fa839b1e28cf3.png)

常量池中的14种常量项的结构总表(续)
  • 这14种表(或者常量项结构)的共同点是:表开始的第一位是一个u1类型的标志位(tag),代表当前这个常量项使用的是哪种表结构,即哪种常量类型。

  • 这14种常量项结构还有一个特点是,其中13表占用得字节固定,只有CONSTANT_Utf8_info占用字节不固定,其大小由length决定。为什么呢?因为从常量池存放的内容可知,其存放的是字面量和符号引用,最终这些内容都会是一个字符串,这些字符串的大小是在编写程序时才确定,比如你定义一个类,类名可以取长取短,所以在没编译前,无法确定大小不固定,编译后,通过utf-8编码,就可以知道其长度。

占用字节
CONSTANT_Class_info 3
CONSTANT_Integer_info 5
CONSTANT_Fieldref_info 5
CONSTANT_Methodref_info 5
CONSTANT_Utf8_info 不固定,取决于length大小
  1. 为什么说常量表的数量是constant_pool_count-1,且索引从1开始而不是0。其实根本原因在于,索引为0也是一个常量(保留常量),只不过它不存在常量表,这个常量就对应null值。因此加上这个系统保留常量,常量个数共为constant_pool_count个,但是常量表数量要减1。
  2. 澳门新葡亰3522平台游戏,在常量池中,如果存在long型或double型字面量,它们会占用两个连续索引。比如:假设一个类中只有一个int型字面量1和一个double型字面量1(当然这种假设是不可能的,因为总会有类名字面量等),则常量池个数为3,而不是2。这正是因为double字面量占用了两个连续的索引。

结束

  • 这一节主要讲了Class文件魔数、版本号和常量池,比较详细介绍了常量池包含的内容以及用到的14种常量项结构。记住本节讲的常量池是class文件中的常量池,要记住还有运行时常量池,每个class文件中的常量池内容在类加载侯会进入方法区的运行时常量池中存放。当然运行时常量池的内容不仅包含这些还包含运行期加入的常量,常见的就是String类的intern()方法。