以 u一、u二、u三、u四、u8 表明 1 个字节,2 个字节、4 个字节、8 个字节的无符号数。无符号数能够描述数字,索引引用、数量值和按照 UTF-8 编码构成的字符串值。html
ClassFile { u4 magic; //魔数, 用于识别class文件格式 u2 minor_version;//次版本号 u2 major_version;//主版本号 u2 constant_pool_count; //常量池计数器 cp_info constant_pool[constant_pool_count-1]; //常量池 u2 access_flags;//访问标志 u2 this_class;//类索引 u2 super_class;//父类索引 u2 interfaces_count;//接口计数器 u2 interfaces[interfaces_count];//接口索引集合 u2 fields_count;//字段计数器 field_info fields[fields_count];//字段表集合 u2 methods_count;//方法计数器 method_info methods[methods_count];//方法表 u2 attributes_count; //属性计数器 attribute_info attributes[attributes_count];附加属性表 }
每一个 Class 文件的头 4 个字节被称为魔数(Magic Number),它的惟一做用是肯定这个文件是否能被虚拟机接受的 Class 文件。它的值是 0xCAFEBABE (咖啡宝贝),很是容易记忆。java
紧接着的字节是次版本号(minor_version)和主版本号(major_version),Java 的版本号从 45 开始,Java1.1 以后的 JDK 大版本发布主版本号向上加一(Java1.0~Java1.1 使用了 45.0~45.3 的版本号)。注意高版本的 JDK 能向下兼容 之前的 Class 文件,但不能运行之后版本的 Class 文件。编程
常量池能够理解为 Class 文件的资源仓库,数组
主要存放:数据结构
字面量(Literal)并发
符号引用(Symbolic References)oracle
类型 | 标识 | 描述 |
---|---|---|
CONSTANT_Class |
7 | 类或接口的符号引用 |
CONSTANT_Fieldref |
9 | 字段的符号引用 |
CONSTANT_Methodref |
10 | 方法的符号引用 |
CONSTANT_InterfaceMethodref |
11 | 接口中方法的符号引用 |
CONSTANT_String |
8 | 字符串类型字面量 |
CONSTANT_Integer |
3 | 整型字面量 |
CONSTANT_Float |
4 | 浮点型字面量 |
CONSTANT_Long |
5 | 长整型字面量 |
CONSTANT_Double |
6 | 双精度浮点型字面量 |
CONSTANT_NameAndType |
12 | 字段或方法的部分符号引用 |
CONSTANT_Utf8 |
1 | UTF-8 编码字符串 |
CONSTANT_MethodHandle |
15 | 标识方法句柄 |
CONSTANT_MethodType |
16 | 标识方法类型 |
CONSTANT_InvokeDynamic |
18 | 动态方法调用点 |
用于识别类和接口层次的访问信息less
Flag Name | Value | Interpretation |
---|---|---|
ACC_PUBLIC |
0x0001 | 是否为被声明为 public ,能够被其余外部包中访问 |
ACC_FINAL |
0x0010 | 是否被声明 final,不能派生子类 |
ACC_SUPER |
0x0020 | Treat superclass methods specially when invoked by the invokespecial instruction. |
ACC_INTERFACE |
0x0200 | 标识一个接口 |
ACC_ABSTRACT |
0x0400 | 声明 abstract,抽象类,不能实例化 |
ACC_SYNTHETIC |
0x1000 | 声明 synthetic; 标识这个类并不是有用户代码产生 |
ACC_ANNOTATION |
0x2000 | 标识这个一个注解 |
ACC_ENUM |
0x4000 | 标识这是一个枚举 |
Class 文件就是由这三项数据来肯定这个类的继承关系。类索引用于肯定类的全限定类名,父索引用于肯定父类的全限定类名,接口索引集合用于描述类实现了那些接口。jvm
字段表集合[field_info] 用于描述接口或者类中声明的变量。字段(field) 包括类变量和实例变量,但不包括方法内部声明的局部变量。ide
字段表结构
field_info { u2 access_flags; //访问标识 u2 name_index; //名称索引 u2 descriptor_index; //描述符索引 u2 attributes_count; //属性计数器 attribute_info attributes[attributes_count]; //属性表 }
字段包含的信息:
字段访问标志
ACC_PUBLIC |
0x0001 | Declared public ; may be accessed from outside its package. |
---|---|---|
ACC_PRIVATE |
0x0002 | 声明 private ; |
ACC_PROTECTED |
0x0004 | 声明 protected ; |
ACC_STATIC |
0x0008 | 声明 static . |
ACC_FINAL |
0x0010 | 声明 final ; |
ACC_VOLATILE |
0x0040 | 声明 volatile ; |
ACC_TRANSIENT |
0x0080 | 声明 transient ; |
ACC_SYNTHETIC |
0x1000 | 声明 synthetic; 字段是否有编译器自动产生的 |
ACC_ENUM |
0x4000 | 声明字段是不是枚举 |
简单名称:没有类型和参数修饰的方法或者字段名称,如 inc()和 m 字段的简称为 inc 和 m
全限定名:com/demo/TestClass; “;”标识类的全限定名结束
描述符:用于描述字段的数据类型,方法的参数列表(数量、类型、顺序)和返回值
标识字符 | 表明类型 | 描述 |
---|---|---|
B |
byte |
基本类型 byte |
C |
char |
基本类型 char |
D |
double |
基本类型 double |
F |
float |
基本类型 float |
I |
int |
基本类型 int |
J |
long |
基本类型 long |
L ClassName ; |
reference |
对象类型,如 : Ljava/lang/Object |
S |
short |
基本类型 short |
Z |
boolean |
j 基本类型 boolean |
[ |
reference |
数组类型 ,如数组int[] 被记录为 [I,数组String[][] 被记录为 [[java/lang/String |
V | void | 特殊类型 Void |
描述符来描述方法时,按照先参数列表,后返回值的顺序描述;如:java.lang.String.toString() 描述为 () Ljava/lang/String,java.lang.String#valueOf(char[], int, int) 描述为 ([CII)Ljava/lang/String
方法描述采起与字段描述彻底一致的方式。
方法表结构
method_info { u2 access_flags; u2 name_index; u2 descriptor_index; u2 attributes_count; attribute_info attributes[attributes_count]; }
相关访问标识
Flag Name | Value | Interpretation |
---|---|---|
ACC_PUBLIC |
0x0001 | 方法是否 public |
ACC_PRIVATE |
0x0002 | 方法是否 private |
ACC_PROTECTED |
0x0004 | 方法是否 protected ; |
ACC_STATIC |
0x0008 | 方法是否 static . |
ACC_FINAL |
0x0010 | 方法是否 final ; |
ACC_SYNCHRONIZED |
0x0020 | 方法是否 synchronized ; 标识同步方法 |
ACC_BRIDGE |
0x0040 | 标识是否由编译器生成的桥接方法 |
ACC_VARARGS |
0x0080 | 方法是否接受不定参数 |
ACC_NATIVE |
0x0100 | 方法是否 native ; |
ACC_ABSTRACT |
0x0400 | 方法是否 abstract ; |
ACC_STRICT |
0x0800 | 方法是否 strictfp ; |
ACC_SYNTHETIC |
0x1000 | 方法是否为 synthetic; |
方法里定义的代码
方法里面的代码,通过编译器编译成字节指令后,存放在方法属性表集合,名为 Code 属性里。
属性表(attribute_info)在 Class 文件、字段表、方法表中均可以携带本身的属性表集合,用于描述某些场景专有的信息。
格式结构
attribute_info { u2 attribute_name_index; u4 attribute_length; u1 info[attribute_length]; }
虚拟机预约义属性
属性 | 位置 | 含义 | class 版本 |
---|---|---|---|
SourceFile |
ClassFile |
记录源文件名称 | 45.3 |
InnerClasses |
ClassFile |
内部类列表 | 45.3 |
EnclosingMethod |
ClassFile |
仅当一个类为局部类或匿名类时才能拥有这个属性,这个属性用于标识这个类所在的外围方法 | 49.0 |
SourceDebugExtension |
ClassFile |
JDK 1.6 中新增的属性,SourccDcbugExtcnsion 属性用于在储额外的调试信息。譬如在进行 JSP 文件调试时,没法经过 Java 堆栈来定位到 JSP 文件的行号, JSR-45 规范为这些非 Java 语言编写,却须要编译成字节码并运行在 Java 虚拟机中的程序提供了一个进行调试的标准机制,使用 SourccDcbugExtcnsion 属性就能够用于存储这个标准所新加入的调试信息 | 49.0 |
BootstrapMethods |
ClassFile |
JDK1.7 新增的属性,用于保存 invokedynamic 指令引用的引导方法限定符 | 51.0 |
ConstantValue |
field_info |
final 关键字定义的常量值 | 45.3 |
Code |
method_info |
Java 代码编译成的字节码指令 | 45.3 |
Exceptions |
method_info |
方法抛出的异常 | 45.3 |
RuntimeVisibleParameterAnnotations , RuntimeInvisibleParameterAnnotations |
method_info |
JDK5 中新增的属性,做用于方法参数 RuntimeVisibleParameterAnnotations属性指明哪些注解是运行时可见; RuntimeInvisibleAnnotations`属性指明哪里注解是运行时不可见的 |
49.0 |
AnnotationDefault |
method_info |
JDK1.5 中新增的属性,用于记录注解类元素的默认值 | 49.0 |
MethodParameters |
method_info |
MethodParameters 属性记录方法的形式参数的信息,好比方法名称。 | 52.0 |
Synthetic |
ClassFile , field_info , method_info |
标识方法或字段为编译器自动生成的 | 45.3 |
Deprecated |
ClassFile , field_info , method_info |
被声明为 Deprecated 的方法和字段 | 45.3 |
Signature |
ClassFile , field_info , method_info |
记录类,接口,构造函数,方法或字段的签名 | 49.0 |
RuntimeVisibleAnnotations , RuntimeInvisibleAnnotations |
ClassFile , field_info , method_info |
JDK5 中新增的属性,为动态注解提供支持。RuntimeVisibleAnnotations 属性指明哪些注解是运行时可见;RuntimeInvisibleAnnotations 属性指明哪里注解是运行时不可见的 |
49.0 |
LineNumberTable |
Code |
LineNumberTable 属性表存放方法的行号信息 | 45.3 |
LocalVariableTable |
Code |
LocalVariableTable 属性表中存放方法的局部变量信息 | 45.3 |
LocalVariableTypeTable |
Code |
JDK 1.5 中新增的屈件,它使用特征签名代替描述符,是为了引入泛型语法以后能描述泛型参数化类型而添加 | 49.0 |
StackMapTable |
Code |
JDKL6 中新增的属性.供新的类型检查验证器 (Type Checker)检查和处理目标方法的后部变量和操做数栈所须要的类型是否匹配 | 50.0 |
RuntimeVisibleTypeAnnotations , RuntimeInvisibleTypeAnnotations |
ClassFile , field_info , method_info , Code |
jdk8 新增属性RuntimeVisibleTypeAnnotations :运行时可见类型注解RuntimeInvisibleTypeAnnotations :运行时不可见类型注解 |
52.0 |
Code 属性
Java 程序方法体中的代码通过 Javac 编译器处理后,最终成为字节码指令存储在 Code 属性内。注意并非全部方法表都存在 Code 属性,例如,接口和抽象类中的方法就不存在 Code 属性。
Code 属性格式定义
Code_attribute { u2 attribute_name_index; //指向常量CONSTANT_UTF8_info的索引,常量固定值为Code u4 attribute_length; u2 max_stack; //操做数栈 u2 max_locals; //局部变量表所需的存储空间 //字节码长度,最大值可达2^32-1, 可是虚拟机限制了一个方法不容许超过65535条字节码指令 //即便用了u2 的长度,超出这个限制会致使编译失败 u4 code_length; u1 code[code_length]; //字节码指令的子节流 u2 exception_table_length; { u2 start_pc; u2 end_pc; u2 handler_pc; u2 catch_type; } exception_table[exception_table_length]; u2 attributes_count; attribute_info attributes[attributes_count]; }
加载和存储指令用于将数据在栈帧中的局部变量表与操做数栈之间传输。
将局部变量加载到操做数栈
// i 表明对int 操做 // l 表明对long 操做 // f 表明对float 操做 // d 表明对double 操做 // a 表明对引用reference 操做 // iload_<n> 表明一组指令,iload_0、iload_一、iload_二、iload_3等指令 iload iload_<n> lload lload_<n> fload fload_<n> dload dload_<n> aload aload_<n>
将数值从操做数栈存储到局部变量表
istore istore_<n> lstore lstore_<n> fstore fstore_<n> dstore dstore_<n> astore astore_<n>
将常量加载到操做数栈
bipush sipush ldc ldc_w ldc2_w aconst_null iconst_ml iconst_<i> lconst_<l> fconst_<f> dconst_<d>
扩充局部变量表的访问索引的指令:wide
加法指令
iadd、ladd、fadd、dadd
减法指令
isub、 lsub、 fsub、 dsub
乘法指令
imul、 lmul、 fmul、 dmul
除法指令
idiv、 ldiv、 fdiv、 ddiv
求余指令
irem、 lrem、 frem、 drem
取反指令
ineg、 lneg、 fneg、 dneg
位移指令
ishl、 isbr、 iusbr、 lsbl、 lshr、 lushr
按位或指令
ior、 lor
按位与指令
iand、 land
按位异或指令
ixor、 lxor
局部变量自增指令
iinc
比较指令
dcmpg、 dcmpl、 fcmpg、 fcmpl、 lcmp
double a = 1; double b = a / 0; //不会报错,结果Infinity double a = 0.0; double b = a / 0.0; //不会报错,结果NaN
类型转换指令能够将两种不一样的数值类型进行互相转换,通常用于用户代码中的显示类型转换操做,隐式类型转换不一样转换指令,虚拟机直接支持。
显示类型转换指令
i2b int 转换byte i2c int 转换char i2s int 转换short l2i long 转换 int f2i float 转换 int f2l float 转换 long d2i double 转换 int d2l double 转换 long d2f double 转换 float
转换规则
double nan = 0.0 / 0.0; int a = (int) nan; System.out.println(a); //0 float b = (float) nan; System.out.println(b); //NaN
建立类实例指令
new
建立数组指令
newarray anewarray multianewarray
访问类字段 和 实例字段
getfield putfield getstatic putstatic
加载数组元素到操做数栈
baload //byte数组 caload //char数组 saload //short数组 iaload //int数组 laload //long 数组 faload //float 数组 daload //double 数组 aaload //对象数组
将操做数栈存储到数组元素中
bastore castore sastore iastore lastore fastore dastore aastore
获取数组长度
arraylength
检查类实例类型的指令
instanceof checkcast
出栈指令
pop pop2 //出栈2个元素
复制栈顶一个或者两个数值并复制或双份的复制值从新压入栈顶
dup dup2 dup_x1 dup2_x1 dup_x2 dup2_x2
将栈最顶端的两个数值互换
swap
条件分支
ifeq iflt ifle ifne ifge ifnull ifnonull if_icmpeq 比较栈顶两个int类型数值的大小 ,当前者 等于 后者时,跳转 if_icmpne if_icmplt if_icmpgt if_icmple if_icmpge if_acmpeq if_acmpne
复合条件分支
tableswitch switch 条件跳转 case值连续 lookupswitch witch 条件跳转 case值不连续
无条件分支
goto 无条件跳转 goto_w 无条件跳转 宽索引 jsr SE6以前 finally字句使用 跳转到指定16位的offset,并将jsr下一条指令地址压入栈顶 jsr_w SE6以前 同上 宽索引 ret SE6以前返回由指定的局部变量所给出的指令地址(通常配合jsr jsr_w使用) w同局部变量的宽索引含义
方法调用指令
invokevirtual: 调用对象实例方法 invokeinterface 调用接口方法 invokespecial 调用一些须要特需处理的实例方法,包括实例初始化方法、私有方法、父类方法 invokestatic 调用类方法 invokedynamic 在运行时动态解析出调用点限定符所引用的方法,并执行
返回指令
ireturn lreturn freturn dreturn areturn return 声明为void 的方法
athrow 显示抛出异常
Java 虚拟机能够支持方法级别的同步和方法内部一段指令序列的同步,这两种同步结构都使用管理(Monitor)来支持。
方法级别的同步是由方法表结构中 ACC_SYNCHRONIZED 访问标识来处理
方法内部一段指令序列的同步
monitorenter 获取锁,进入代码块 monitorexit 释放锁,必须与monitorenter成对出现
源码
public class SynchronizedInstruction { private Object lock=new Object(); void onlyMe(Object lock){ synchronized (lock){ //doSomething } } }
反汇编
Compiled from "SynchronizedInstruction.java" public class cn.hdj.jvm.bytecode.SynchronizedInstruction { private java.lang.Object lock; public cn.hdj.jvm.bytecode.SynchronizedInstruction(); void onlyMe(java.lang.Object); } Classfile /home/hdj/IDEA/Java-Learning/src/main/java/cn/hdj/jvm/bytecode/SynchronizedInstruction.class Last modified 2021-3-20; size 488 bytes MD5 checksum 1f6db0fa955b6d719018d2ea50e1e910 Compiled from "SynchronizedInstruction.java" public class cn.hdj.jvm.bytecode.SynchronizedInstruction SourceFile: "SynchronizedInstruction.java" minor version: 0 major version: 51 flags: ACC_PUBLIC, ACC_SUPER Constant pool: #1 = Methodref #2.#19 // java/lang/Object."<init>":()V #2 = Class #20 // java/lang/Object #3 = Fieldref #4.#21 // cn/hdj/jvm/bytecode/SynchronizedInstruction.lock:Ljava/lang/Object; #4 = Class #22 // cn/hdj/jvm/bytecode/SynchronizedInstruction #5 = Utf8 lock #6 = Utf8 Ljava/lang/Object; #7 = Utf8 <init> #8 = Utf8 ()V #9 = Utf8 Code #10 = Utf8 LineNumberTable #11 = Utf8 onlyMe #12 = Utf8 (Ljava/lang/Object;)V #13 = Utf8 StackMapTable #14 = Class #22 // cn/hdj/jvm/bytecode/SynchronizedInstruction #15 = Class #20 // java/lang/Object #16 = Class #23 // java/lang/Throwable #17 = Utf8 SourceFile #18 = Utf8 SynchronizedInstruction.java #19 = NameAndType #7:#8 // "<init>":()V #20 = Utf8 java/lang/Object #21 = NameAndType #5:#6 // lock:Ljava/lang/Object; #22 = Utf8 cn/hdj/jvm/bytecode/SynchronizedInstruction #23 = Utf8 java/lang/Throwable { private java.lang.Object lock; flags: ACC_PRIVATE public cn.hdj.jvm.bytecode.SynchronizedInstruction(); flags: ACC_PUBLIC Code: stack=3, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: aload_0 5: new #2 // class java/lang/Object 8: dup 9: invokespecial #1 // Method java/lang/Object."<init>":()V 12: putfield #3 // Field lock:Ljava/lang/Object; 15: return LineNumberTable: line 8: 0 line 9: 4 void onlyMe(java.lang.Object); flags: Code: stack=2, locals=4, args_size=2 0: aload_1 //将lock对象入栈 1: dup //复制栈顶元素 2: astore_2 //将栈顶元素存储到局部变量表Slot2中 3: monitorenter //以lock对象为锁,开始同步 4: aload_2 //将局部变量表Slot2中元素入栈 5: monitorexit //退出同步 6: goto 14 //程序正常结束,跳转到14返回 9: astore_3 //从这步开始是异常路径,开下面的Exception table 10: aload_2 //将局部变量表Slot2中元素入栈 11: monitorexit //退出同步 12: aload_3 //将局部变量表Slot3中元素(异常对象)入栈 13: athrow //把异常对象从新抛出个onlyMe方法调用者 14: return //方法返回 Exception table: from to target type 4 6 9 any 9 12 9 any LineNumberTable: line 11: 0 line 13: 4 line 14: 14 StackMapTable: number_of_entries = 2 frame_type = 255 /* full_frame */ offset_delta = 9 locals = [ class cn/hdj/jvm/bytecode/SynchronizedInstruction, class java/lang/Object, class java/lang/Object ] stack = [ class java/lang/Throwable ] frame_type = 250 /* chop */ offset_delta = 4 }
public class DemoDynamic { public static void foo() { int a = 1; int b = 2; int c = (a + b) * 5; } }
javap 命令(也可使用 IDEA 查看字节码工具:jclasslib)
javac -g -encoding utf-8 DemoDynamic.java javap -verbose -c .\DemoDynamic.class > .\DemoDynamic.javap
字节文件
Classfile /D:/IDEA/Java-Learning/src/main/java/cn/hdj/jvm/bytecode/DemoDynamic.class Last modified 2020-10-17; size 419 bytes MD5 checksum 0242e2d86e94eb62d302f5a034336416 Compiled from "DemoDynamic.java" public class cn.hdj.jvm.bytecode.DemoDynamic minor version: 0 //版本号 major version: 52 flags: ACC_PUBLIC, ACC_SUPER //访问标识符 Constant pool: //常量池 #1 = Methodref #3.#18 // java/lang/Object."<init>":()V #2 = Class #19 // cn/hdj/jvm/bytecode/DemoDynamic #3 = Class #20 // java/lang/Object #4 = Utf8 <init> #5 = Utf8 ()V #6 = Utf8 Code #7 = Utf8 LineNumberTable #8 = Utf8 LocalVariableTable #9 = Utf8 this #10 = Utf8 Lcn/hdj/jvm/bytecode/DemoDynamic; #11 = Utf8 foo #12 = Utf8 a #13 = Utf8 I #14 = Utf8 b #15 = Utf8 c #16 = Utf8 SourceFile #17 = Utf8 DemoDynamic.java #18 = NameAndType #4:#5 // "<init>":()V #19 = Utf8 cn/hdj/jvm/bytecode/DemoDynamic #20 = Utf8 java/lang/Object { public cn.hdj.jvm.bytecode.DemoDynamic(); //默认的构造方法 descriptor: ()V flags: ACC_PUBLIC Code: //栈容量1 , 局部变量表容量1, 参数个数1(由于每一个实例方法都会有一个隐藏参数this) stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return LineNumberTable: line 6: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this Lcn/hdj/jvm/bytecode/DemoDynamic; public static void foo(); //foo() 方法 descriptor: ()V flags: ACC_PUBLIC, ACC_STATIC //标识符,public static Code: //方法表中Code 属性 stack=2, locals=3, args_size=0 //栈容量2 , 局部变量表容量3, 参数个数0 0: iconst_1 // 将常量值1入栈-> 栈1=1 1: istore_0 // 将栈顶元素存储到局部变量表Slot1位置 -> 局部0=1 2: iconst_2 // 将常量值2入栈 -> 栈1=2 3: istore_1 // 将栈顶元素存储到局部变量表Slot2位置 -> 局部1=2 4: iload_0 // 将局部变量表Slot1中元素入栈 5: iload_1 // 将局部变量表Slot2中元素入栈 6: iadd // 执行相加操做, 1+2 = 3, 入栈 7: iconst_5 // 将常量值5入栈 8: imul // 执行相乘操做,3*5=15,入栈 9: istore_2 // 将栈顶元素存储到局部变量表Slot2位置-> 局部2=15 10: return //返回 LineNumberTable: //行数表 line 9: 0 line 10: 2 line 11: 4 line 12: 10 LocalVariableTable: //局部变量表 Start Length Slot Name Signature 2 9 0 a I 4 7 1 b I 10 1 2 c I } SourceFile: "DemoDynamic.java"
具体详情看 字节码加强技术探索,这里只简单列出相关工具及使用场景。
对于须要手动操纵字节码的需求,可使用 ASM,它能够直接生产 .class 字节码文件,也能够在类被加载入 JVM 以前动态修改类行为
利用 Javassist 实现字节码加强时,能够无须关注字节码刻板的结构,其优势就在于编程简单。直接使用 java 编码的形式,而不须要了解虚拟机指令,就能动态改变类的结构或者动态生成类。
instrument 是 JVM 提供的一个能够修改已加载类的类库,专门为 Java 语言编写的插桩服务提供支持。它须要依赖 JVMTI 的 Attach API 机制实现。注意:ASM 和 Javassist 操做字节码库只能在类加载前对类进行强化。
AOP 面向切面编程
热部署:不部署服务而对线上服务作修改,能够作打点、增长日志等操做。
Mock:测试时候对某些服务作 Mock。
性能诊断工具:好比 bTrace 就是利用 Instrument,实现无侵入地跟踪一个正在运行的 JVM,监控到类和方法级别的状态信息。