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)

下图所示:

操作码(指令)由类型前缀类型前缀和操作名称两部分组成,例如: iadd 操作码,i 代表 Integer 类型,add 代表加法操作,

所以 iadd 就是整数类型数据的加法操作。

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

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.14fminor 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 指令,不然调用完 方法以后,操作数栈为空,就再也找不回刚刚创建的对象了。

下图所示:

(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

python把桢写入txt_Java 字节码与字节码分析相关推荐

  1. python把桢写入txt_ffmpeg 常用参数一览表及python 使用示例

    FFmpeg是一套可以用来记录.转换数字音频.视频,并能将其转化为流的开源计算机程序.提供了录制.转换以及流化音视频的完整解决方案. 它包含了非常先进的音频/视频编解码库libavcodec,这里介绍 ...

  2. python把桢写入txt_使用Tesseract+python进行图片转文字记录

    python部分尚未完成循环的代码,等写完再放上来吧,先把Tesseract的基础部分放上来. 由于对游戏的文案非常感兴趣,所以希望可以将游戏中图片截图,识别图片上的文字转成txt,基于此记录一下学习 ...

  3. python有哪些码_Python字节码介绍

    Python部落(python.freelycode.com)组织翻译,禁止转载,欢迎转发. 了解Python字节码是什么,Python如何使用它来执行代码,以及了解它可以帮我们干什么. 如果你曾经编 ...

  4. python序列符号_Python中的文本和字节序列

    导语:本文章记录了本人在学习Python基础之数据结构篇的重点知识及个人心得,打算入门Python的朋友们可以来一起学习并交流. 本章重点: 1.了解字符字节等概念,编解码问题: 2.熟悉字符的规范化 ...

  5. python自动化办公 百度网盘-Python自动化办公实战教程2020最新带源码

    资源目录: 视频 001_Excel_xlrd读_xlwt写.mp4 003_Excel_案例_统计每位同学总分.mp4 004_MySQL_我为什么不建议学.mp4 005_Excel_更灵活的操作 ...

  6. 关于Python ord()和chr()返回ASCII码和Unicode码的看法

    刚开始学这两个函数的时候,看到书上说的是返回Unicode码, 但是自己去百度之后,发现很多人都说返回是ASCII码 为什么会这样呢? 那么为了不误解 ,我们先介绍下Unicode码和ASCII码 A ...

  7. python自动化办公入门书籍-Python自动化办公实战教程2020最新带源码

    资源目录: 视频 001_Excel_xlrd读_xlwt写.mp4 003_Excel_案例_统计每位同学总分.mp4 004_MySQL_我为什么不建议学.mp4 005_Excel_更灵活的操作 ...

  8. python 全栈开发,Day128(创建二维码,扫码,创建玩具的基本属性)

    python 全栈开发,Day128(创建二维码,扫码,创建玩具的基本属性) 昨日内容回顾 1.app播放音乐plus.audio.createPlayer(文件路径/URL)player.play( ...

  9. python项目二:多种验证码及二维码输出

    python项目二:多种验证码及二维码输出 ` import os import qrcode import random import time import tkinter from pystri ...

最新文章

  1. 生成对抗网络(GAN)的理论与应用完整入门介绍
  2. 动态加载JS脚本的4种方法
  3. 三维动画制作的基本流程
  4. 【转载】挖坑浪费时间
  5. 从0到1设计一个秒杀系统
  6. DropDownList and ListBox 两级联动菜单
  7. Leetcode每日一题:10.09.sorted-matrix-search-lcci(排序矩阵查找)
  8. MVCWebForm对照学习:文件上传(以图片为例)
  9. oracle 查询时间点数据_ORACLE中查询时间点前后一分钟的数据
  10. 2009年4月21日 博客更新公告,敬请关注!
  11. 基于深度学习的视觉 SLAM 综述
  12. cydia未能联到服务器,cydia无法加载,小编教你cydia无法加载怎么解决
  13. Visual Studio中绘制UML用例图
  14. 大觅网Springcloud项目实战开发(持续更新中)
  15. radmi4a Android,红米手机4A获取root超级权限的教程
  16. 支付产品理解第一篇——使用第三方支付的信息流与资金流
  17. 浅谈升级到 MacBook Air M1的体验
  18. 奇瑞鲍思语畅谈奇瑞未来发展,前途一片光明
  19. 浅析linux下的回收站以及U盘中的.Trash文件夹
  20. STC8F2K08S2

热门文章

  1. 对象属性的调用_面向对象的三主线之一:Java类及类的成员(1)
  2. 闭锁java_java多线程学习十::::CountDownLatch闭锁
  3. 如何在python中构造想要的时间戳参数
  4. 一篇文章学会Python函数重写,每天进步一个知识点
  5. python 单例模式的四种创建方式
  6. python 中set集合类型(去重、成员运算)
  7. 15行用Python实现仿百度搜索引擎
  8. 如何求解单边z变换_的单边Z变换。.ppt
  9. mysql1756_MySQL Error_code: 1756
  10. 【中级软考】函数参数传递传值与传引用的区别(global关键字,函数内定义全局变量)