代码不想写,我拿什么拯救你,copy吧,少年:https://github.com/singgel/eight-sorting-algorithms/tree/master/src/test/java/com/hks/eightsortingalgorithms/script
Write Once,Run Anywhere——byteCode
byteCode 平台无关
java通过存储编译后的字节码,并将字码加载到JVM中,实现了java语言的跨平台。字节码是平台中立的代码存储格式,任意一个平台只要安装了JRE(跟平台有关),那么程序就是可以运行的。
byteCode 语言无关
除此之外,更值得注意的是字节码不仅是平台无关的,也同样是语言无关的,并不是说只有java语言才能生成byteCode,byteCode比java语言有着更强的描述性,而且JVM不与语言进行绑定,所以其他语言(Jython,groovy等),也是可以通过编译为byteCode运行在JVM中的。
1.Class文件基础
(1)文件格式
Class文件的结构不像XML等描述语言那样松散自由。由于它没有任何分隔符号,
所以,以上数据项无论是顺序还是数量都是被严格限定的。哪个字节代表什么
含义,长度是多少,先后顺序如何,都不允许改变。
(2)数据类型
仔细观察上面的Class文件格式,可以看出Class文件格式采用一种类似于C语言
结构体的伪结构来存储,这种伪结构中只有两种数据类型:无符号数和表。
无符号数就是u1、u2、u4、u8来分别代表1个、2个、4个、8个字节。表是由
多个无符号数或其他表构成的复合数据类型,以“_info”结尾。在表开始位置,
通常会使用一个前置的容量计数器,因为表通常要描述数量不定的多个数据。
下图表示的就是Class文件格式中按顺序各个数据项的类型:
(3)兼容性
高版本的JDK能向下兼容以前版本的Class文件,但不能运行以后版本的Class文件,
即使文件格式未发生任何变化。举例来说,JDK 1.7中的JRE能够执行JDK 1.5编译
出的Class文件,但是JDK 1.7编译出来的Class文件不能被JDK 1.5使用。这就是
target参数的用处,可以在使用JDK 1.7编译时指定-target 1.5。
2.一个简单的例子
package com.hks.eightsortingalgorithms.asm;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class Hello {
public static final String FLAG = "我是常量";
public Hello() {
}
public void display() {
for(int var1 = 0; var1 < 8; ++var1) {
System.out.println(">>>>>>>>>>我是常量");
}
}
public List<String> testList() {
ArrayList var1 = new ArrayList();
var1.add("Tome");
var1.add("Jack");
var1.add("Lily");
System.out.println(">>>>>>>>>>testList > list.size = " + var1.size());
return var1;
}
public List<Map<String, String>> testMapList(boolean var1, Map... var2) {
ArrayList var3 = new ArrayList();
if (var1) {
Map[] var7 = var2;
int var6 = var2.length;
for(int var5 = 0; var5 < var6; ++var5) {
Map var4 = var7[var5];
var3.add(var4);
}
}
System.out.println(">>>>>>>>>>testMapList > list.size = " + var3.size());
return var3;
}
}
编译成Class文件后的样子:
3.逐个字节分析
(1)魔数和版本号
前四个字节(u4)cafebabe就是Class文件的魔数,第5、6字节(u2)是Class文件的
次版本号,第7、8字节(u2)是主版本号。十六进制0和32,也就是版本号为50.0,
即JDK 1.6。之前介绍的target参数会影响这四个字节的值,从而使Class文件兼容不同
的JDK版本。
(2)常量池
常量池是一个表结构,并且就像之前介绍过的,在表的内容前有一个u2类型的计数器,
表示常量池的长度。十六进制23的十进制值为35,表示常量池里有下标为1~34的表项。
下标从1开始而不是0,是因为第0个表项表示“不引用常量池中的任意一项”。每个表项
的第一个字节是一个u1类型,表示12中数据类型。具体含义如下:
以第一项07 00 02为例,07表示该常量是个CONSTANT_Class_info类型,紧接着一个u2
类型的索引执行第2项常量。再看第二项01 00 24 63 6f 6d 2f ... 65表示的就是字符串
类型,长度为36(十六进制00 24),紧接着就是UTF-8编码的字符串"com/hks/eightsortingalgorithms/asm/hello/Hello"。很容易读懂吧?常量池主要是为后面的字段表和方法表服务的。
下面是通过javap解析后常量池的全貌(执行javap -c -l -s -verbose Hello)
Classfile /Users/yiche/github/eight-sorting-algorithms/src/test/java/com/hks/eightsortingalgorithms/asm/hello/Hello.class
Last modified Oct 19, 2018; size 1422 bytes
MD5 checksum 060a2add21cb5569a11f8949de66a50a
Compiled from "Hello.java"
public class com.hks.eightsortingalgorithms.asm.Hello
minor version: 0
major version: 51
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Utf8 com/hks/eightsortingalgorithms/asm/Hello
#2 = Class #1 // com/hks/eightsortingalgorithms/asm/Hello
#3 = Utf8 java/lang/Object
#4 = Class #3 // java/lang/Object
#5 = Utf8 Hello.java
#6 = Utf8 FLAG
#7 = Utf8 Ljava/lang/String;
#8 = Utf8 我是常量
#9 = String #8 // 我是常量
#10 = Utf8 <init>
#11 = Utf8 ()V
#12 = NameAndType #10:#11 // "<init>":()V
#13 = Methodref #4.#12 // java/lang/Object."<init>":()V
#14 = Utf8 display
#15 = Utf8 java/lang/System
#16 = Class #15 // java/lang/System
#17 = Utf8 out
#18 = Utf8 Ljava/io/PrintStream;
#19 = NameAndType #17:#18 // out:Ljava/io/PrintStream;
#20 = Fieldref #16.#19 // java/lang/System.out:Ljava/io/PrintStream;
#21 = Utf8 >>>>>>>>>>我是常量
#22 = String #21 // >>>>>>>>>>我是常量
#23 = Utf8 java/io/PrintStream
#24 = Class #23 // java/io/PrintStream
#25 = Utf8 println
#26 = Utf8 (Ljava/lang/String;)V
#27 = NameAndType #25:#26 // println:(Ljava/lang/String;)V
#28 = Methodref #24.#27 // java/io/PrintStream.println:(Ljava/lang/String;)V
#29 = Utf8 testList
#30 = Utf8 ()Ljava/util/List;
#31 = Utf8 ()Ljava/util/List<Ljava/lang/String;>;
#32 = Utf8 java/util/ArrayList
#33 = Class #32 // java/util/ArrayList
#34 = Methodref #33.#12 // java/util/ArrayList."<init>":()V
#35 = Utf8 Tome
#36 = String #35 // Tome
#37 = Utf8 java/util/List
#38 = Class #37 // java/util/List
#39 = Utf8 add
#40 = Utf8 (Ljava/lang/Object;)Z
#41 = NameAndType #39:#40 // add:(Ljava/lang/Object;)Z
#42 = InterfaceMethodref #38.#41 // java/util/List.add:(Ljava/lang/Object;)Z
#43 = Utf8 Jack
#44 = String #43 // Jack
#45 = Utf8 Lily
#46 = String #45 // Lily
#47 = Utf8 java/lang/StringBuilder
#48 = Class #47 // java/lang/StringBuilder
#49 = Utf8 >>>>>>>>>>testList > list.size =
#50 = String #49 // >>>>>>>>>>testList > list.size =
#51 = NameAndType #10:#26 // "<init>":(Ljava/lang/String;)V
#52 = Methodref #48.#51 // java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
#53 = Utf8 size
#54 = Utf8 ()I
#55 = NameAndType #53:#54 // size:()I
#56 = InterfaceMethodref #38.#55 // java/util/List.size:()I
#57 = Utf8 append
#58 = Utf8 (I)Ljava/lang/StringBuilder;
#59 = NameAndType #57:#58 // append:(I)Ljava/lang/StringBuilder;
#60 = Methodref #48.#59 // java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
#61 = Utf8 toString
#62 = Utf8 ()Ljava/lang/String;
#63 = NameAndType #61:#62 // toString:()Ljava/lang/String;
#64 = Methodref #48.#63 // java/lang/StringBuilder.toString:()Ljava/lang/String;
#65 = Utf8 testMapList
#66 = Utf8 (Z[Ljava/util/Map;)Ljava/util/List;
#67 = Utf8 (Z[Ljava/util/Map<Ljava/lang/String;Ljava/lang/String;>;)Ljava/util/List<Ljava/util/Map<Ljava/lang/String;Ljava/lang/String;>;>;
#68 = Utf8 [Ljava/util/Map;
#69 = Class #68 // "[Ljava/util/Map;"
#70 = Utf8 >>>>>>>>>>testMapList > list.size =
#71 = String #70 // >>>>>>>>>>testMapList > list.size =
#72 = Utf8 ConstantValue
#73 = Utf8 Code
#74 = Utf8 StackMapTable
#75 = Utf8 Signature
#76 = Utf8 SourceFile
{
public static final java.lang.String FLAG;
descriptor: Ljava/lang/String;
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
ConstantValue: String 我是常量
public com.hks.eightsortingalgorithms.asm.Hello();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #13 // Method java/lang/Object."<init>":()V
4: return
public void display();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=2, args_size=1
0: iconst_0
1: istore_1
2: goto 16
5: getstatic #20 // Field java/lang/System.out:Ljava/io/PrintStream;
8: ldc #22 // String >>>>>>>>>>我是常量
10: invokevirtual #28 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
13: iinc 1, 1
16: iload_1
17: bipush 8
19: if_icmplt 5
22: return
StackMapTable: number_of_entries = 2
frame_type = 252 /* append */
offset_delta = 5
locals = [ int ]
frame_type = 10 /* same */
public java.util.List<java.lang.String> testList();
descriptor: ()Ljava/util/List;
flags: ACC_PUBLIC
Code:
stack=4, locals=2, args_size=1
0: new #33 // class java/util/ArrayList
3: dup
4: invokespecial #34 // Method java/util/ArrayList."<init>":()V
7: astore_1
8: aload_1
9: ldc #36 // String Tome
11: invokeinterface #42, 2 // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
16: pop
17: aload_1
18: ldc #44 // String Jack
20: invokeinterface #42, 2 // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
25: pop
26: aload_1
27: ldc #46 // String Lily
29: invokeinterface #42, 2 // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
34: pop
35: getstatic #20 // Field java/lang/System.out:Ljava/io/PrintStream;
38: new #48 // class java/lang/StringBuilder
41: dup
42: ldc #50 // String >>>>>>>>>>testList > list.size =
44: invokespecial #52 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
47: aload_1
48: invokeinterface #56, 1 // InterfaceMethod java/util/List.size:()I
53: invokevirtual #60 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
56: invokevirtual #64 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
59: invokevirtual #28 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
62: aload_1
63: areturn
Signature: #31 // ()Ljava/util/List<Ljava/lang/String;>;
public java.util.List<java.util.Map<java.lang.String, java.lang.String>> testMapList(boolean, java.util.Map<java.lang.String, java.lang.String>...);
descriptor: (Z[Ljava/util/Map;)Ljava/util/List;
flags: ACC_PUBLIC, ACC_VARARGS
Code:
stack=4, locals=8, args_size=3
0: new #33 // class java/util/ArrayList
3: dup
4: invokespecial #34 // Method java/util/ArrayList."<init>":()V
7: astore_3
8: iload_1
9: ifeq 51
12: aload_2
13: dup
14: astore 7
16: arraylength
17: istore 6
19: iconst_0
20: istore 5
22: goto 44
25: aload 7
27: iload 5
29: aaload
30: astore 4
32: aload_3
33: aload 4
35: invokeinterface #42, 2 // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
40: pop
41: iinc 5, 1
44: iload 5
46: iload 6
48: if_icmplt 25
51: getstatic #20 // Field java/lang/System.out:Ljava/io/PrintStream;
54: new #48 // class java/lang/StringBuilder
57: dup
58: ldc #71 // String >>>>>>>>>>testMapList > list.size =
60: invokespecial #52 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
63: aload_3
64: invokeinterface #56, 1 // InterfaceMethod java/util/List.size:()I
69: invokevirtual #60 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
72: invokevirtual #64 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
75: invokevirtual #28 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
78: aload_3
79: areturn
StackMapTable: number_of_entries = 3
frame_type = 255 /* full_frame */
offset_delta = 25
locals = [ class com/hks/eightsortingalgorithms/asm/Hello, int, class "[Ljava/util/Map;", class java/util/List, top, int, int, class "[Ljava/util/Map;" ]
stack = []
frame_type = 18 /* same */
frame_type = 255 /* full_frame */
offset_delta = 6
locals = [ class com/hks/eightsortingalgorithms/asm/Hello, int, class "[Ljava/util/Map;", class java/util/List ]
stack = []
Signature: #67 // (Z[Ljava/util/Map<Ljava/lang/String;Ljava/lang/String;>;)Ljava/util/List<Ljava/util/Map<Ljava/lang/String;Ljava/lang/String;>;>;
}
SourceFile: "Hello.java"
(3)访问标志
显然,00 21表示的就是公有的类。
(4)类、父类、接口
这三个u2类型的值分别表示类索引1、父类索引3、接口索引集合0。查看之前的常量池,
第1项为"com/cdai/jvm/bytecode/ByteCodeSample",第3项为"java/lang/Object"。第0项
表示此类没有实现任何接口,这也就是常量池第0项的作用!
(5)字段表
00 01表示有1个字段。00 02是字段的访问标志,表示private权限的。00 05是字段的名称
索引,指向常量池里第5项"msg"。00 06是字段的描述符索引,指向常量池里的第6项
"Ljava/lang/String"。最后的00 00表示该字段没有其他属性表了。
描述符的作用就是用来描述字段的数据类型、方法的参数列表和返回值。而属性表就是为
字段表和方法表提供额外信息的表结构。对于字段来说,此处如果将字段声明为一个static
final msg = "aaa"的常量,则字段后就会跟着一个属性表,其中存在一项名为ConstantValue,
指向常量池中的一个常量,值为的"aaa"。
属性表不像Class文件中的其他数据项那样具有严格的顺序、长度和内容,任何人实现的编译器
都可以向属性表中写入自己定义的属性信息,JVM会忽略掉它不认识的属性。后面的方法表中
还要用到属性表的Code属性,来保存方法的字节码。
(6)方法表
00 02表示有两个方法。00 01是方法的访问标志,表示公有方法。00 07和00 08与字段表中的名称
和描述符索引相同,在这里分别表示"<init>"和"()V"。00 01表示该方法有属性表,属性名称为00 09
即我们前面提到的Code属性。
要注意的是:Code属性表也可以有自己的属性,如后面的LocalVariableTable和LineNumberTable。
它们分别为JVM提供方法的栈信息和调试信息。