深入理解JVM-字节码

╰+攻爆jí腚メ 2021-09-29 23:34 514阅读 0赞

字节码

  • Class文件
  • Java字节码整体结构
  • Java字节码结构
  • 常量池中数据类型的结构总表
  • 访问标志
  • 字段表集合
  • 方法表
    • 方法的属性结构
    • JVM规范预定义的attribute
    • Code结构
    • 附加属性
  • 代码示例
    • 示例1
    • 示例2
    • 示例3

Class文件

  1. Java源程序被编译器编译后称为class文件,而Class文件则由自己的格式,其采用一种类似于C语言结构体的伪结构来存储数据,这种伪结构只有两种数据类型,无符号数和表。正是因为class文件拥有自己的格式才使得Java程序可以跨平台执行。

Java字节码整体结构





























































字节数 名称 含义
4 Magic Number 魔数,值为0xCAFEBABE
2+2 Version 包括minor version和major version
2 + n Constant Pool 字符串常量池
2 Access Flags 权限
2 This Class Name 当前类的全限定类名,指向常量池
2 Super Class Name 父类的全限定类名,指向常量池
2+n Interfaces 所实现的接口以及接口表
2+n Fields 拥有的字段以及字段表
2+n Methods 拥有的方法以及方法表
2+n Attributes 拥有的属性以及属性表

在这里插入图片描述
在这里插入图片描述

Java字节码结构

  1. Class字节码中有两种数据类型:
  • 字节数据直接量:这是基本的数据类型。共分为u1、u2、u4、u8四种,分别代表连续的一个字节、2个字节、4个字节、8个字节组成的整体数据。
  • 表(数组):表是由多个基本数据或其他表,按照既定顺序组成的大的数据集合。表是有结构的,它的结构体现在:组成表的成分所在的位置和顺序都是已经严格定义好的。

常量池中数据类型的结构总表

在这里插入图片描述

  1. 上面的表中描述了11中常用的数据类型,jdk1.7之后又增加了三种,CONSTANT\_MethodHandle\_infoCONSTANT\_MethodType\_info以及CONSTANT\_MethodDynamic\_info

访问标志

在这里插入图片描述如果一个类是public以及final的,那么其标志值为分别对应的标志值相或,所以其标志值为0x0011。

字段表集合

字段表用于描述类和接口中声明的变量。这里的字段包含了类级别变量以及实例变量,但是不包括方法内部的局部变量。

fields_count: u2 (表示字段的个数)

在这里插入图片描述

方法表

method_count:u2 (表示方法的个数)
在这里插入图片描述

方法的属性结构

方法中的每个属性都是一个attribute_info结构

在这里插入图片描述

JVM规范预定义的attribute

  • JVM预定义了部分attribute,但是编译器自己也可以实现自己的attribute写入class文件里,供运行时使用。
  • 不同的attribute通过attribute_name_index来区分。

在这里插入图片描述

Code结构

Code attribute的作用是保存该方法的结构

在这里插入图片描述

  • attribute_length 表示attribute所包含的字节数,不包含attribute_name_index和attribute_length字段。
  • max_stack表示这个方法运行的任何时刻所能达到的操作数栈的最大深度
  • max_locals表示方法执行期间创建的局部变量的数目,包含用来表示传入的参数的局部变量
  • code_length表示该方法所包含的字节码的字节数以及具体的指令码
  • 具体字节码即是该方法被调用时,虚拟机所指向的字节码
  • exception_table,异常表,这里存放的是处理异常的信息
  • 每个exception_table表项由start_pc,end_pc,handle_pc,catch_type组成
  • start_pc和end_pc表示在code数组中的从start_pc和end_pc处(包含start_pc,不包含end_pc)的指令抛出的异常由这个表项来处理
  • handler_pc表示处理异常的代码的开始处。catch_type表示会被处理的异常类型,它指向常量池里的一个异常类。当catch_type为0时,表示处理所有异常

附加属性

方法的附加属性:

  • LineNumberTable:这个属性用来表示code数组·中的字节码和Java代码行数之间的关系。这个属性可以用来在调试的时候定位代码执行的行数。

代码示例

示例1

  1. public class MyTest1 {
  2. private int a = 1;
  3. public int getA() {
  4. return a;
  5. }
  6. public void setA(int a) {
  7. this.a = a;
  8. }
  9. }
  10. E:\IdeaProjects\jvm_lecture\target\classes>javap -verbose com/wangzhao/jvm/bytecode/MyTest1
  11. Classfile /E:/IdeaProjects/jvm_lecture/target/classes/com/wangzhao/jvm/bytecode/MyTest1.class
  12. Last modified 2019-7-21; size 497 bytes
  13. MD5 checksum 5fea812ebd710c6be8d438b7e0959a03
  14. Compiled from "MyTest1.java"
  15. public class com.wangzhao.jvm.bytecode.MyTest1
  16. minor version: 0
  17. major version: 52
  18. flags: ACC_PUBLIC, ACC_SUPER
  19. Constant pool:
  20. #1 = Methodref #4.#20 // java/lang/Object."<init>":()V
  21. #2 = Fieldref #3.#21 // com/wangzhao/jvm/bytecode/MyTest1.a:I
  22. #3 = Class #22 // com/wangzhao/jvm/bytecode/MyTest1
  23. #4 = Class #23 // java/lang/Object
  24. #5 = Utf8 a
  25. #6 = Utf8 I
  26. #7 = Utf8 <init>
  27. #8 = Utf8 ()V
  28. #9 = Utf8 Code
  29. #10 = Utf8 LineNumberTable
  30. #11 = Utf8 LocalVariableTable
  31. #12 = Utf8 this
  32. #13 = Utf8 Lcom/wangzhao/jvm/bytecode/MyTest1;
  33. #14 = Utf8 getA
  34. #15 = Utf8 ()I
  35. #16 = Utf8 setA
  36. #17 = Utf8 (I)V
  37. #17 = Utf8 (I)V
  38. #18 = Utf8 SourceFile
  39. #19 = Utf8 MyTest1.java
  40. #20 = NameAndType #7:#8 // "<init>":()V
  41. #21 = NameAndType #5:#6 // a:I
  42. #22 = Utf8 com/wangzhao/jvm/bytecode/MyTest1
  43. #23 = Utf8 java/lang/Object
  44. {
  45. public com.wangzhao.jvm.bytecode.MyTest1();
  46. descriptor: ()V
  47. flags: ACC_PUBLIC
  48. Code:
  49. stack=2, locals=1, args_size=1
  50. 0: aload_0
  51. 1: invokespecial #1 // Method java/lang/Object."<init>":()V
  52. 4: aload_0
  53. 5: iconst_1
  54. 6: putfield #2 // Field a:I
  55. 9: return
  56. LineNumberTable:
  57. line 7: 0
  58. line 10: 4
  59. LocalVariableTable:
  60. Start Length Slot Name Signature
  61. 0 10 0 this Lcom/wangzhao/jvm/bytecode/MyTest1;
  62. public int getA();
  63. descriptor: ()I
  64. flags: ACC_PUBLIC
  65. Code:
  66. stack=1, locals=1, args_size=1
  67. 0: aload_0
  68. 1: getfield #2 // Field a:I
  69. 4: ireturn
  70. LineNumberTable:
  71. line 13: 0
  72. LocalVariableTable:
  73. Start Length Slot Name Signature
  74. 0 5 0 this Lcom/wangzhao/jvm/bytecode/MyTest1;
  75. public void setA(int);
  76. descriptor: (I)V
  77. flags: ACC_PUBLIC
  78. Code:
  79. stack=2, locals=2, args_size=2
  80. 0: aload_0
  81. 1: iload_1
  82. 2: putfield #2 // Field a:I
  83. 5: return
  84. LineNumberTable:
  85. line 18: 0
  86. line 19: 5
  87. LocalVariableTable:
  88. Start Length Slot Name Signature
  89. 0 6 0 this Lcom/wangzhao/jvm/bytecode/MyTest1;
  90. 0 6 1 a I
  91. }
  92. SourceFile: "MyTest1.java"

通过javap命令,我们对MyTest1的class文件解析可得到如下结果:
在这里插入图片描述

在这里插入图片描述在这里插入图片描述
通过常量池,我们可以找到这个类拥有哪些字段,方法以及父类和接口的全限定类名。需要注意的是,常量池中的索引是从1开始,并没有使用0是因为0表示不引用任何一个常量。

在这里插入图片描述

可以看到==对于类中实例变量的赋值,是在构造方法中完成的。==同时编译器为我们自动增加了一个构造方法。

在这里插入图片描述
对于getA()方法,我们并没有传入参数,但是参数个数却为1,这是因为对于Java类中的每一个实例方法(非static)方法,其在编译后所生成的字节码当中,方法参数的数量总是会比源代码中方法参数的数量多一个(this),它位于方法的参数的第一个参数位置处;这样,我们就可以在java的实例方法中使用this来去访问当前对象的属性以及其他方法。

示例2

  1. public class MyTest2 {
  2. String str = "Welcome";
  3. private int x = 5;
  4. public static Integer in = 10;
  5. public static void main(String[] args) {
  6. MyTest2 myTest2 = new MyTest2();
  7. myTest2.setX(8);
  8. in = 20;
  9. }
  10. public void setX(int x) {
  11. this.x = x;
  12. }
  13. private static void test(String str){
  14. synchronized (str){
  15. System.out.println("hello world");
  16. }
  17. }
  18. }

在这里插入图片描述

可以看到synchronized与两个指令相关联,分别为minitorenter以及monitorexit。
minortorenter的作用是:执行该指令的线程尝试获取相关对象的监视器的所有权,如果监视器的计数为0,则线程进入监视器并将其计数设置为1。如果该线程已经拥有该对象的所有权,则监视器的计数加1(可重入)。而其他线程将阻塞,直到该对象的监视器计数为1。
minortorexit的作用是:拥有当前对象监视器所有权的线程递减关联对象的监视器计数,直到为0,当前线程退出监视器。

在这里插入图片描述
对于静态的字段,编译器会生成一个静态构造代码块,完成静态成员变量的赋值。

示例3

  1. public class MyTest3 {
  2. public void test(){
  3. try {
  4. InputStream is = new FileInputStream("test.txt");
  5. ServerSocket serverSocket = new ServerSocket(9999);
  6. }catch (FileNotFoundException ex){
  7. }catch (IOException ex){
  8. }catch (Exception ex){
  9. }finally {
  10. System.out.println("finally");
  11. }
  12. }
  13. }

在这里插入图片描述
在这里插入图片描述

可以看到对于异常的处理流程是通过异常表,罗列出所有可能的路径。

当异常处理存在finally语句块时,现代化的JVM采取的处理方式是将finally语句块的字节码拼接到每一个catch块后面,换句话说,程序中存在多少个catch块,就会在每一个catch块后面重复多少个finally语句块的字节码。

发表评论

表情:
评论列表 (有 0 条评论,514人围观)

还没有评论,来说两句吧...

相关阅读