目录

  • 1.入门
  • 2 javap 工具
  • 3 图解方法执行流程
    • 3.1.原始 java 代码
    • 3.2.编译后的字节码文件
    • 3.3.常量池载入运行时常量池
    • 3.4.方法字节码载入方法区
    • 3.5.main 线程开始运行,分配栈帧内存
    • 3.6.执行引擎开始执行字节码
  • 4 练习 - 分析 i++
  • 5.条件判断
  • 6.循环控制指令
  • 7 练习 - 判断结果
  • 8 构造方法
  • 9 方法调用
  • 10.多态的原理
  • 11.异常处理
    • 11.1.try-catch
    • 11.2.多个single-catch
    • 11.3.finally
    • 11.4.finally面试题
      • 11.4.1.finally中的return
      • 11.4.2.被吞掉的异常
      • 11.4.3.finally不带return
  • 12.synchronized

1.入门

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

2a b7 00 01 b1

它实际上对应字节码的指令,java虚拟机内部有解释器,解释器会识别这些平台无关的字节码指令,把它们最终解释为机器码,然后执行。
那么怎么知道机器码对应的字节码指令呢。
请参考
https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5
查找0x2a

0x2a aload_0
b7 invokespecial
b1 return

  1. 2a => aload_0 加载 slot 0 的局部变量,即 this,做为下面的 invokespecial 构造方法调用的参数 (把局部变量表中0号槽位的变量加载到操作数栈上)
  2. b7 => invokespecial 预备调用构造方法,哪个方法呢?(准备进行方法的调用)
  3. 00 01 引用常量池中 #1 项,即【 Method java/lang/Object." ": () V 】
  4. b1 表示返回

所以这个是通过this调用了父类的无参构造方法。最后b1是方法执行了要返回。

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

b2 00 02 12 03 b6 00 04 b1
  1. b2 => getstatic 用来加载静态变量,哪个静态变量呢?
  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 表示返回

注意,这里字节码是先准备参数,再调用方法。

2 javap 工具

自己分析类文件结构太麻烦了,Oracle 提供了 javap 工具来反编译 class 文件
使用IDEA反编译

F:\IDEA\projects\jvm>javap -v F:\IDEA\projects\jvm\out\production\untitled\cn\yj\jvm\HelloWorld.class
Classfile /F:/IDEA/projects/jvm/out/production/untitled/cn/yj/jvm/HelloWorld.classLast modified 2021-2-2; size 553 bytesMD5 checksum 6b7033e0eab7845f9c8aa7b8e1f2d44fCompiled from "HelloWorld.java"
public class cn.yj.jvm.HelloWorldminor version: 0major version: 52flags: ACC_PUBLIC, ACC_SUPER
Constant pool:#1 = Methodref          #6.#20         // java/lang/Object."<init>":()V#2 = Fieldref           #21.#22        // java/lang/System.out:Ljava/io/PrintStream;#3 = String             #23            // hello world#4 = Methodref          #24.#25        // java/io/PrintStream.println:(Ljava/lang/String;)V#5 = Class              #26            // cn/yj/jvm/HelloWorld#6 = Class              #27            // java/lang/Object#7 = Utf8               <init>#8 = Utf8               ()V#9 = Utf8               Code#10 = Utf8               LineNumberTable#11 = Utf8               LocalVariableTable#12 = Utf8               this#13 = Utf8               Lcn/yj/jvm/HelloWorld;#14 = Utf8               main#15 = Utf8               ([Ljava/lang/String;)V#16 = Utf8               args#17 = Utf8               [Ljava/lang/String;#18 = Utf8               SourceFile#19 = Utf8               HelloWorld.java#20 = NameAndType        #7:#8          // "<init>":()V#21 = Class              #28            // java/lang/System#22 = NameAndType        #29:#30        // out:Ljava/io/PrintStream;#23 = Utf8               hello world#24 = Class              #31            // java/io/PrintStream#25 = NameAndType        #32:#33        // println:(Ljava/lang/String;)V#26 = Utf8               cn/yj/jvm/HelloWorld#27 = Utf8               java/lang/Object#28 = Utf8               java/lang/System#29 = Utf8               out#30 = Utf8               Ljava/io/PrintStream;#31 = Utf8               java/io/PrintStream#32 = Utf8               println#33 = Utf8               (Ljava/lang/String;)V
{public cn.yj.jvm.HelloWorld();descriptor: ()Vflags: ACC_PUBLICCode:stack=1, locals=1, args_size=10: aload_01: invokespecial #1                  // Method java/lang/Object."<init>":()V4: returnLineNumberTable:line 2: 0LocalVariableTable:Start  Length  Slot  Name   Signature0       5     0  this   Lcn/yj/jvm/HelloWorld;public static void main(java.lang.String[]);descriptor: ([Ljava/lang/String;)Vflags: ACC_PUBLIC, ACC_STATICCode:stack=2, locals=1, args_size=10: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;3: ldc           #3                  // String hello world5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V8: returnLineNumberTable:line 4: 0line 5: 8LocalVariableTable:Start  Length  Slot  Name   Signature0       9     0  args   [Ljava/lang/String;
}
SourceFile: "HelloWorld.java"

3 图解方法执行流程

3.1.原始 java 代码

package cn.yj.jvm;/** * 演示 字节码指令 和 操作数栈、常量池的关系 */
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);}
}

3.2.编译后的字节码文件

F:\IDEA\projects\jvm>javap -v F:\IDEA\projects\jvm\out\production\untitled\cn\yj\jvm\Demo3_1.class
Classfile /F:/IDEA/projects/jvm/out/production/untitled/cn/yj/jvm/Demo3_1.classLast modified 2021-2-2; size 603 bytesMD5 checksum 9bdbe178a29e07556915f368dbf7def1Compiled from "Demo3_1.java"
public class cn.yj.jvm.Demo3_1minor version: 0major version: 52flags: ACC_PUBLIC, ACC_SUPER
Constant pool:#1 = Methodref          #7.#25         // java/lang/Object."<init>":()V#2 = Class              #26            // java/lang/Short#3 = Integer            32768#4 = Fieldref           #27.#28        // java/lang/System.out:Ljava/io/PrintStream;#5 = Methodref          #29.#30        // java/io/PrintStream.println:(I)V#6 = Class              #31            // cn/yj/jvm/Demo3_1#7 = Class              #32            // java/lang/Object#8 = Utf8               <init>#9 = Utf8               ()V#10 = Utf8               Code#11 = Utf8               LineNumberTable#12 = Utf8               LocalVariableTable#13 = Utf8               this#14 = Utf8               Lcn/yj/jvm/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               SourceFile#24 = Utf8               Demo3_1.java#25 = NameAndType        #8:#9          // "<init>":()V#26 = Utf8               java/lang/Short#27 = Class              #33            // java/lang/System#28 = NameAndType        #34:#35        // out:Ljava/io/PrintStream;#29 = Class              #36            // java/io/PrintStream#30 = NameAndType        #37:#38        // println:(I)V#31 = Utf8               cn/yj/jvm/Demo3_1#32 = Utf8               java/lang/Object#33 = Utf8               java/lang/System#34 = Utf8               out#35 = Utf8               Ljava/io/PrintStream;#36 = Utf8               java/io/PrintStream#37 = Utf8               println#38 = Utf8               (I)V
{public cn.yj.jvm.Demo3_1();descriptor: ()Vflags: ACC_PUBLICCode:stack=1, locals=1, args_size=10: aload_01: invokespecial #1                  // Method java/lang/Object."<init>":()V4: returnLineNumberTable:line 4: 0LocalVariableTable:Start  Length  Slot  Name   Signature0       5     0  this   Lcn/yj/jvm/Demo3_1;public static void main(java.lang.String[]);descriptor: ([Ljava/lang/String;)Vflags: ACC_PUBLIC, ACC_STATICCode:stack=2, locals=4, args_size=10: bipush        102: istore_13: ldc           #3                  // int 327685: istore_26: iload_17: iload_28: iadd9: istore_310: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;13: iload_314: invokevirtual #5                  // Method java/io/PrintStream.println:(I)V17: returnLineNumberTable:line 7: 0line 8: 3line 9: 6line 10: 10line 11: 17LocalVariableTable:Start  Length  Slot  Name   Signature0      18     0  args   [Ljava/lang/String;3      15     1     a   I6      12     2     b   I10       8     3     c   I
}
SourceFile: "Demo3_1.java"

3.3.常量池载入运行时常量池

当我们java代码被执行时,它会由java虚拟机的类加载器把我们main方法所在的类进行类加载的操作,类加载实际上把这些字节的class的数据读取到内存里,常量池的数据被放入运行时常量池,运行时常量池属于方法区的组成部分,只是因为相对比较特殊,其实就是把class文件中的数据存入到运行时常量池的地方。将来找其中一些常量池信息就到运行时常量池中找。
如图只列出了3,4,5几项,第3项是原码中的int b = Short.MAX_VALUE + 1;而int a=10;比较小的数字与方法字节码存储在一起,不存在常量池中。一旦超过了Short整数的最大值的范围,就存到常量池中
常量池也属于方法区,只不过这里单独提出来了

3.4.方法字节码载入方法区

3.5.main 线程开始运行,分配栈帧内存

(stack=2,locals=4) 对应操作数栈有2个空间(每个空间4个字节),局部变量表中有4个槽位

3.6.执行引擎开始执行字节码

bipush 10

  • 将一个 byte 压入操作数栈(其长度会补齐 4 个字节),类似的指令还有
  • sipush 将一个 short 压入操作数栈(其长度会补齐 4 个字节)
  • ldc 将一个 int 压入操作数栈
  • ldc2_w 将一个 long 压入操作数栈(分两次压入,因为 long 是 8 个字节)
  • 这里小的数字都是和字节码指令存在一起,超过 short 范围的数字存入了常量池


istore 1

将操作数栈栈顶元素弹出,放入局部变量表的slot 1中0

对应代码中的

a = 10



ldc #3
读取运行时常量池中#3,即32768(超过short最大值范围的数会被放到运行时常量池中),将其加载到操作数栈中

注意 Short.MAX_VALUE 是 32767,所以 32768 = Short.MAX_VALUE + 1 实际是在编译期间计算好的

istore 2
将操作数栈中的元素弹出,放到局部变量表的2号位置


iload_1 iload_2
将局部变量表中1号位置和2号位置的元素放入操作数栈中

因为只能在操作数栈中执行运算操作


iadd

将操作数栈中的两个元素弹出栈并相加,结果在压入操作数栈中


istore 3
将操作数栈中的元素弹出,放入局部变量表的3号位置


getstatic #4

在运行时常量池中找到#4,发现是一个对象

在堆内存中找到该对象,并将其引用放入操作数栈中



iload 3
将局部变量表中3号位置的元素压入操作数栈中



invokevirtual 5

找到常量池 #5 项,定位到方法区 java/io/PrintStream.println:(I)V 方法

生成新的栈帧(分配 locals、stack等)

传递参数,执行新栈帧中的字节码


执行完毕,弹出栈帧
清除 main 操作数栈内容


return
完成 main 方法调用,弹出 main 栈帧
程序结束

4 练习 - 分析 i++

目的:从字节码角度分析 a++ 相关题目
源码:

package cn.yj.jvm;/** * 从字节码角度分析 a++  相关题目 */ public class Demo3_2 {public static void main(String[] args) {int a = 10;int b = a++ + ++a + a--;System.out.println(a);System.out.println(b);}
}

字节码:

F:\IDEA\projects\jvm>javap -v F:\IDEA\projects\jvm\out\production\untitled\cn\yj\jvm\Demo3_2.class
Classfile /F:/IDEA/projects/jvm/out/production/untitled/cn/yj/jvm/Demo3_2.classLast modified 2021-2-3; size 578 bytesMD5 checksum c5e9d3ebbd57d36a03305a1c3a5d9b4cCompiled from "Demo3_2.java"
public class cn.yj.jvm.Demo3_2minor version: 0major version: 52flags: ACC_PUBLIC, ACC_SUPER
Constant pool:#1 = Methodref          #5.#22         // java/lang/Object."<init>":()V#2 = Fieldref           #23.#24        // java/lang/System.out:Ljava/io/PrintStream;#3 = Methodref          #25.#26        // java/io/PrintStream.println:(I)V#4 = Class              #27            // cn/yj/jvm/Demo3_2#5 = Class              #28            // java/lang/Object#6 = Utf8               <init>#7 = Utf8               ()V#8 = Utf8               Code#9 = Utf8               LineNumberTable#10 = Utf8               LocalVariableTable#11 = Utf8               this#12 = Utf8               Lcn/yj/jvm/Demo3_2;#13 = Utf8               main#14 = Utf8               ([Ljava/lang/String;)V#15 = Utf8               args#16 = Utf8               [Ljava/lang/String;#17 = Utf8               a#18 = Utf8               I#19 = Utf8               b#20 = Utf8               SourceFile#21 = Utf8               Demo3_2.java#22 = NameAndType        #6:#7          // "<init>":()V#23 = Class              #29            // java/lang/System#24 = NameAndType        #30:#31        // out:Ljava/io/PrintStream;#25 = Class              #32            // java/io/PrintStream#26 = NameAndType        #33:#34        // println:(I)V#27 = Utf8               cn/yj/jvm/Demo3_2#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               (I)V
{public cn.yj.jvm.Demo3_2();descriptor: ()Vflags: ACC_PUBLICCode:stack=1, locals=1, args_size=10: aload_01: invokespecial #1                  // Method java/lang/Object."<init>":()V4: returnLineNumberTable:line 3: 0LocalVariableTable:Start  Length  Slot  Name   Signature0       5     0  this   Lcn/yj/jvm/Demo3_2;public static void main(java.lang.String[]);descriptor: ([Ljava/lang/String;)Vflags: ACC_PUBLIC, ACC_STATICCode:stack=2, locals=3, args_size=10: bipush        102: istore_13: iload_14: iinc          1, 17: iinc          1, 110: iload_111: iadd12: iload_113: iinc          1, -116: iadd17: istore_218: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;21: iload_122: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V25: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;28: iload_229: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V32: returnLineNumberTable:line 6: 0line 7: 3line 8: 18line 9: 25line 10: 32LocalVariableTable:Start  Length  Slot  Name   Signature0      33     0  args   [Ljava/lang/String;3      30     1     a   I18      15     2     b   I
}
SourceFile: "Demo3_2.java"

分析:
注意 iinc 指令是直接在局部变量 slot 上进行运算
a++ 和 ++a 的区别是先执行 iload 还是 先执行 iinc

5.条件判断

指令 助记符 含义
0x99 ifeq 判断是否 == 0
0x9a ifne 判断是否 != 0
0x9b iflt 判断是否 < 0
0x9c ifge 判断是否 >= 0
0x9d ifgt 判断是否 > 0
0x9e ifle 判断是否 <= 0
0x9f if_icmpeq 两个int是否 ==
0xa0 if_icmpne 两个int是否 !=
0xa1 if_icmplt 两个int是否 <
0xa2 if_icmpge 两个int是否 >=
0xa3 if_icmpgt 两个int是否 >
0xa4 if_icmple 两个int是否 <=
0xa5 if_acmpeq 两个引用是否 ==
0xa6 if_acmpne 两个引用是否 !=
0xc6 ifnull 判断是否 == null
0xc7 ifnonnull 判断是否 != null

几点说明:
byte,short,char 都会按 int 比较,因为操作数栈都是 4 字节 goto 用来进行跳转到指定行号的字节码

字节码:

     0: iconst_01: istore_12: iload_13: ifne          126: bipush        108: istore_19: goto          1512: bipush        2014: istore_115: return

注意,比较小的数用iconst来表示,ifne 12。判断操作数中的栈是不是不等于0,如果不等于0就会跳转到12行,如果不成立,就会执行后面的代码,接着往下走。goto 15是直接跳转到15行。
思考
细心的同学应当注意到,以上比较指令中没有 long,float,double 的比较,那么它们要比较怎 么办?

参考 https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.lcmp

6.循环控制指令

其实循环控制还是前面介绍的那些指令,例如 while 循环:

public class Demo3_4 {public static void main(String[] args) {int a = 0;while (a < 10) { a++;} }
}
字节码是:0: iconst_0 1: istore_1 2: iload_1 3: bipush        10 5: if_icmpge     14 8: iinc          1, 1 11: goto          2 14: return
public class Demo3_5 {public static void main(String[] args) {int a = 0;do {a++;}while (a < 10);}}
}

后再看看 for 循环:

public class Demo3_6 {public static void main(String[] args) {for (int i = 0; i < 10; i++) { }}
}
 0: iconst_0 1: istore_1 2: iload_1 3: bipush        10 5: if_icmpge     14 8: iinc          1, 1 11: goto          214: return

注意 比较 while 和 for 的字节码,你发现它们是一模一样的,殊途也能同归

7 练习 - 判断结果

请从字节码角度分析,下列代码运行的结果:

public class Demo3_6_1 {public static void main(String[] args) {int i = 0;int x = 0;while (i < 10) {x = x++;i++;}System.out.println(x);}
}

x=x++;
x++的过程对应两条字节码指令
iload_x
iinc x,1
初始x(0)
iload是把局部变量表中的0读进操作数栈。读完以后,我们iinc进行自增。自增的结果是局部变量表中的x变为1,它然后又执行了赋值操作,把操作数栈中的0取出来,再覆盖掉局部变量中的x.等第一次循环之后,局部变量表中的x仍然是0,即使再循环多少次,值仍然为0。

8 构造方法

public class Demo3_8_1 {static int i = 10;static {i = 20;}static {i = 30;}public static void main(String[] args) {System.out.println(Demo3_8_1.i);}
}

编译器会按从上至下的顺序,收集所有 static 静态代码块和静态成员赋值的代码,合并为一个特殊的方法 < cinit> ()V :

stack=1, locals=0, args_size=00: bipush        102: putstatic     #2                  // Field i:I5: bipush        207: putstatic     #2                  // Field i:I10: bipush        3012: putstatic     #2                  // Field i:I15: return

最后赋值的是30,所以结果是30

init()V

public class Demo4 {private String a = "s1";{b = 20;}private int b = 10;{a = "s2";}public Demo4(String a, int b) {this.a = a;this.b = b;}public static void main(String[] args) {Demo4 d = new Demo4("s3", 30);System.out.println(d.a);System.out.println(d.b);}
}

编译器会按从上至下的顺序,收集所有 {} 代码块和成员变量赋值的代码,形成新的构造方法,但原始构造方法内的代码总是在后

Code:stack=2, locals=3, args_size=30: aload_01: invokespecial #1                  // Method java/lang/Object."<init>":()V4: aload_05: ldc           #2                  // String s17: putfield      #3                  // Field a:Ljava/lang/String;10: aload_011: bipush        2013: putfield      #4                  // Field b:I16: aload_017: bipush        1019: putfield      #4                  // Field b:I22: aload_023: ldc           #5                  // String s225: putfield      #3                  // Field a:Ljava/lang/String;//原始构造方法在最后执行28: aload_029: aload_130: putfield      #3                  // Field a:Ljava/lang/String;33: aload_034: iload_235: putfield      #4                  // Field b:I38: return


执行顺序:静态代码块->非静态代码块->类的构造方法

9 方法调用

public class Demo5 {public Demo5() {}private void test1() {}private final void test2() {}public void test3() {}public static void test4() {}public static void main(String[] args) {Demo5 demo5 = new Demo5();demo5.test1();demo5.test2();demo5.test3();Demo5.test4();}
}

不同方法在调用时,对应的虚拟机指令有所区别

私有、构造、被final修饰的方法,在调用时都使用invokespecial指令
普通成员方法在调用时,使用invokespecial指令。因为编译期间无法确定该方法的内容,只有在运行期间才能确定
静态方法在调用时使用invokestatic指令
invokespecial在调用时无法确定是调用哪个对象的方法,也许是父类的,也许是子类的。invokespecial称之为动态绑定,在运行的时候确定调用哪个对象的方法。invokestatic静态绑定直接就能找到方法的入口地址了。

Code:stack=2, locals=2, args_size=10: new           #2                  // class com/nyima/JVM/day5/Demo5 3: dup4: invokespecial #3                  // Method "<init>":()V7: astore_18: aload_19: invokespecial #4                  // Method test1:()V12: aload_113: invokespecial #5                  // Method test2:()V16: aload_117: invokevirtual #6                  // Method test3:()V20: invokestatic  #7                  // Method test4:()V23: return

new 是创建【对象】,给对象分配堆内存,执行成功会将【对象引用】压入操作数栈
dup 是复制操作数栈栈顶的内容,本例即为【对象引用】,为什么需要两份引用呢,一个是要配合 invokespecial 调用该对象的构造方法 “init”

JVM学习-字节码指令相关推荐

  1. jvm理论-字节码指令

    Java虚拟机的指令由一个字节长度的.代表着某种特定操作含义的数字(称为操作码,Opcode)以及跟随其后的零至多个代表此操作所需参数(称为操作数,Operands)而构成. 基本数据类型 1.除了l ...

  2. JVM笔记:Java虚拟机的字节码指令详解

    1.字节码 Java能发展到现在,其"一次编译,多处运行"的功能功不可没,这里最主要的功劳就是JVM和字节码了,在不同平台和操作系统上根据JVM规范的定制JVM可以运行相同字节码( ...

  3. java虚拟机编译_[四] java虚拟机JVM编译器编译代码简介 字节码指令实例 代码到底编译成了什么形式...

    前言简介 前文已经对虚拟机进行过了简单的介绍,并且也对class文件结构,以及字节码指令进行了详尽的说明 想要了解JVM的运行机制,以及如何优化你的代码,你还需要了解一下,java编译器到底是如何编译 ...

  4. 【深入理解java虚拟机】 - JVM字节码指令介绍

    文章目录 什么是字节码指令 javap的用法 字节码与数据类型 字节码指令集 加载和存储指令 运算指令 类型转换指令 对象创建与访问指令 操作数栈管理指令 控制转移指令 方法调用和返回指令 异常处理指 ...

  5. 深入理解JVM虚拟机(五):字节码指令简介

    Java 虚拟机的指令由一个字节长度的.代表着某种特定操作含义的数字(称为操作码)以及跟随其后的零至多个代表此操作所需参数(操作数)而构成.由于 Java 虚拟机采用面向操作数栈而不是寄存器的架构,所 ...

  6. 【JVM源码解析】模板解释器解释执行Java字节码指令(上)

    本文由HeapDump性能社区首席讲师鸠摩(马智)授权整理发布 第17章-x86-64寄存器 不同的CPU都能够解释的机器语言的体系称为指令集架构(ISA,Instruction Set Archit ...

  7. idea如何反编译字节码指令_美团点评:Java字节码增强技术,线上问题诊断利器...

    作者简介:泽恩,美团到店住宿业务研发团队工程师.文章转载于公众号:美团技术团队 1. 字节码 1.1 什么是字节码? Java之所以可以"一次编译,到处运行",一是因为JVM针对各 ...

  8. 为什么要推荐大家学习字节码?

    配套视频: 为什么推荐大家学习Java字节码 https://www.bilibili.com/video/av77600176/ 一.背景 本文主要探讨:为什么要学习 JVM 字节码? 可能很多人会 ...

  9. 你还在为怎么查看字节码指令而担忧吗?

    来自:烟雨星空 前言 我们平时编码过程中,可能很少去查看 Java 文件编译后的字节码指令.但是,不管你是因为对技术非常热爱,喜欢刨根问底,还是想在别人面前装X .我认为,都非常有必要了解一下常见的字 ...

最新文章

  1. docker-dockerfile
  2. 多所高校宣布:延迟开学!
  3. u3d:强大的dotween。使用dotween处理延时问题,最最最最简单的办法
  4. 使用 XML 实现按钮改变焦点设置背景图
  5. Knowledge Test about Match
  6. Direct3D9 Fx/HLSL的若干条有用的笔记
  7. Scrapy Crawl 运行出错 AttributeError: 'xxxSpider' object has no attribute '_rules' 的问题解决...
  8. matplotlib.patches.Polygon
  9. Cocos2d-x属性变化动作
  10. linux内存源码分析 - 内存池
  11. 单片机控制步进电机-电路连接
  12. NVIDIA显卡型号有哪些?怎么知道自己电脑的型号?
  13. java中标签内容居中显示_图形标签中图像上的figcaption标签的居中和对齐宽度
  14. 【css】i标签icon图标旋转样式
  15. 通过码云来学习Git的进阶技能(文末附福利!)
  16. 35 米色系网页设计
  17. 搜索算法工程师、专家、leader
  18. 用手机打开word图表位置很乱_超实用!word、excel、ppt文件互相转换技巧来了
  19. 增强子调控法则被揭示:序列不同,但可以在不同物种起相似功能
  20. 2019-11-29-逗比面试官成长路线-如何让被面试者觉得糟心

热门文章

  1. 不写一行代码,基于Jmeter打造性能测试数据平台
  2. android去除标题栏
  3. http/https 协议(概略)
  4. 使用python统计出txt文档中含有某个单词的个数
  5. mysql查询每个用户第一条数据_MySQL数据库订单表按用户邮箱字段分组查询每个用户的第一条记录...
  6. 深入了解nginx.conf配置文件
  7. 栈和队列:1.栈(Stack)
  8. mysql开源内库_MySQL数据库(查询语句)
  9. 我的docker随笔37:使用gitlab和jenkins实现CICD
  10. ubuntu系统debootstrap的再三实验