Java中的类文件结构之二:分析一个.class文件的文本化阅读

版权声明:本文为博主原创文章,转载请注明出处: https://blog.csdn.net/kcstrong/article/details/81233672

    这个文章是一个系列,准备写至少四篇吧,离上一篇写得已经有几个月了,一个没有出第二篇,原因就一个字:懒。其实整理文档挺耗时的,之后一直在关注老罗的Blog,老罗说他一篇Blog要花一周时间,我深以为然,随便写写肯定省时间 ,但对不起写这个事情,话不多说,进入正题吧。

    还是上一篇https://blog.csdn.net/kcstrong/article/details/79460262中的那个例子  

package com.demo;

public class Temp4Test extends Temp3Test {

	private int i = 1;
	public float f;
	public static String thisstr = "";
	
	public Temp4Test(int ii, String str, float ff) {
		i = ii;
		thisstr = str;
		f = ff;
	}
	
	public static void main(String[] args) {
		Temp4Test t4 = new Temp4Test(100, "hello", 5.5f);
		System.out.println(t4);
	}
	
	@Override
	public String toString() {
		return "[" + i + " , " + thisstr + " , " + f + "]";
	}
}

    我们使用以下命令分析下该源码编译后的class文件,注意,该命令的操作目标是class文件:

    javap -verbose Temp4Test

    得到如下的信息:

Classfile /Users/eclipse/workspace/KCSDemo/bin/com/demo/Temp4Test.class
  Last modified 2018-1-30; size 1255 bytes
  MD5 checksum 879afe0e66247b9a5210e9e004bbeda8
  Compiled from "Temp4Test.java"
public class com.demo.Temp4Test extends com.demo.Temp3Test
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Class              #2             // com/demo/Temp4Test
   #2 = Utf8               com/demo/Temp4Test
   #3 = Class              #4             // com/demo/Temp3Test
   #4 = Utf8               com/demo/Temp3Test
   #5 = Utf8               i
   #6 = Utf8               I
   #7 = Utf8               f
   #8 = Utf8               F
   #9 = Utf8               thisstr
  #10 = Utf8               Ljava/lang/String;
  #11 = Utf8               <clinit>
  #12 = Utf8               ()V
  #13 = Utf8               Code
  #14 = String             #15            //
  #15 = Utf8
  #16 = Fieldref           #1.#17         // com/demo/Temp4Test.thisstr:Ljava/lang/String;
  #17 = NameAndType        #9:#10         // thisstr:Ljava/lang/String;
  #18 = Utf8               LineNumberTable
  #19 = Utf8               LocalVariableTable
  #20 = Utf8               <init>
  #21 = Utf8               (ILjava/lang/String;F)V
  #22 = Methodref          #3.#23         // com/demo/Temp3Test."<init>":()V
  #23 = NameAndType        #20:#12        // "<init>":()V
  #24 = Fieldref           #1.#25         // com/demo/Temp4Test.i:I
  #25 = NameAndType        #5:#6          // i:I
  #26 = Fieldref           #1.#27         // com/demo/Temp4Test.f:F
  #27 = NameAndType        #7:#8          // f:F
  #28 = Utf8               this
  #29 = Utf8               Lcom/demo/Temp4Test;
  #30 = Utf8               ii
  #31 = Utf8               str
  #32 = Utf8               ff
  #33 = Utf8               main
  #34 = Utf8               ([Ljava/lang/String;)V
  #35 = String             #36            // hello
  #36 = Utf8               hello
  #37 = Float              5.5f
  #38 = Methodref          #1.#39         // com/demo/Temp4Test."<init>":(ILjava/lang/String;F)V
  #39 = NameAndType        #20:#21        // "<init>":(ILjava/lang/String;F)V
  #40 = Fieldref           #41.#43        // java/lang/System.out:Ljava/io/PrintStream;
  #41 = Class              #42            // java/lang/System
  #42 = Utf8               java/lang/System
  #43 = NameAndType        #44:#45        // out:Ljava/io/PrintStream;
  #44 = Utf8               out
  #45 = Utf8               Ljava/io/PrintStream;
  #46 = Methodref          #47.#49        // java/io/PrintStream.println:(Ljava/lang/Object;)V
  #47 = Class              #48            // java/io/PrintStream
  #48 = Utf8               java/io/PrintStream
  #49 = NameAndType        #50:#51        // println:(Ljava/lang/Object;)V
  #50 = Utf8               println
  #51 = Utf8               (Ljava/lang/Object;)V
  #52 = Utf8               args
  #53 = Utf8               [Ljava/lang/String;
  #54 = Utf8               t4
  #55 = Utf8               toString
  #56 = Utf8               ()Ljava/lang/String;
  #57 = Class              #58            // java/lang/StringBuilder
  #58 = Utf8               java/lang/StringBuilder
  #59 = String             #60            // [
  #60 = Utf8               [
  #61 = Methodref          #57.#62        // java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
  #62 = NameAndType        #20:#63        // "<init>":(Ljava/lang/String;)V
  #63 = Utf8               (Ljava/lang/String;)V
  #64 = Methodref          #57.#65        // java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
  #65 = NameAndType        #66:#67        // append:(I)Ljava/lang/StringBuilder;
  #66 = Utf8               append
  #67 = Utf8               (I)Ljava/lang/StringBuilder;
  #68 = String             #69            //  ,
  #69 = Utf8                ,
  #70 = Methodref          #57.#71        // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  #71 = NameAndType        #66:#72        // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  #72 = Utf8               (Ljava/lang/String;)Ljava/lang/StringBuilder;
  #73 = Methodref          #57.#74        // java/lang/StringBuilder.append:(F)Ljava/lang/StringBuilder;
  #74 = NameAndType        #66:#75        // append:(F)Ljava/lang/StringBuilder;
  #75 = Utf8               (F)Ljava/lang/StringBuilder;
  #76 = String             #77            // ]
  #77 = Utf8               ]
  #78 = Methodref          #57.#79        // java/lang/StringBuilder.toString:()Ljava/lang/String;
  #79 = NameAndType        #55:#56        // toString:()Ljava/lang/String;
  #80 = Utf8               SourceFile
  #81 = Utf8               Temp4Test.java
{
  public float f;
    descriptor: F
    flags: ACC_PUBLIC

  public static java.lang.String thisstr;
    descriptor: Ljava/lang/String;
    flags: ACC_PUBLIC, ACC_STATIC

  static {};
    descriptor: ()V
    flags: ACC_STATIC
    Code:
      stack=1, locals=0, args_size=0
         0: ldc           #14                 // String
         2: putstatic     #16                 // Field thisstr:Ljava/lang/String;
         5: return
      LineNumberTable:
        line 7: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature

  public com.demo.Temp4Test(int, java.lang.String, float);
    descriptor: (ILjava/lang/String;F)V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=4, args_size=4
         0: aload_0
         1: invokespecial #22                 // Method com/demo/Temp3Test."<init>":()V
         4: aload_0
         5: iconst_1
         6: putfield      #24                 // Field i:I
         9: aload_0
        10: iload_1
        11: putfield      #24                 // Field i:I
        14: aload_2
        15: putstatic     #16                 // Field thisstr:Ljava/lang/String;
        18: aload_0
        19: fload_3
        20: putfield      #26                 // Field f:F
        23: return
      LineNumberTable:
        line 9: 0
        line 5: 4
        line 10: 9
        line 11: 14
        line 12: 18
        line 13: 23
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      24     0  this   Lcom/demo/Temp4Test;
            0      24     1    ii   I
            0      24     2   str   Ljava/lang/String;
            0      24     3    ff   F

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=5, locals=2, args_size=1
         0: new           #1                  // class com/demo/Temp4Test
         3: dup
         4: bipush        100
         6: ldc           #35                 // String hello
         8: ldc           #37                 // float 5.5f
        10: invokespecial #38                 // Method "<init>":(ILjava/lang/String;F)V
        13: astore_1
        14: getstatic     #40                 // Field java/lang/System.out:Ljava/io/PrintStream;
        17: aload_1
        18: invokevirtual #46                 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
        21: return
      LineNumberTable:
        line 16: 0
        line 17: 14
        line 18: 21
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      22     0  args   [Ljava/lang/String;
           14       8     1    t4   Lcom/demo/Temp4Test;

  public java.lang.String toString();
    descriptor: ()Ljava/lang/String;
    flags: ACC_PUBLIC
    Code:
      stack=3, locals=1, args_size=1
         0: new           #57                 // class java/lang/StringBuilder
         3: dup
         4: ldc           #59                 // String [
         6: invokespecial #61                 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
         9: aload_0
        10: getfield      #24                 // Field i:I
        13: invokevirtual #64                 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
        16: ldc           #68                 // String  ,
        18: invokevirtual #70                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        21: getstatic     #16                 // Field thisstr:Ljava/lang/String;
        24: invokevirtual #70                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        27: ldc           #68                 // String  ,
        29: invokevirtual #70                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        32: aload_0
        33: getfield      #26                 // Field f:F
        36: invokevirtual #73                 // Method java/lang/StringBuilder.append:(F)Ljava/lang/StringBuilder;
        39: ldc           #76                 // String ]
        41: invokevirtual #70                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        44: invokevirtual #78                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        47: areturn
      LineNumberTable:
        line 22: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      48     0  this   Lcom/demo/Temp4Test;
}
SourceFile: "Temp4Test.java"

    在第一篇中,我们分析了Temp4Test.class文件的二进制各字节的含义,但从二进制看类结构,可读性较差,就像直接看机器码一样,当然有更好的方法,上面即是用JVM所提供的工具javap导出的文本格式的类结构。接下来逐条分析下该文本中包含了那些信息。

    1.第一段:

Classfile /Users/eclipse/workspace/KCSDemo/bin/com/demo/Temp4Test.class
  Last modified 2018-1-30; size 1255 bytes
  MD5 checksum 879afe0e66247b9a5210e9e004bbeda8
  Compiled from "Temp4Test.java"
public class com.demo.Temp4Test extends com.demo.Temp3Test
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER

    望文即可生意:

        a.第一行为文件名及路径

        b.第二行修改时间及文件大小

        c.第三行文件的MD5码

        d.第四行源代码名

        e.第五行继承关系

        f.第六、七行为class文件版本号,主版本号为52,次版本号0,即JDK1.8.0

        g.第八行为类的访问权限

    在上一篇的二进制中这些信息均可看到,此处是JVM整理了一下,详看上一篇即可找到二进制中的定义,比如类的访问权限,所在的位置为:以下为第一篇中的截图

    

    2.第二段:

Constant pool:
   #1 = Class              #2             // com/demo/Temp4Test
   #2 = Utf8               com/demo/Temp4Test
   #3 = Class              #4             // com/demo/Temp3Test
   #4 = Utf8               com/demo/Temp3Test
   #5 = Utf8               i
   #6 = Utf8               I
   #7 = Utf8               f
   #8 = Utf8               F
   #9 = Utf8               thisstr
  #10 = Utf8               Ljava/lang/String;
  #11 = Utf8               <clinit>
  #12 = Utf8               ()V
  #13 = Utf8               Code
  #14 = String             #15            //
  #15 = Utf8
  #16 = Fieldref           #1.#17         // com/demo/Temp4Test.thisstr:Ljava/lang/String;
  #17 = NameAndType        #9:#10         // thisstr:Ljava/lang/String;
  #18 = Utf8               LineNumberTable
  #19 = Utf8               LocalVariableTable
  #20 = Utf8               <init>
  #21 = Utf8               (ILjava/lang/String;F)V
  #22 = Methodref          #3.#23         // com/demo/Temp3Test."<init>":()V
  #23 = NameAndType        #20:#12        // "<init>":()V
  #24 = Fieldref           #1.#25         // com/demo/Temp4Test.i:I
  #25 = NameAndType        #5:#6          // i:I
  #26 = Fieldref           #1.#27         // com/demo/Temp4Test.f:F
  #27 = NameAndType        #7:#8          // f:F
  #28 = Utf8               this
  #29 = Utf8               Lcom/demo/Temp4Test;
  #30 = Utf8               ii
  #31 = Utf8               str
  #32 = Utf8               ff
  #33 = Utf8               main
  #34 = Utf8               ([Ljava/lang/String;)V
  #35 = String             #36            // hello
  #36 = Utf8               hello
  #37 = Float              5.5f
  #38 = Methodref          #1.#39         // com/demo/Temp4Test."<init>":(ILjava/lang/String;F)V
  #39 = NameAndType        #20:#21        // "<init>":(ILjava/lang/String;F)V
  #40 = Fieldref           #41.#43        // java/lang/System.out:Ljava/io/PrintStream;
  #41 = Class              #42            // java/lang/System
  #42 = Utf8               java/lang/System
  #43 = NameAndType        #44:#45        // out:Ljava/io/PrintStream;
  #44 = Utf8               out
  #45 = Utf8               Ljava/io/PrintStream;
  #46 = Methodref          #47.#49        // java/io/PrintStream.println:(Ljava/lang/Object;)V
  #47 = Class              #48            // java/io/PrintStream
  #48 = Utf8               java/io/PrintStream
  #49 = NameAndType        #50:#51        // println:(Ljava/lang/Object;)V
  #50 = Utf8               println
  #51 = Utf8               (Ljava/lang/Object;)V
  #52 = Utf8               args
  #53 = Utf8               [Ljava/lang/String;
  #54 = Utf8               t4
  #55 = Utf8               toString
  #56 = Utf8               ()Ljava/lang/String;
  #57 = Class              #58            // java/lang/StringBuilder
  #58 = Utf8               java/lang/StringBuilder
  #59 = String             #60            // [
  #60 = Utf8               [
  #61 = Methodref          #57.#62        // java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
  #62 = NameAndType        #20:#63        // "<init>":(Ljava/lang/String;)V
  #63 = Utf8               (Ljava/lang/String;)V
  #64 = Methodref          #57.#65        // java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
  #65 = NameAndType        #66:#67        // append:(I)Ljava/lang/StringBuilder;
  #66 = Utf8               append
  #67 = Utf8               (I)Ljava/lang/StringBuilder;
  #68 = String             #69            //  ,
  #69 = Utf8                ,
  #70 = Methodref          #57.#71        // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  #71 = NameAndType        #66:#72        // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  #72 = Utf8               (Ljava/lang/String;)Ljava/lang/StringBuilder;
  #73 = Methodref          #57.#74        // java/lang/StringBuilder.append:(F)Ljava/lang/StringBuilder;
  #74 = NameAndType        #66:#75        // append:(F)Ljava/lang/StringBuilder;
  #75 = Utf8               (F)Ljava/lang/StringBuilder;
  #76 = String             #77            // ]
  #77 = Utf8               ]
  #78 = Methodref          #57.#79        // java/lang/StringBuilder.toString:()Ljava/lang/String;
  #79 = NameAndType        #55:#56        // toString:()Ljava/lang/String;
  #80 = Utf8               SourceFile
  #81 = Utf8               Temp4Test.java

    该类文件的常量池,对比一下上一篇的二进制格式,是不是清楚多了。简单说一下上述文本的读法。

    1号常量为Class类型,其定义名指向了2号常量,2号常量为一个字符串值,该值为com/demo/Temp4Test,这两个常量联合起来定义了一个类的具体值。16号常量为Fieldref类型,下图为十四种常量的定义格式,从下图中找到Fieldref类型(第九种),指向声明字段的类或者接口描述符的值为1,即指向1号常量,指向字段描述符CONSTANT_NameAndType的索引值为17,指向第17号常量,是一个NameAndType类型,然后再从下表中找到该类型(第12种),从第17号常理中分别得到该种常量所对应的两个索引:9号常量thisstr为字段名称,10号常量为字段描述符,字段描述符的含义在上一篇中也讲过,在此不再展开。按此方法可以理解全部常量池的常量内容,再查看下上面文件的右侧类似代码中的注释部分,已经自行将所关联的跳转标出,直接看这个也能得到各值的含义。

    

    

    3.第三段,是剩余部分,这一部分可以看成是源代码的一个类似表述,对字节码熟悉的话可以从这一段还原源码逻辑,这一段比较长,分为小段,一段一段的看

        3.1 变量定义部分

  public float f;
    descriptor: F
    flags: ACC_PUBLIC

  public static java.lang.String thisstr;
    descriptor: Ljava/lang/String;
    flags: ACC_PUBLIC, ACC_STATIC

        这一部分是源码中的变量定义,包含:变量名、类型、访问权限,上面非常清楚,一目了然,不再赘述

        3.2 静态初始化块

  static {};
    descriptor: ()V
    flags: ACC_STATIC
    Code:
      stack=1, locals=0, args_size=0
         0: ldc           #14                 // String
         2: putstatic     #16                 // Field thisstr:Ljava/lang/String;
         5: return
      LineNumberTable:
        line 7: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature

        类中的静态块,为什么会有这个模块,然后这个模块为什么要放在此处呢,这个和Java中的类构造器cinit()、类变量(static变量)的定义及访问原理有关,感兴趣的同学可以研究下Java的类加载过程中的初始化阶段,推荐《深入理解Java虚拟机》的第七章对于该块实现的描述,这里简单说一下与本例有关的,首先,因为源码中有静态类变量thisstr。因此,此处有静态块,静态块一定是位于方法区的最前方,编译器收集是按字节码的先后顺序收集的。这个模块中有一个LineNumberTable部分,是什么意思呢,标识的是对应索引的字节码在源码中的位置,本例中为第0行字节码(:后的值)对应于源码第7行(: 前的值),我们去看看第7行是什么?下图是我的IDE中的代码标示,正是静态类变量的定义位置:

        3.3 类构造方法

public com.demo.Temp4Test(int, java.lang.String, float);
    descriptor: (ILjava/lang/String;F)V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=4, args_size=4
         0: aload_0
         1: invokespecial #22                 // Method com/demo/Temp3Test."<init>":()V
         4: aload_0
         5: iconst_1
         6: putfield      #24                 // Field i:I
         9: aload_0
        10: iload_1
        11: putfield      #24                 // Field i:I
        14: aload_2
        15: putstatic     #16                 // Field thisstr:Ljava/lang/String;
        18: aload_0
        19: fload_3
        20: putfield      #26                 // Field f:F
        23: return
      LineNumberTable:
        line 9: 0
        line 5: 4
        line 10: 9
        line 11: 14
        line 12: 18
        line 13: 23
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      24     0  this   Lcom/demo/Temp4Test;
            0      24     1    ii   I
            0      24     2   str   Ljava/lang/String;
            0      24     3    ff   F

        flag为访问权限及类型,同下图:

        

        code为源码转换成的字节码,此部分不在这篇中展开描述了,之后的文档中会专门讲字节码怎么解读

        LineNumberTable显示了上面字节码部分所用到的逻辑在源码中的位置

        LocalVariableTable中的start--Length表示后面的变量在字节码中的作用范转为第start行到start+length-1行(从0开始),Slot为与栈桢有关的值

        3.4 main方法

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=5, locals=2, args_size=1
         0: new           #1                  // class com/demo/Temp4Test
         3: dup
         4: bipush        100
         6: ldc           #35                 // String hello
         8: ldc           #37                 // float 5.5f
        10: invokespecial #38                 // Method "<init>":(ILjava/lang/String;F)V
        13: astore_1
        14: getstatic     #40                 // Field java/lang/System.out:Ljava/io/PrintStream;
        17: aload_1
        18: invokevirtual #46                 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
        21: return
      LineNumberTable:
        line 16: 0
        line 17: 14
        line 18: 21
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      22     0  args   [Ljava/lang/String;
           14       8     1    t4   Lcom/demo/Temp4Test;

        3.4 toString()方法

  public java.lang.String toString();
    descriptor: ()Ljava/lang/String;
    flags: ACC_PUBLIC
    Code:
      stack=3, locals=1, args_size=1
         0: new           #57                 // class java/lang/StringBuilder
         3: dup
         4: ldc           #59                 // String [
         6: invokespecial #61                 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
         9: aload_0
        10: getfield      #24                 // Field i:I
        13: invokevirtual #64                 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
        16: ldc           #68                 // String  ,
        18: invokevirtual #70                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        21: getstatic     #16                 // Field thisstr:Ljava/lang/String;
        24: invokevirtual #70                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        27: ldc           #68                 // String  ,
        29: invokevirtual #70                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        32: aload_0
        33: getfield      #26                 // Field f:F
        36: invokevirtual #73                 // Method java/lang/StringBuilder.append:(F)Ljava/lang/StringBuilder;
        39: ldc           #76                 // String ]
        41: invokevirtual #70                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        44: invokevirtual #78                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        47: areturn
      LineNumberTable:
        line 22: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      48     0  this   Lcom/demo/Temp4Test;

        以上的两个方法分析方法同上,不再详述。

猜你喜欢

转载自blog.csdn.net/kcstrong/article/details/81233672