这个文章是一个系列,准备写至少四篇吧,离上一篇写得已经有几个月了,一个没有出第二篇,原因就一个字:懒。其实整理文档挺耗时的,之后一直在关注老罗的Blog,老罗说他一篇Blog要花一周时间,我深以为然,随便写写肯定省时间 ,但对不起写这个事情,话不多说,进入正题吧。
还是上一篇https://blog.youkuaiyun.com/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;
以上的两个方法分析方法同上,不再详述。