java中类的字节码_Java 字节码与字节码分析

1.1 Java 字节码简介Java 字节码由单字节(byte)的指令组成,理论上最多支持 256 个操作码(opcode)。实际上 Java 只使用了200左右的操作码, 还有一些操作码则保留给调试操作。

根据指令的性质,主要分为四个大类:1.栈操作指令,包括与局部变量交互的指令

2.程序流程控制指令

3.对象操作指令,包括方法调用指令

4.算术运算以及类型转换指令

字节码的运行时结构:JVM 基于栈的计算模型每个线程都有一个独属于自己的线程栈(JVM Stack),用于存储栈帧(Stack Frame)。

每一次方法调用,JVM 都会自动创建一个栈帧。每当为 Java 方法分配栈桢时,JVM 需要开辟一块额外的空间作为操作数栈,来存放计算的操作数以及返回结果。

栈桢的组成部分:1.局部变量表 (Local Variables)

2.操作数栈 (Operand Stack)

3.动态链接 (Dynamic Linking)

4.方法返回地址 (Return Address)

下图所示: cab5c8dedfcbbacd0b5ef44a8ec21071.png

操作码(指令)由类型前缀类型前缀和操作名称两部分组成,例如: iadd 操作码,i 代表 Integer 类型,add 代表加法操作, 所以 iadd 就是整数类型数据的加法操作。

1.2 Java 字节码文件结构简述字节码文件结构

74653101c9c2d720457c4f152692aba0.png

72dc3085d5d866417d54c80a8b3512ab.png

1.3 字节码文件分析以下字节码分析都是基于 HelloByteCode.java 使用 javap 反编译之后来展开。

(1) 类信息与常量池信息Classfile /D:/lesson01/bytecode/HelloByteCode.class

Last modified Jan 9, 2021; size 1888 bytes

MD5 checksum d0225425f9a99537afc8664fa6125129

Compiled from "HelloByteCode.java"

public class lesson01.bytecode.HelloByteCode

minor version: 0

major version: 52

flags: ACC_PUBLIC, ACC_SUPER

Constant pool:

#1 = Methodref #7.#60 // java/lang/Object."":()V

#2 = Fieldref #61.#62 // java/lang/System.out:Ljava/io/PrintStream;

#3 = String #32 // myMethod

#4 = Methodref #63.#64 // java/io/PrintStream.println:(Ljava/lang/String;)V

#5 = String #33 // myStaticMethod

#6 = String #65 // sum = %d, division = %d, multiplication = %d, sub = %d%n

#7 = Class #66 // java/lang/Object

#8 = Methodref #67.#68 // java/lang/Integer.valueOf:(I)Ljava/lang/Integer;

#9 = Methodref #63.#69 // java/io/PrintStream.printf:(Ljava/lang/String;[Ljava/lang/Object;)Ljava/io/PrintStream;

#10 = Long 6000000l

#12 = Float 3.14f

复制代码minor version 与 major version: 字节码格式版本号,52.0 = JDK8

flags: 访问权限修饰与属性ACC_PUBLIC 表示当前类的访问修饰是 public

ACC_SUPER 是历史原因, JDK1.0 的BUG修正中引入 ACC_SUPER 标志来修正 invokespecial 指令调用 super 类方法的问题,从 Java 1.1 开始, 编译器一般都会自动生成 ACC_SUPER 标志。Constant pool: 常量池1、#2: 这些表示常量池成员的编号,常量池成员可以存储字符串、整数、浮点数、符号引用、常量编号(索引值)

Methodref、Fieldref、String等: 用于说明此常量位存储的是什么类型的数据,例如 Methodref 代表这个常量指向的是一个方法

(2) 方法信息public double myPublicMethod(int, double, java.lang.String);

descriptor: (IDLjava/lang/String;)D

flags: ACC_PUBLIC

Code:

stack=4, locals=5, args_size=4

0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;

3: new #3 // class java/lang/StringBuilder

6: dup

7: invokespecial #4 // Method java/lang/StringBuilder."":()V

10: ldc #5 // String myPublicMethod, x =

12: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

15: iload_1

16: invokevirtual #7 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;

19: ldc #8 // String , d =

21: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

24: dload_2

25: invokevirtual #9 // Method java/lang/StringBuilder.append:(D)Ljava/lang/StringBuilder;

28: ldc #10 // String , str =

30: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

33: aload 4

35: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

38: invokevirtual #11 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;

41: invokevirtual #12 // Method java/io/PrintStream.println:(Ljava/lang/String;)V

44: iload_1

45: i2d

46: dload_2

47: dadd

48: dreturn

LineNumberTable:

line 12: 0

line 13: 44

LocalVariableTable:

Start Length Slot Name Signature

0 49 0 this Llesson01/bytecode/HelloByteCode;

0 49 1 x I

0 49 2 d D

0 49 4 str Ljava/lang/String;

复制代码descriptor: 方法描述(IDLjava/lang/String;)D小括号内的为形参参数类型描述,小括号右边的是方法返回值类型描述。

下括号内 I 代表第一个参数为 int 类型,D 代表第二个参数为 double 类型。L 代表第三个参数是对象类型,java/lang/String 代表是 String 类型对象。

小括号右边的 D 代表方法的返回值为 double 类型。flags: 访问权限修饰符,ACC_PUBLIC 代表 public 方法,ACC_STATIC 代表静态方法。

Code: 源代码对应的 JVM 操作码和操作数区域。在进行字节码增强时重点操作的就是 Code 区这一部分。操作码左边的数字代表当前操作码在字节码二进制文件中的字节位置

stack: 执行该方法时需要的栈深度

locals: 需要在局部变量表中保留多少个槽位

args_size: 方法的参数个数此示例有3个形参,但是 args_size 是 4,这是因为非静态方法有 this 引用,this 被分配在局部变量表的第0号槽位中LineNumberTable: 行号表将 Code 区的操作码和源代码中的行号对应,Debug 的时候可以通过行号表来看源代码执行一行时需要执行多少个 JVM 操作码

例如 line 12: 0 ,12代表源代码的行号,0代表 Code 区操作码的行号LocalVariableTable: 局部变量表其中包含了方法的参数,以及在方法体内定义的局部变量。

元素个数等于 args_size

Start 和 Length: 代表当前局部变量或方法参数在 Code 操作码区中的作用域范围例如当前示例 start 为0,length 为49代表该变量的作用域从0一直到48,也就是整个方法体。

Slot: 槽位,从0开始,如果访问的是64位数据类型变量(例如: long类型变量,double类型变量)会占用2个槽位。

Name: 变量名

Signature: 变量类型描述

(3) 基本数据类型变量定义Constant pool:

#22 = Long 6000000l

#24 = Float 3.14f

#25 = Double 4125.5647d

public static void main(java.lang.String[]);

descriptor: ([Ljava/lang/String;)V

flags: ACC_PUBLIC, ACC_STATIC

Code:

stack=7, locals=15, args_size=1

0: bipush 10

2: istore_1

3: bipush 50

5: istore_2

.... 其他省略

72: iconst_5

73: istore 7

75: ldc2_w #22 // long 6000000l

78: lstore 8

80: ldc #24 // float 3.14f

82: fstore 10

84: ldc2_w #25 // double 4125.5647d

87: dstore 11

89: iconst_1

90: istore 13

.... 其他省略

246: return

LocalVariableTable:

Start Length Slot Name Signature

162 18 14 i I

0 247 0 args [Ljava/lang/String;

3 244 1 x I

6 241 2 y I

13 234 3 sum I

18 229 4 division I

23 224 5 multiplication I

28 219 6 sub I

75 172 7 b B

80 167 8 l J

84 163 10 f F

89 158 11 d D

92 155 13 bool Z

233 14 14 helloByteCode Llesson01/bytecode/HelloByteCode;

复制代码0行: 将常量10压入操作数栈的栈顶中

2行: 将栈顶 int 类型的值保存到槽位为1的局部变量中,也就是把常量10赋值给变量 x

3行: 将常量50压入操作数栈的栈顶中

5行: 将栈顶 int 类型的值保存到槽位为2的局部变量中,也就是把常量10赋值给变量 y 72行: 将常量5压入操作数栈的栈顶中

73行: 将栈顶 int 类型的值保存到槽位为7的局部变量中,也就是把常量5赋值给变量 b

75行: 将常量编号为22的 long 类型的常量值压入操作数栈的栈顶中

78行: 将栈顶 long 类型的值保存到槽位为8的局部变量中,也就是把常量6000000赋值给变量 l

80行: 将常量编号为24的 float 类型常量值压入操作数栈的栈顶中

82行: 将栈顶 float 类型的值保存到槽位为10的局部变量中

84行: 将常量编号为25的 double 类型常量值压入操作数栈的栈顶中

87行: 将栈顶 double 类型的值保存到槽位为11的局部变量中

(4) 创建对象与对象初始化Constant pool:

#36 = Class #121 // lesson01/bytecode/HelloByteCode

#37 = Methodref #36.#86 // lesson01/bytecode/HelloByteCode."":()V

#41 = Utf8

#42 = Utf8 ()V

#86 = NameAndType #41:#42 // "":()V

#121 = Utf8 lesson01/bytecode/HelloByteCode

public static void main(java.lang.String[]);

descriptor: ([Ljava/lang/String;)V

flags: ACC_PUBLIC, ACC_STATIC

Code:

stack=7, locals=15, args_size=1

.... 其他省略

224: new #36 // class lesson01/bytecode/HelloByteCode

227: dup

228: invokespecial #37 // Method "":()V

231: astore 14

.... 其他省略

246: return

LocalVariableTable:

Start Length Slot Name Signature

162 18 14 i I

0 247 0 args [Ljava/lang/String;

3 244 1 x I

6 241 2 y I

13 234 3 sum I

18 229 4 division I

23 224 5 multiplication I

28 219 6 sub I

75 172 7 b B

80 167 8 l J

84 163 10 f F

89 158 11 d D

92 155 13 bool Z

233 14 14 helloByteCode Llesson01/bytecode/HelloByteCode;

复制代码224行: 创建一个 HelloByteCode 对象, 并将其引用的引用值压入操作数栈的栈顶中,此时没有调用构造函数,所以对象没有初始化。

227行: 复制栈顶的 对象引用值 并将复制值压入操作数栈的栈顶中

228行: 调用 HelloByteCode 的无参构造函数 231行: 将栈顶的引用类型数值保存到槽位为14的局部变量中

为什么创建对象时需要 dup 指令?使用 invokespecial 命令会从操作数堆栈中弹出 nargs 参数值和 objectref ,正是因为需要调用这个函数才导致中间必须要有一个 dup 指令,不然调用完 方法以后,操作数栈为空,就再也找不回刚刚创建的对象了。

下图所示:

a69e847a39b903f296ea4c72738939d9.png

(5) 算术运算public static void main(java.lang.String[]);

descriptor: ([Ljava/lang/String;)V

flags: ACC_PUBLIC, ACC_STATIC

Code:

stack=7, locals=15, args_size=1

.... 其他省略

6: iload_1

7: iload_2

8: iadd

9: bipush 60

11: iadd

12: istore_3

13: iload_2

14: iload_1

15: idiv

16: istore 4

18: iload_1

19: iload_2

20: imul

21: istore 5

23: iload_2

.... 其他省略

246: return

LocalVariableTable:

Start Length Slot Name Signature

162 18 14 i I

0 247 0 args [Ljava/lang/String;

3 244 1 x I

6 241 2 y I

13 234 3 sum I

18 229 4 division I

23 224 5 multiplication I

28 219 6 sub I

75 172 7 b B

80 167 8 l J

84 163 10 f F

89 158 11 d D

92 155 13 bool Z

233 14 14 helloByteCode Llesson01/bytecode/HelloByteCode;

复制代码6行: 将局部变量表中槽位为1的变量值压入操作数栈的栈顶中

7行: 将局部变量表中槽位为2的变量值压入操作数栈的栈顶中

8行: 将栈顶两个 int 型数值相加并将结果压入栈顶

9行: 将常量60压入栈顶

11行: 将栈顶两个 int 型数值相加并将结果压入栈顶,此时栈顶的值就是 x + y + 60

12行: 将栈顶的值保存到槽位为3的局部变量中

其他算术操作类似,以此类推。

(6) 数组创建与元素访问public static void main(java.lang.String[]);

descriptor: ([Ljava/lang/String;)V

flags: ACC_PUBLIC, ACC_STATIC

Code:

stack=7, locals=18, args_size=1

.... 其他省略

145: iconst_5

146: newarray int

148: dup

149: iconst_0

150: iconst_1

151: iastore

152: dup

153: iconst_1

154: bipush 7

156: iastore

157: dup

158: iconst_2

159: sipush 220

162: iastore

163: dup

164: iconst_3

165: bipush 50

167: iastore

168: dup

169: iconst_4

170: sipush 1000

173: iastore

174: astore 14

176: aload 14

178: iconst_0

179: iaload

180: istore 15

.... 其他省略

246: return

LocalVariableTable:

Start Length Slot Name Signature

244 26 16 i I

249 21 17 len I

0 335 0 args [Ljava/lang/String;

3 332 1 x I

6 329 2 y I

13 322 3 sum I

18 317 4 division I

23 312 5 multiplication I

28 307 6 sub I

75 260 7 b B

80 255 8 l J

84 251 10 f F

89 246 11 d D

92 243 13 bool Z

176 159 14 iArr [I

182 153 15 elementValue I

321 14 16 helloByteCode Llesson01/bytecode/HelloByteCode;

复制代码145行: 将 int 类型5压入栈顶

146行: 先从栈弹出获取栈顶的值,获取到5,然后创建5个 int 类型元素的数组对象,并把该数组对象的引用值压入栈顶

148行: 复制栈顶的 数组对象引用值 并将复制值压入栈顶

149行: 将 int 类型0压入栈顶,该值为数组下标

150行: 将 int 类型1压入栈顶,该值为要存入数组的元素值

151行: 从操作数栈弹出三个值,分别是要存入的元素值、数组下标、数组的对象引用,并将元素值存入数组对应的下标中

152行: 因为 iastore 指令会弹出数组的引用,所以要复制一下数组的引用并压入栈顶,供后续数组操作使用

153行 - 173行: 与上述操作逻辑相同,将元素值存入数组中,此处省略详细分析

174行: 将栈顶的 数组对象引用 保存到槽位为14的局部变量中,也就是将数组引用赋值给局部变量 iArr

176行: 将槽位为14的局部变量引用值压入栈顶,也就是将 iArr 数组引用压入栈顶

178行: 将 int 类型0压入栈顶,此值为数组的下标

179行: 从栈顶弹出获取2个值,数组下标值和数组对象引用,在从数组中获取到该下标的元素值并压入栈顶

180行: 将栈顶 int 类型数组元素值保存到槽位为15的局部变量中,也就是将数组下标为0的元素值赋值给局部变量 elementValue

(7) 流程控制指令

if else 语句:Constant pool:

#2 = Fieldref #94.#95 // java/lang/System.out:Ljava/io/PrintStream;

#3 = Class #96 // java/lang/StringBuilder

#4 = Methodref #3.#93 // java/lang/StringBuilder."":()V

#6 = Methodref #3.#98 // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

#7 = Methodref #3.#99 // java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;

#11 = Methodref #3.#103 // java/lang/StringBuilder.toString:()Ljava/lang/String;

#12 = Methodref #104.#105 // java/io/PrintStream.println:(Ljava/lang/String;)V

#33 = String #125 // x = 10

#34 = String #126 // x = 20

#35 = String #127 // x =

#43 = Utf8

#44 = Utf8 ()V

#93 = NameAndType #43:#44 // "":()V

#94 = Class #132 // java/lang/System

#95 = NameAndType #133:#134 // out:Ljava/io/PrintStream;

#96 = Utf8 java/lang/StringBuilder

#98 = NameAndType #135:#136 // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

#99 = NameAndType #135:#137 // append:(I)Ljava/lang/StringBuilder;

#103 = NameAndType #139:#140 // toString:()Ljava/lang/String;

#104 = Class #141 // java/io/PrintStream

#105 = NameAndType #142:#143 // println:(Ljava/lang/String;)V

#125 = Utf8 x = 10

#126 = Utf8 x = 20

#127 = Utf8 x =

#132 = Utf8 java/lang/System

#133 = Utf8 out

#134 = Utf8 Ljava/io/PrintStream;

#135 = Utf8 append

#136 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder;

#137 = Utf8 (I)Ljava/lang/StringBuilder;

#138 = Utf8 (D)Ljava/lang/StringBuilder;

#139 = Utf8 toString

#140 = Utf8 ()Ljava/lang/String;

#141 = Utf8 java/io/PrintStream

#142 = Utf8 println

#143 = Utf8 (Ljava/lang/String;)V

public static void main(java.lang.String[]);

descriptor: ([Ljava/lang/String;)V

flags: ACC_PUBLIC, ACC_STATIC

Code:

stack=7, locals=18, args_size=1

.... 其他省略

182: iload_1

183: bipush 10

185: if_icmpne 199

188: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;

191: ldc #33 // String x = 10

193: invokevirtual #12 // Method java/io/PrintStream.println:(Ljava/lang/String;)V

196: goto 275

199: iload_1

200: bipush 20

202: if_icmple 216

205: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;

208: ldc #34 // String x > 20

210: invokevirtual #12 // Method java/io/PrintStream.println:(Ljava/lang/String;)V

213: goto 275

216: iload_1

217: bipush 50

219: if_icmpge 233

222: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;

225: ldc #35 // String x < 50

227: invokevirtual #12 // Method java/io/PrintStream.println:(Ljava/lang/String;)V

230: goto 275

233: iload_1

234: bipush 60

236: if_icmplt 250

239: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;

242: ldc #36 // String x >= 60

244: invokevirtual #12 // Method java/io/PrintStream.println:(Ljava/lang/String;)V

247: goto 275

250: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;

253: new #3 // class java/lang/StringBuilder

256: dup

257: invokespecial #4 // Method java/lang/StringBuilder."":()V

260: ldc #37 // String x =

262: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

265: iload_1

266: invokevirtual #7 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;

269: invokevirtual #11 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;

272: invokevirtual #12 // Method java/io/PrintStream.println:(Ljava/lang/String;)V

.... 其他省略

246: return

LocalVariableTable:

Start Length Slot Name Signature

278 26 16 i I

283 21 17 len I

0 371 0 args [Ljava/lang/String;

3 368 1 x I

6 365 2 y I

13 358 3 sum I

18 353 4 division I

23 348 5 multiplication I

28 343 6 sub I

75 296 7 b B

80 291 8 l J

84 287 10 f F

89 282 11 d D

92 279 13 bool Z

176 195 14 iArr [I

182 189 15 elementValue I

357 14 16 helloByteCode Llesson01/bytecode/HelloByteCode;

复制代码182行: 将槽位为1的局部变量值压入栈顶

183行: 将常量值10压入栈顶

185行: 从栈顶弹出2个 int 类型值然后比较,如果结果不相等,就跳转到第199行,否则就继续往下执行

188行: 获取 System 的静态字段 out 并将其值加入栈顶中

191行: 把字符串常量 x = 10 压入栈顶

193行: 从栈顶弹出2个值,第一个值是 x = 10 字符串常量,第二个值是 out 静态字段,然后调用 println 方法打印 x = 10

196行: 已经执行完 if else 代码块,所以跳转到275行,跳出 if else 代码块

199行 - 272行: 执行逻辑相同,只是 if 的判断指令有所不同。

250行 - 272行: 是使用 StringBuilder 进行字符串常量 "x = " 与 局部变量x进行拼接,然后调用 System 的静态字段 out 的 println 方法打印拼接后的字符串

for 语句:public static void main(java.lang.String[]);

descriptor: ([Ljava/lang/String;)V

flags: ACC_PUBLIC, ACC_STATIC

Code:

stack=7, locals=18, args_size=1

.... 其他省略

275: iconst_0

276: istore 16

278: aload 14

280: arraylength

281: istore 17

283: iload 16

285: iload 17

287: if_icmpge 304

290: iload_3

291: aload 14

293: iload 16

295: iaload

296: iadd

297: istore_3

298: iinc 16, 1

301: goto 283

.... 其他省略

246: return

LocalVariableTable:

Start Length Slot Name Signature

278 26 16 i I

283 21 17 len I

0 371 0 args [Ljava/lang/String;

3 368 1 x I

6 365 2 y I

13 358 3 sum I

18 353 4 division I

23 348 5 multiplication I

28 343 6 sub I

75 296 7 b B

80 291 8 l J

84 287 10 f F

89 282 11 d D

92 279 13 bool Z

176 195 14 iArr [I

182 189 15 elementValue I

357 14 16 helloByteCode Llesson01/bytecode/HelloByteCode;

复制代码275行: 将 int 类型0压入栈顶

276行: 将栈顶值保存到槽位为16的局部变量中,此时槽位为16的局部变量是 i

278行: 将槽位为14的引用变量值压入栈顶,就是 iArr 数组对象

280行: 首先弹出栈顶的数组对象引用值,获取该数组的长度值并压入栈顶

281行: 将栈顶值保存到槽位为17的局部变量中,就是把数组长度保存到局部变量 len

283行: 将槽位为16的局部变量值压入栈顶,局部变量 i 的值

285行: 将槽位为17的局部变量值压入栈顶,局部变量 len 的值 287行: 先从栈中弹出两个操作数,局部变量 len 的值和局部变量 i 的值,比较 len 和 i,当 len >= i 时则跳转到304行,跳出循环

290行 - 301行: 为 for 循环体相关字节码

290行: 将槽位为3的局部变量值压入栈顶,局部变量 sum 的值

291行: 将槽位为14的局部变量引用值压入栈顶,也就是 iArr 数组对象引用值

293行: 将槽位为16的局部变量 int 类型值压入栈顶,也就是局部变量 i 的值

295行: 先从栈顶弹出两个值,第一个是局部变量 i 的值,第二个是 iArr 数组对象引用值,将 i 的值作为数组下标,取出对应的元素值并压入栈顶

296行: 先从栈顶弹出两个值,第一个是 iArr[i] 的值,第二个是局部变量 sum 的值,并将它们相加,然后把相加后的值压入栈顶

297行: 将栈顶的 int 类型值保存到槽位为3的局部变量中,局部变量 sum 中

298行: 将槽位为16的局部变量值自增1,也就是 i++

301行: 跳转到283行,重新判断循环条件,满足就继续执行循环体,否则跳出循环

switch 语句:public static void main(java.lang.String[]);

descriptor: ([Ljava/lang/String;)V

flags: ACC_PUBLIC, ACC_STATIC

Code:

stack=7, locals=18, args_size=1

.... 其他省略

304: iload_2

305: lookupswitch { // 2

10: 332

50: 340

default: 348

}

332: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;

335: ldc #38 // String y = 10

337: invokevirtual #12 // Method java/io/PrintStream.println:(Ljava/lang/String;)V

340: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;

343: ldc #39 // String y = 50

345: invokevirtual #12 // Method java/io/PrintStream.println:(Ljava/lang/String;)V

348: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;

351: ldc #40 // String default

353: invokevirtual #12 // Method java/io/PrintStream.println:(Ljava/lang/String;)V

.... 其他省略

246: return

LocalVariableTable:

Start Length Slot Name Signature

278 26 16 i I

283 21 17 len I

0 379 0 args [Ljava/lang/String;

3 376 1 x I

6 373 2 y I

13 366 3 sum I

18 361 4 division I

23 356 5 multiplication I

28 351 6 sub I

75 304 7 b B

80 299 8 l J

84 295 10 f F

89 290 11 d D

92 287 13 bool Z

176 203 14 iArr [I

182 197 15 elementValue I

365 14 16 helloByteCode Llesson01/bytecode/HelloByteCode;

复制代码304行: 将槽位为2的局部变量值压入栈顶,局部变量 y 的值压入栈顶

305行: 从栈顶弹出获取到局部变量 y 的值,并进行值的匹配,y 的值为10时跳转到332行,y 的值为50时跳转到340行,如果都不匹配执行 default 分支跳转到348行

332行 - 353行: 都是调用不同匹配值的分支,调用 println 打印不同的字符串

(8) 方法调用指令和参数传递Constant pool:

#14 = String #109 // Hello

#41 = Class #136 // lesson01/bytecode/HelloByteCode

#42 = Methodref #41.#96 // lesson01/bytecode/HelloByteCode."":()V

#43 = Double 90.0d

#45 = Methodref #41.#137 // lesson01/bytecode/HelloByteCode.myPublicMethod:(IDLjava/lang/String;)D

#46 = Utf8

#47 = Utf8 ()V

#53 = Utf8 myPublicMethod

#54 = Utf8 (IDLjava/lang/String;)D

#96 = NameAndType #46:#47 // "":()V

#109 = Utf8 Hello

#136 = Utf8 lesson01/bytecode/HelloByteCode

#137 = NameAndType #53:#54 // myPublicMethod:(IDLjava/lang/String;)D

public static void main(java.lang.String[]);

descriptor: ([Ljava/lang/String;)V

flags: ACC_PUBLIC, ACC_STATIC

Code:

stack=7, locals=18, args_size=1

.... 其他省略

356: new #41 // class lesson01/bytecode/HelloByteCode

359: dup

360: invokespecial #42 // Method "":()V

363: astore 16

365: aload 16

367: bipush 80

369: ldc2_w #43 // double 90.0d

372: ldc #14 // String Hello

374: invokevirtual #45 // Method myPublicMethod:(IDLjava/lang/String;)D

377: pop2

378: return

LocalVariableTable:

Start Length Slot Name Signature

278 26 16 i I

283 21 17 len I

0 379 0 args [Ljava/lang/String;

3 376 1 x I

6 373 2 y I

13 366 3 sum I

18 361 4 division I

23 356 5 multiplication I

28 351 6 sub I

75 304 7 b B

80 299 8 l J

84 295 10 f F

89 290 11 d D

92 287 13 bool Z

176 203 14 iArr [I

182 197 15 elementValue I

365 14 16 helloByteCode Llesson01/bytecode/HelloByteCode;

复制代码356行: 创建 HelloByteCode 对象,并将对象引用值压入栈顶

359行: 复制栈顶的对象引用值并压入栈顶

360行: 调用 HelloByteCode 的无参构造函数

363行: 将栈顶的 HelloByteCode 对象引用值保存到槽位为16的局部变量中,此时的局部变量已经是 helloByteCode ,之前同样槽位为16的局部变量 i 超过了作用域,复用了同一个槽位

365行: 将槽位为16的 helloByteCode 局部变量引用值压入栈顶

367行: 将常量值80压入栈顶

369行: 将常量编号为43的浮点型常量值90.0压入栈顶

372行: 将字符串常量值 Hello 压入栈顶

374行: 弹出栈中所有数据调用 HelloByteCode 的 myPublicMethod 方法并将参数传入,执行完后将返回值压入栈顶

377行: 从栈顶将返回值弹出

378行: 方法返回

上述的情况是 myPublicMethod 方法有返回值,但是没有局部变量去接收的情况。

myPublicMethod 方法没有返回值的字节码情况:374: invokevirtual #45 // Method myPublicMethod:(IDLjava/lang/String;)V

377: return

复制代码没有返回值会直接 return

myPublicMethod 方法有返回值并且也有局部变量接收的字节码情况:374: invokevirtual #45 // Method myPublicMethod:(IDLjava/lang/String;)D

377: dstore 17

379: return

复制代码有返回值且有局部变量接收,会使用指令将在栈顶的返回值保存到对应的局部变量

1.4 字节码相关 JDK 命令行工具javac 编译工具

常用参数:-g 在生成的class文件中包含所有调试信息(包括局部变量),缺省情况下只生成行号和源文件信息。

-g:none 不生成任何调试信息

-g:{关键字列表} 只生成某些类型的调试信息,这些类型由逗号分隔的关键字列表所指定

-encoding 指定编码

-verbose 冗长输出。它包括了每个所加载的类和每个所编译的源文件的有关信息。

示例: javac -encoding utf-8 -g HelloByteCode.java

javap 查看字节码工具

常用参数:-c 输出分解后的代码,例如,类中每一个方法内,包含 java 字节码的指令

-verbose 输出栈大小,方法参数的个数、局部变量表等

示例: javap -c -verbose HelloByteCode.class

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值