【欧冠体育体育竞猜 官网入口】

一篇带给你JVM 字节码剖析进程

发布日期:2022-08-06 23:55    点击次数:198
概述

概述本文主若是基于 .class 文件,举行阐发 .class 文件的内容。

这部份集团感应主若是属于策画机构拓展的内容,巨匠可以或许一起来深造一下 Java 字节码的策画布局以及感想感染一上策画者的策画。

class 类文件布局

Java 供应 javap 敕令可以或许阐发字节码文件,我们可运用 javap -verbose 敕令阐发一个字节码文件时, 将会阐发该字节码文件的魔数、版本号、常量池、类信息、类的布局编制、类中的编制信息、类变量与成员变量等信息。

一个俭朴的 Java 代码

public class TestClass {      private int m;      public int inc() {        return ++m;     } } 

下图体现的是 Java 代码编译后 .class 文件的十六进制信息

bytecode_十六进制.png

为了方便对比我执行一下 javap -v TestClass

Classfile /../../TestClass.class   Last modified 2021-2-6; size 306 bytes   MD5 checksum eeba40cc40cc28ef4d416ff70d901561   Compiled from "TestClass.java" public class cn.edu.cqvie.jvm.bytecode.TestClass   minor version: 0   major version: 52   flags: ACC_PUBLIC, ACC_SUPER Constant pool:    #1 = Methodref          #4.#15         // java/lang/Object."<init>":()V    #2 = Fieldref           #3.#16         // cn/edu/cqvie/jvm/bytecode/TestClass.m:I    #3 = Class              #17            // cn/edu/cqvie/jvm/bytecode/TestClass    #4 = Class              #18            // java/lang/Object    #5 = Utf8               m    #6 = Utf8               I    #7 = Utf8               <init>    #8 = Utf8               ()V    #9 = Utf8               Code   #10 = Utf8               LineNumberTable   #11 = Utf8               inc   #12 = Utf8               ()I   #13 = Utf8               SourceFile   #14 = Utf8               TestClass.java   #15 = NameAndType        #7:#8          // "<init>":()V   #16 = NameAndType        #5:#6          // m:I   #17 = Utf8               cn/edu/cqvie/jvm/bytecode/TestClass   #18 = Utf8               java/lang/Object {   public cn.edu.cqvie.jvm.bytecode.TestClass();     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 3: 0    public int inc();     descriptor: ()I     flags: ACC_PUBLIC     Code:       stack=3, locals=1, args_size=1          0: aload_0          1: dup          2: getfield      #2                  // Field m:I          5: iconst_1          6: iadd          7: dup_x1          8: putfield      #2                  // Field m:I         11: ireturn       LineNumberTable:         line 8: 0 } SourceFile: "TestClass.java" 
Java 字节码布局

bytecode_布局.png

1. 魔数和 Class 文件版本

魔数:全体的.class 字节码文件的前4个字节都是魔数,魔数为安稳值: 0xCAFEBABE

版本信息,魔数今后的4个字节是版本信息,前两个字节默示 minor version (次版本号), 后2个字节默示major version (主版本号)。这里的版本号 00 00 00 34 换算成十进制表, 默示次版本号为0, 主版本号为 52. 所以该文件的版本号为 1.8.0。可以或许经由过程 java -version 来验证这一点。

➜  ~ java -version java version "1.8.0_281" Java(TM) SE Runtime Environment (build 1.8.0_281-b09) Java HotSpot(TM) 64-Bit Server VM (build 25.281-b09, mixed mode) 
2. 常量池

常量池 (constant pool): 2+N个字节 紧接着主版本号今后的就是常量池入口。一个java 类中定义的良多信息都是由常量池来形貌的,可以或许将常量池看做是 Class 文件的资源货仓旅馆,比喻说Java类中变量的编制与变量信息,都是存储在常量池中。常量池中次要存储2类常量:字面量与符号引用。

字面量, 如字符串文本,java 中声名为final 的常量值等。 符号引用, 如类和接口的全范围制名, 字段的名称和形貌符,编制的名称和形貌符等。

常量池的总体布局:Java类所对应的常量池次要由常量池(常量表)的数量与常量池数组这两部份怪异造成。常量池中常量数量紧跟着在主版本号后面,盘踞2字节: 常量池长度 比喻这里我们的十六进制就是 00 13 代表有 18 个常量。常量数组则紧跟着常量池数量今后。常量池数组与普通数组差别的是, 常量池数组中差别的元素的范例,布局都是差别的。长度固然也就差别;然则, 一种元素的第一种元素的第一个数据都是一个u1范例, 该字节是一个标识位,盘踞1个字节。JVM在剖析常量池时,会更具这个u1 范例来取得元素的具体范例。值得留心的是:常量池中元素的个数 = 常量池数 -1 (个中0姑且不实用), 目标是餍足某些常量池索引值的数据在特定环境下需求剖明【不引用任何一个常量池】的含义:基本启事在于,索引为0也是一个常量(留存常量),只不过他不位于常量表中。这个常量就对应null值, 所以常量池的索引是从1起头而非0起头。

上面表中形貌了11种数据范例的机构, 其实在jdk1.7今后又添加了3种(CONSTANT_MethodHandle_info, CONSTANT_MethodType_info 以及CONSTANT_InvokeDynami_info)。这样一共14种。

在JVM尺度中, 每个变量/字段都有形貌信息, 形貌信息次要的浸染是形貌字段的数据范例、编制的参数列表(蕴含数量、范例、按次)与前去值。痛处形貌符 划定端方, 根抵数据范例和代表无前去值的的void 范例都用一个大写字符来默示, 工具范例则运用字符L加工具的全限制名称来默示。为了压缩字节码文件的体积 关于根抵数据范例,JVM都只运用一个大写字母来默示,以下所示:B-byte, C-char, D-double, F-float, I-int, J-long, S-short, Z-boolean , V -void L -默示工具范例,如: Ljava/lang/String;

关于数组范例来说,招聘信息每个维度运用一个前置的 [来默示, 如int[] 被符号为 [I , String[][]被默示为 [[java/lang/String;

用形貌符形貌编制时, 根据先参数列表, 后前去值的按次来形貌. 参数列表根据参数的严厉按次放在一组()内, 如编制: String getRealnameByIdNickname(int id, String name)的形貌符为: (I, Ljava/lang/String;) Ljava/lang/String

Class 字节码中有两种数据范例 字节数据间接量:这是根抵的数据范例。共细分为u一、u二、u四、u8四种,划分代表间断的1个字节、2个字节、4个字节、8个字节形成的总体数据。 表(数组):默示由多个根抵数据或别的表,根据既定按次形成的大的数据鸠合。表是有布局的 。它的布局体今朝:形成表的身分所在的职位地方温顺序都是 严厉定义好的。

常量池常量

0A 00 04 00 0F method_info 00 04 指向常量池中的常量的职位地方, 00 0F 指向的是 15 的职位地方。 09 00 03 00 10 field_info 00 03 指向 3 的职位地方 00 10 (16 职位地方) 07 00 11 class info 00 11 (17 职位地方) 07 00 12 class info 00 12 (18 职位地方) 01 00 01 6D utf8 长度为 1 内容为 6D 默示内容转换为 10 进制为 109 转换为 ASCII 码最后的终局为 m 01 00 01 49 utf8 长度为 1 内容为 I 01 00 06 3C 69 6E 69 74 3E utf8 长度为 8 内容为 01 00 03 28 29 56 utf8 长度为 3 内容为:()V 01 00 04 43 6F 64 65 utf8长度为 4 内容为 Code 01 00 0F 4C 69 6E 65 4E 75 6D 62 65 72 54 61 62 6C 65 utf8 长度为 15 内容为 LineNumberTable 01 00 03 69 6E 63 utf8 长度为3 内容为 inc 01 00 03 28 29 49 长度为 3 内容为 ()I 01 00 0A 53 6F 75 72 63 65 46 69 6C 65 长度为 10 内容为 SourceFile 01 00 0E 54 65 73 74 43 6C 61 73 73 2E 6A 61 76 61 长度为 14 内容为 TestClass.java 0C 00 07 00 08 NameAndType 内容 指向 7 职位地方。00 08 (8 职位地方) 0C 00 05 00 06 NameAndType 内容 指向 5 职位地方。00 06 (6 职位地方) 01 00 23 63 6E 2F 65 64 75 2F 63 71 76 69 65 2F 6A 76 6D 2F 62 79 74 65 63 6F 64 65 2F 54 65 73 74 43 6C 61 73 73 长度 35 内容 cn/edu/cqvie/jvm/bytecode/TestClass 01 00 10 6A 61 76 61 2F 6C 61 6E 67 2F 4F 62 6A 65 63 74 长度 16 内容 java/lang/Object 3. 拜访符号

Access_Flag 拜访符号 拜访标识信息蕴含该Class文件时类和接口是否被定义成为了public,是不是 abstract, 假定是类,是否被声名为成final。经由过程扇面的源代码。

0x 00 21: 默示是0x0020 和0x0001的并集, 默示 ACC_PUBLIC 与 ACC_SUPER

bytecode_拜访符号.png

4. 类索引、父类索引

00 03 类名, 03 常量池职位地方 cn/edu/cqvie/jvm/bytecode/TestClass

00 04 父类名. 04 常量池职位地方 java/lang/Object

00 00 接口个数, 0 个, 接口个数至多 65535

5. 字段表鸠合

00 01 字段的个数, 这里有一个

bytecode_字段表.png

字段表布局
field_info {   u2 access_flags; 00 02 Fieldref   u2 name_index; 00 05 (默示字段名称在常量池中的索引职位地方) m   u2 descriptor_index; 00 06 (形貌符索引) I    u2 attributes_count; 00 00   attribute_info attributes[attributes_count]  } 
6. 编制表鸠合

00 02 默示有两个编制

bytecode_编制表.png

编制表布局

method_info {   u2 access_flags; 00 01  Methodref   u2 name_index;  // 00 07  <init>   u2 descriptor_index; 00 08 // ()V   u2 attributes_count; 00 01 // 属性布局   attributes_info attributes[attributes_count] } 

编制属性布局

attribute_info {   u2 attribute_name_index; 00 09 // Code   u4 attribute_length; 00 00 00 1D 长度 29   u1 info[attribute_length];     ...  } 
7. 属性表鸠合

00 09 00 00 1D 00 01 00 01 00 00 00 05 2A B7 00 01 B1 00 00 00 01 00 0A 00 00 00 06 00 01 00 00 00 03 00

"Code" 默示上面是执行代码

JVM 预约义了一部份的attribute, 然则编译器自身也可以完成自身的attribute 写入class文件中, 供运行时运用。差别的attribute 经由过程attribute_name_index 来判别。

JVM 尺度预约义的attribute

Code 布局

Code attribute 的浸染是生活生涯该放的的布局,如所对应的字节码

Code_attribute {     u2 attribute_name_index;  // 00 09 ==> Code     u4 attribute_length; // 00 00 00 1D ==> 29     u2 max_stack;   //  00 01 栈深度为 1 (栈帧中操作数栈的深度)     u4 code_length;   // 00 00 00 05 指令的长度是几多     u1 code[code_lenght]; // 2A B7 00 01 B1 个中 2A          // 2A aload_0     // B7 invokespecial     // 00 #1     // 01 <java/lang/Object.<init>>     // B1 return          // 0 aload_0   // 1 invokespecial #1 <java/lang/Object.<init>>   // 4 return     u2 exception_table_length; // 00 00      {         u2 start_pc;         u2 end_pc;         u2 handler_pc;         u2 catch_type;     } exception_table[exception_table_lenght];     u2 attributes_count; // 00 01     attribute_info attributes[attributes_count]; } 

attribute_length 默示 attribute 所包孕的字节数, 不包孕attribute_name_index 和 attribute_length 字段。

max_stack 默示这个编制运行的任什么时光刻所能达到的操作数栈的最大深度。

max_locals 默示编制执行时期创立的部份变量的数量,包孕用来默示传入的参数的部份变量。

code_length 默示该编制所包孕的字节码的字节数以及具体的指令码。

具体字节码即是该编制被调历时,虚拟机所执行的字节码。

exception_table, 这里寄放的是处理惩罚很是信息。

每个 exception_table, 这里寄放的是处理惩罚很是的信息。

每个 exception_table 表项由start_pc , end_pc, handler_pc, catch_type 形成。 start_pc 和 end_pc 默示在code数组中的从 start_pc 到 end_pc 处(包孕start_pc, 不包孕end_pc)的指令抛出的很是会由 这个表项来处理惩罚。 handler_pc 默示处理惩罚很是的代码的起头处。catch_type 默示会被处理惩罚的很是范例, 它指向常量池里的一个很是类。当catch_type为0时, 默示处理惩罚全体的很是。 LineNumberTable 的布局
LineNumberTable_attribute {     u2 attribute_name_index; //00 0A 常量池10号职位地方  LineNumberTable     u4 attribute_lenght;  // 00 00 00 06 一共 6个长度     u2 line_number_table_length;  // 00 01 晖映的对数  1 对     line_number_info {         u2 start_pc; // 指令行号  00 00          u2 line_number; // 源码调试  00 03     }     line_number_table[line_number_table_length];  } 

部份变量表 LocalVariableTable

LocalVariableTable_attribute {     u2 attribute_name_index;      u4 attribute_lenght;     u2 local_variable_table_length;      {         u2 start_pc;         u2 line_number;         u2 name_index;         u2 descriptor_index;          u2 index;     } } 
总结 布局编制中会初始化成员属性的默认值,假定自身完成为了默认的布局编制, 依然照旧在布局编制中赋值,这就是对指令的重排序。 假定多个布局编制那末每个布局编制中都有初始化成员变量的属性,来保障每个布局编制初始化的岁月都市执行到属性的初始化进程。 假定布局编制中有执行语句, 那末会先执行赋值信息, 尔后在执行自定义的执行代码。 关于Java每个实例编制(非动静编制), 其在编译西席成的字节码中比理论编制多一个参数, 它位于编制的第一个参数职位地方. 我们就能在今后编制中 的this去拜访今后工具中的this这个操作是在Javac 编译器在编译时期将this的拜访转换为对通俗实例编制的参数拜访,接上去在运行时期, 由JVM的调用实例编制时, 自动向实例编制中传入该this参数, 所以在实例编制的部份变量表中, 起码会一个指向今后工具的部份变量。 字节码关于处理惩罚很是的编制: 统一给与很是表的编制来对很是处理惩罚。 在Jdk1.4.2从前的版本中, 实在不是运用很是表的编制来对很是举行处理惩罚的,而是给与特定的指令编制。 当很是处理惩罚存在finally 语句块时,今世化的JVM才去的处理惩罚编制将finally语句块的自身拼接到每个catch块后面, 换句话说,顺序中中存在多个catch块,就会在每个catch块后面重复几多个finally 的语句块字节码。