一、JVM加载过程
openJDK源码:http://hg.openjdk.java.net/jdk8u
二、Class文件
2.1 什么是class文件
2.2 解析class文件
查看class文件结构命令:javap -verbose StringTest.class
下图是对class文件转成16进制的部分结构图
图2.2.1
根据官网class规范(网址:https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html)可获得下图:
图2.2.2
16进制 10进制
ca fe ba be 代表class文件
0000 0 代表class文件副版本号 minor version: 0
0033 51 主版本号 major version: 51
004c 76 常量池个数(Constant pool长度+1) constant_pool[constant_pool_count-1];
public class com.naixue.vip.p6.jvm.bytecode.StringTest
minor version: 0
major version: 51
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #17 // com/naixue/vip/p6/jvm/bytecode/StringTest
super_class: #18 // java/lang/Object
interfaces: 0, fields: 0, methods: 5, attributes: 1
Constant pool:
#1 = Methodref #18.#45 // java/lang/Object."<init>":()V
#2 = String #46 //
#3 = Class #47 // java/lang/StringBuilder
#4 = Methodref #3.#45 // java/lang/StringBuilder."<init>":()V
#5 = Methodref #3.#48 // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#6 = String #49 // test,
#7 = Methodref #3.#50 // java/lang/StringBuilder.toString:()Ljava/lang/String;
#8 = Fieldref #51.#52 // java/lang/System.out:Ljava/io/PrintStream;
#9 = Methodref #53.#54 // java/io/PrintStream.println:(Ljava/lang/String;)V
#10 = Class #55 // java/lang/StringBuffer
#11 = Methodref #10.#45 // java/lang/StringBuffer."<init>":()V
#12 = Methodref #10.#56 // java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer;
#13 = Methodref #53.#57 // java/io/PrintStream.println:(Ljava/lang/Object;)V
#14 = String #58 // hello
#15 = String #59 // samuel
#16 = Methodref #17.#60 // com/naixue/vip/p6/jvm/bytecode/StringTest.test3:()Ljava/lang/String;
#17 = Class #61 // com/naixue/vip/p6/jvm/bytecode/StringTest
#18 = Class #62 // java/lang/Object
#19 = Utf8 <init>
#20 = Utf8 ()V
#21 = Utf8 Code
#22 = Utf8 LineNumberTable
#23 = Utf8 LocalVariableTable
#24 = Utf8 this
#25 = Utf8 Lcom/naixue/vip/p6/jvm/bytecode/StringTest;
#26 = Utf8 test1
#27 = Utf8 i
#28 = Utf8 I
#29 = Utf8 str
#30 = Utf8 Ljava/lang/String;
#31 = Utf8 StackMapTable
#32 = Class #63 // java/lang/String
#33 = Utf8 test2
#34 = Utf8 Ljava/lang/StringBuffer;
#35 = Class #55 // java/lang/StringBuffer
#36 = Utf8 test3
#37 = Utf8 ()Ljava/lang/String;
#38 = Class #64 // java/lang/Throwable
#39 = Utf8 main
#40 = Utf8 ([Ljava/lang/String;)V
#41 = Utf8 args
#42 = Utf8 [Ljava/lang/String;
#43 = Utf8 SourceFile
#44 = Utf8 StringTest.java
#45 = NameAndType #19:#20 // "<init>":()V
#46 = Utf8
#47 = Utf8 java/lang/StringBuilder
#48 = NameAndType #65:#66 // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#49 = Utf8 test,
#50 = NameAndType #67:#37 // toString:()Ljava/lang/String;
#51 = Class #68 // java/lang/System
#52 = NameAndType #69:#70 // out:Ljava/io/PrintStream;
#53 = Class #71 // java/io/PrintStream
#54 = NameAndType #72:#73 // println:(Ljava/lang/String;)V
#55 = Utf8 java/lang/StringBuffer
#56 = NameAndType #65:#74 // append:(Ljava/lang/String;)Ljava/lang/StringBuffer;
#57 = NameAndType #72:#75 // println:(Ljava/lang/Object;)V
#58 = Utf8 hello
#59 = Utf8 samuel
#60 = NameAndType #36:#37 // test3:()Ljava/lang/String;
#61 = Utf8 com/naixue/vip/p6/jvm/bytecode/StringTest
#62 = Utf8 java/lang/Object
#63 = Utf8 java/lang/String
#64 = Utf8 java/lang/Throwable
#65 = Utf8 append
#66 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder;
#67 = Utf8 toString
#68 = Utf8 java/lang/System
#69 = Utf8 out
#70 = Utf8 Ljava/io/PrintStream;
#71 = Utf8 java/io/PrintStream
#72 = Utf8 println
#73 = Utf8 (Ljava/lang/String;)V
#74 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuffer;
#75 = Utf8 (Ljava/lang/Object;)V
图2.2.3
由上面三图可解析:
u4代表4个字节ca fe ba be代表class文件
u2 代表5,6字节0000转十进制为0代表class文件副版本号 minor version: 0
u2 代表7,8字节0033转十进制为51代表class主版本号 major version: 51
u2 代表9,10字节004c转十进制为76 代表class常量池个数,但由上图只能看到常量池个数为75,因为索引0位置是虚拟机预留的,不可使用。
CONSTANT_Class_info { u1 tag; u2 name_index; }
tag是标识,根据本小节的字节码文件可得0a转十进制为10。tag标识对应结构如下图:
10代表是CONSTANT_Methodref_info的结构,如下:
CONSTANT_Methodref_info { u1 tag; u2 class_index; 0012转十进制18 u2 name_and_type_index; 002d转十进制45 }
可由图2.2.3中,看到Methodref,同时有2个值分别的18和45,和上面的解析相对应
#1 = Methodref #18.#45 // java/lang/Object."<init>":()
class文件解析作用:可以做字节码增强,可通过cglib(通过asm实现),aspectj,java Proxy,javassist。比如一些jvm调优工具,一些挂载,热部署的实现也是通过字节码技术来实现。
2.3 从字节码上看String和StringBuffer的区别
/**
* 格式:操作码+操作数 istore_1
* 加载和存储执行
* iload_X:从局部变量表中加载int类型的数据到操作数栈 lload fload dload aload
* ldc ldc2 ldc_w bipush。。。常量加载到操作数栈
* 对象的创建和访问指令
* new
* newarray XXXarray
* getfield pupfild getstatic putstatic
* 控制转移指令
* ifeq iflt ifle ifgt
* goto goto_w
* 方法调用指令
* invokevirtual 调用实例方法
* invokestatic 调用静态方法
* invokeintface
* invokeXXXX
*/
public static void test1(){
String str = "";
for (int i=0;i<10;i++){
str = str+"test,";
}
System.out.println(str);
}
public static void test2(){
StringBuffer str = new StringBuffer();
for (int i=0;i<10;i++){
str.append("test,");
}
System.out.println(str);
}
public static void test1();
descriptor: ()V
flags: (0x0009) ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=2, args_size=0 //stack栈的深度,locals本地变量表的长度, args_size 形参列表的长度
0: ldc #2 // String
2: astore_0
3: iconst_0
4: istore_1
5: iload_1
6: bipush 10
8: if_icmpge 37
11: new #3 // class java/lang/StringBuilder
14: dup
15: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V
18: aload_0
19: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
22: ldc #6 // String test,
24: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
27: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
30: astore_0
31: iinc 1, 1
34: goto 5
37: getstatic #8 // Field java/lang/System.out:Ljava/io/PrintStream;
40: aload_0
41: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
44: return
LineNumberTable:
line 7: 0
line 8: 3
line 9: 11
line 8: 31
line 11: 37
line 13: 44
LocalVariableTable:
Start Length Slot Name Signature
5 32 1 i I
3 42 0 str Ljava/lang/String;
StackMapTable: number_of_entries = 2
frame_type = 253 /* append */
offset_delta = 5
locals = [ class java/lang/String, int ]
frame_type = 250 /* chop */
offset_delta = 31
public static void test2();
descriptor: ()V
flags: (0x0009) ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=2, args_size=0
0: new #10 // class java/lang/StringBuffer
3: dup
4: invokespecial #11 // Method java/lang/StringBuffer."<init>":()V
7: astore_0
8: iconst_0
9: istore_1
10: iload_1
11: bipush 10
13: if_icmpge 29
16: aload_0
17: ldc #6 // String test,
19: invokevirtual #12 // Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer;
22: pop
23: iinc 1, 1
26: goto 10
29: getstatic #8 // Field java/lang/System.out:Ljava/io/PrintStream;
32: aload_0
33: invokevirtual #13 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
36: return
LineNumberTable:
line 16: 0
line 17: 8
line 18: 16
line 17: 23
line 20: 29
line 22: 36
LocalVariableTable:
Start Length Slot Name Signature
10 19 1 i I
8 29 0 str Ljava/lang/StringBuffer;
StackMapTable: number_of_entries = 2
frame_type = 253 /* append */
offset_delta = 10
locals = [ class java/lang/StringBuffer, int ]
frame_type = 250 /* chop */
offset_delta = 18
上面三图分别展示了
1、方法中一些常用指令格式的解析。
2、String和StringBuffer的测试代码
3、2中代码的字节码文件结构
可以看到test1()方法使String进行拼接时,每次拼接都会创建一个StringBuffer对象进行拼接,而test2()只会创建一个StringBuffer对象。