JVM第十一篇(类加载与字节码技术二)

接着上一节,研究一下两组字节码指令,
一个是 public cn.itcast.jvm.t5.HelloWorld(); 构造方法的字节码指令

2a b7 00 01 b1
  1. 2a 对应的指令为 aload_0,加载 slot 0 的局部变量,即 this,做为下面的 invokespecial 构造方法的参数
  2. b7 对应的指令为 invokespecial 预备调用构造方法,通过 00 01 找到要调用的方法。
  3. 00 01 引用常量池中 #1 项,即【 Method java/lang/Object.“”😦)V 】,Object中的返回值为void 的init方法
  4. b1 对应的指令为 return ,表示调用完方法后返回。

通过上面指令的分析可以得出,在 HelloWorld() 这个构造方法中的指令,是调用 Object 中的 init方法。

另一个是 public static void main(java.lang.String[]); 主方法的字节码指令

b2 00 02 12 03 b6 00 04 b1
  1. b2 对应的指令为 getstatic 用来加载静态变量。加载的静态变量由后面2个字节指明。
  2. 00 02引用常量池中 #2 项,即【Field java/lang/System.out:Ljava/io/PrintStream;】
  3. 12 => ldc 加载参数,参数有后面一个字节指明。
  4. 03 引用常量池中 #3 项,即 【String hello world】
  5. b6 => invokevirtual 预备调用成员方法,调用的方法由后面的两个字节指明。
  6. 00 04 引用常量池中 #4 项,即【Method java/io/PrintStream.println:(Ljava/lang/String;)V】
  7. b1对应的指令为 return 表示返回

通过对上面的指令进行分析,可以得知,执行 java/io/PrintStream类下的参数为Ljava/lang/String,返回值为void 的 println方法。

参考文档

javap工具

Oracle 提供了 javap 工具来反编译 class 文件,使得分析字节码文件更加方便。

使用命令:javap -v HelloWorld.class

下面是使用 javap 反编译的 class文件。

Classfile /root/HelloWorld.class
Last modified Jul 7, 2019; size 597 bytes
MD5 checksum 361dca1c3f4ae38644a9cd5060ac6dbc
Compiled from "HelloWorld.java"
public class cn.itcast.jvm.t5.HelloWorld
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER

Constant pool:
#1 = Methodref #6.#21 // java/lang/Object."<init>":()V
#2 = Fieldref #22.#23 //
java/lang/System.out:Ljava/io/PrintStream;
#3 = String #24 // hello world
#4 = Methodref #25.#26 // java/io/PrintStream.println:
(Ljava/lang/String;)V
#5 = Class #27 // cn/itcast/jvm/t5/HelloWorld
#6 = Class #28 // java/lang/Object
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 LocalVariableTable
#12 = Utf8 this
#13 = Utf8 Lcn/itcast/jvm/t5/HelloWorld;#14 = Utf8 main
#15 = Utf8 ([Ljava/lang/String;)V
#16 = Utf8 args
#17 = Utf8 [Ljava/lang/String;
#18 = Utf8 MethodParameters
#19 = Utf8 SourceFile
#20 = Utf8 HelloWorld.java
#21 = NameAndType #7:#8 // "<init>":()V
#22 = Class #29 // java/lang/System
#23 = NameAndType #30:#31 // out:Ljava/io/PrintStream;
#24 = Utf8 hello world
#25 = Class #32 // java/io/PrintStream
#26 = NameAndType #33:#34 // println:(Ljava/lang/String;)V
#27 = Utf8 cn/itcast/jvm/t5/HelloWorld
#28 = Utf8 java/lang/Object
#29 = Utf8 java/lang/System
#30 = Utf8 out
#31 = Utf8 Ljava/io/PrintStream;
#32 = Utf8 java/io/PrintStream
#33 = Utf8 println
#34 = Utf8 (Ljava/lang/String;)V

{
	public cn.itcast.jvm.t5.HelloWorld();
	descriptor: ()V
	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 4: 0
	LocalVariableTable:
	Start Length Slot  Name  Signature
	  0 	5 	   0   this  Lcn/itcast/jvm/t5/HelloWorld;
	public static void main(java.lang.String[]);
	descriptor: ([Ljava/lang/String;)V
	flags: ACC_PUBLIC, ACC_STATIC
	Code:
		stack=2, locals=1, args_size=1
		0: getstatic #2 	// Field   java/lang/System.out:Ljava/io/PrintStream;
		3: ldc #3 			// String  hello world
		5: invokevirtual #4 // Method  java/io/PrintStream.println:(Ljava/lang/String;)V
		8: return
	LineNumberTable:
	line 6: 0
	line 7: 8
	LocalVariableTable:
		Start Length Slot  Name    Signature
	 	 0 	9 	   0   args    [Ljava/lang/String;
	MethodParameters:
		Name 	Flags
		args
}

通过使用 javap 工具可以更直观的分析 class 文件。

图解方法的执行流程

演示代码

package cn.itcast.jvm.t3.bytecode;
/**
* 演示 字节码指令 和 操作数栈、常量池的关系
*/
public class Demo3_1 {
	public static void main(String[] args) {
		int a = 10;
		int b = Short.MAX_VALUE + 1;
		int c = a + b;
		System.out.println(c);
	}
}
Classfile /root/Demo3_1.class
Last modified Jul 7, 2019; size 665 bytes
MD5 checksum a2c29a22421e218d4924d31e6990cfc5
Compiled from "Demo3_1.java"
public class cn.itcast.jvm.t3.bytecode.Demo3_1
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #7.#26 // java/lang/Object."<init>":()V
#2 = Class #27 // java/lang/Short
#3 = Integer 32768
#4 = Fieldref #28.#29 //
java/lang/System.out:Ljava/io/PrintStream;
#5 = Methodref #30.#31 // java/io/PrintStream.println:(I)V
#6 = Class #32 // cn/itcast/jvm/t3/bytecode/Demo3_1
#7 = Class #33 // java/lang/Object
#8 = Utf8 <init>
#9 = Utf8 ()V
#10 = Utf8 Code
#11 = Utf8 LineNumberTable
#12 = Utf8 LocalVariableTable
#13 = Utf8 this
#14 = Utf8 Lcn/itcast/jvm/t3/bytecode/Demo3_1;
#15 = Utf8 main
#16 = Utf8 ([Ljava/lang/String;)V
#17 = Utf8 args
#18 = Utf8 [Ljava/lang/String;
#19 = Utf8 a#20 = Utf8 I
#21 = Utf8 b
#22 = Utf8 c
#23 = Utf8 MethodParameters
#24 = Utf8 SourceFile
#25 = Utf8 Demo3_1.java
#26 = NameAndType #8:#9 // "<init>":()V
#27 = Utf8 java/lang/Short
#28 = Class #34 // java/lang/System
#29 = NameAndType #35:#36 // out:Ljava/io/PrintStream;
#30 = Class #37 // java/io/PrintStream
#31 = NameAndType #38:#39 // println:(I)V
#32 = Utf8 cn/itcast/jvm/t3/bytecode/Demo3_1
#33 = Utf8 java/lang/Object
#34 = Utf8 java/lang/System
#35 = Utf8 out
#36 = Utf8 Ljava/io/PrintStream;
#37 = Utf8 java/io/PrintStream
#38 = Utf8 println
#39 = Utf8 (I)V
{
	public cn.itcast.jvm.t3.bytecode.Demo3_1();
		descriptor: ()V
		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 6: 0
			LocalVariableTable:
				Start 	Length 	Slot	 Name 	Signature
					0 		5 	   0	 this 	Lcn/itcast/jvm/t3/bytecode/Demo3_1;
	public static void main(java.lang.String[]);
		descriptor: ([Ljava/lang/String;)V
		flags: ACC_PUBLIC, ACC_STATIC
		Code:
			stack=2, locals=4, args_size=1
			0: bipush 10
			2: istore_1
			3: ldc #3 // int 32768
			5: istore_2
			6: iload_1
			7: iload_2
			8: iadd
			9: istore_3
			10: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
			13: iload_3
			14: invokevirtual #5 // Method java/io/PrintStream.println:(I)V
			17: return
			LineNumberTable:
				line 8: 0
				line 9: 3
				line 10: 6
				line 11: 10
				line 12: 17
		LocalVariableTable:
		Start Length Slot Name Signature
		0 18 0 args [Ljava/lang/String;
		3 15 1 a I
		6 12 2 b I
		10 8 3 c I
		MethodParameters:
		Name Flags
		args
}

在代码执行时,由JVM中的类加载器将类加载到内存中。其中常量池中的数据会放入运行时常量池。
运行时常量池是属于方法区的一部分,方法的字节码会存放在方法区中。
在代码执行时,去运行时常量池找寻数据。数字的范围超过Short.MAX_VALUE(32768),数字就会存储在常量池中。
在这里插入图片描述
等待类加载完成之后,main方法开始执行,分配栈帧内存。(stack=2,locals=4)
stack为栈的最大深度,locals为变量表的长度。

在这里插入图片描述
执行引擎开始执行字节码
bipush 10
将一个 byte 压入操作数栈(其长度会补齐 4 个字节),类似的指令还有sipush 将一个 short 压入操作数栈(其长度会补齐 4 个字节),ldc 将一个 int 压入操作数栈,ldc2_w 将一个 long 压入操作数栈(分两次压入,因为 long 是 8 个字节)。
这里小的数字都是和字节码指令存在一起,超过 short 范围的数字存入了常量池
在这里插入图片描述
istore_1
将操作数栈顶数据弹出,存入局部变量表的 slot 1
上面两条指令对于java代码 int a = 10;
在这里插入图片描述
ldc #3
从常量池加载 #3 数据到操作数栈
注意 Short.MAX_VALUE 是 32767,所以 32768 = Short.MAX_VALUE + 1 实际是在编译期间计算
好的。
在这里插入图片描述
istore_2
上面两条指令对于着 int b = Short.MAX_VALUE + 1;
在这里插入图片描述
iload_1与iload_2 的功能是将 变量表中1号槽位的值和变量表中2号槽位的值读取到 操作数栈中。
请添加图片描述
iadd 将操作数栈中的两个值相加放入栈中。
请添加图片描述
istore_3 将操作数栈中的数据放入变量表中的3号槽位
以上的指令对应着java代码 int c = a + b;
在这里插入图片描述
getstatic #4 到运行时常量池找到 4号项的引用,对象在堆中存放,将对象的引用放入常量池。
在这里插入图片描述
在这里插入图片描述
iload_3 将 变量表中3号槽位的值放入操作数栈。
在这里插入图片描述
invokevirtual #5
找到常量池 #5 项
定位到方法区 java/io/PrintStream.println:(I)V 方法
生成新的栈帧(分配 locals、stack等)
传递参数,执行新栈帧中的字节码
在这里插入图片描述
执行完毕,弹出栈帧
清除 main 操作数栈内容
在这里插入图片描述
return
完成 main 方法调用,弹出 main 栈帧
程序结束

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值