类文件详解
类文件介绍
Class 文件是一组以 8 位字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑地排列在 Class 文件之中,中间没有添加任何 分隔符,这使得整个 Class 文件中存储的内容几乎全部是程序运行的必要数据,没有空隙存在。 当遇到需要占用 8 位字节以上空间的数据项时,则会按照高位在前(Big-Endian)的方式分割成若干个 8 位字节进行存储。 Class 文件只有两种数据类型:无符号数和表
类文件结构
魔数 Class文件版本 常量池 访问标志 类索引,父类索引,接口索引集合 字段表集合 方法表集合 属性表集合
ClassFile { u4 magic; // 魔法数字,表明当前文件是.class文件,固定0xCAFEBABE u2 minor_version; // 分别为Class文件的副版本和主版本 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]; // 各种属性 }
常量池
CONSTANT_Methodref_info
用于记录方法的信息 CONSTANT_Methodref_info { u1 tag; u2 class_index; u2 name_and_type_index; }
类索引,父类索引
字段表集合 字段表用于描述接口或者类中声明的变量
字节码介绍
字节码与数据类型
• 在虚拟机的指令集中,大多数的指令包含了其操作所对应的数据类型信息 • iLoad:从局部变量表中加载int型数据到操作数栈 • 大多数指令包含类型信息 • 类型多,指令少
加载与存储指令
• 加载和存储指令用于将数据在栈帧中的局部变量表和操作数栈直接来回传输 • 将局部变量表加载到操作数栈: iload lload fload dload aload • 将一个数值从操作数栈存储到局部变量表:istore :lfda • 将一个常量加载到操作数栈:bipush sipush ldc ldc_w ldc2_w aconst_null iconst_m1 iconst • 扩充局部变量表的访问索引的指令:wide
代码语言:javascript复制package jvm;
public class Test1 {
public Test1() {}
public static void main(String[] args) {
int a = 2;
int b =400;
int c = a b;
System.out.println(c);
}
// public static void main(java.lang.String[]);
// descriptor: ([Ljava/lang/String;)V
// flags: ACC_PUBLIC, ACC_STATIC
// Code:
// 操作数栈的深度为2
// stack=2, locals=4, args_size=1
// 0: iconst_2 #常量2压栈
// 1: istore_1 #出栈保存到本地变量1里面
// 2: sipush 400
// 5: istore_2 #出栈保存到本地变量2里面
// 6: iload_1 #局部变量1压栈
// 7: iload_2 #局部变量2压栈
// 8: iadd #栈顶两个元素相加 计算结果压栈
// 9: istore_3 #出栈保存到局部变量3中
// 10: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
// 13: iload_3
// 14: invokevirtual #3 // Method java/io/PrintStream.println:(I)V
// 17: return
// LineNumberTable:
// line 8: 0
// line 9: 2
// line 10: 6
// line 11: 10
// line 12: 17
// LocalVariableTable:
// Start Length Slot Name Signature
// 0 18 0 args [Ljava/lang/String;
// 2 16 1 a I
// 6 12 2 b I
// 10 8 3 c I
}
运算指令
• 运算或算术指令用于对两个操作数栈上的值进行某种特定的运算,并把结果存储到操作数栈顶
类型转换指令
• 类型转换指令可以将两种不同的数值类型进行相互转换,这些转换操作一般用于实现用户代码中的显示类型转换操作以 及用来处理字节码指令集中数据类型相关指令无法与数据类型一一对应的问题 • 宽化类型处理和窄化类型处理 • L2b i2c i2s l2i
代码语言:javascript复制package jvm;
public class Demo01 {
public static void main(String[] args) {
int hour = 24;
long mi = hour* 60*60*1000;
long mic = hour * 60*60*1000*1000;
int i =0;
long l =1;
i = (int) l;
System.out.println(mic/mi);
}
// public static void main(java.lang.String[]);
// descriptor: ([Ljava/lang/String;)V
// flags: ACC_PUBLIC, ACC_STATIC
// Code:
// stack=5, locals=9, args_size=1
// 0: bipush 24
// 2: istore_1
// 3: iload_1
// 4: bipush 60
// 6: imul
// 7: bipush 60
// 9: imul
// 10: sipush 1000
// 13: imul
// 14: i2l
// 15: lstore_2
// 16: iload_1
// 17: bipush 60
// 19: imul
// 20: bipush 60
// 22: imul
// 23: sipush 1000
// 26: imul
// 27: sipush 1000
// 30: imul
// 31: i2l
// 32: lstore 4
// 34: iconst_0
// 35: istore 6
// 37: lconst_1
// 38: lstore 7
// 40: lload 7
// 42: l2i
// 43: istore 6
// 45: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
// 48: lload 4
// 50: lload_2
// 51: ldiv
// 52: invokevirtual #3 // Method java/io/PrintStream.println:(J)V
// 55: return
// LineNumberTable:
// line 6: 0
// line 7: 3
// line 8: 16
// line 9: 34
// line 10: 37
// line 11: 40
// line 12: 45
// line 13: 55
// LocalVariableTable:
// Start Length Slot Name Signature
// 0 56 0 args [Ljava/lang/String;
// 3 53 1 hour I
// 16 40 2 mi J
// 34 22 4 mic J
// 37 19 6 i I
// 40 16 7 l J
}
对象创建与访问指令
• 创建类实例的指令:new • 创建数组的指令:newarray anewarray multianewarray • 访问类字段:getfield putfield getstatic putstatic • 把数组元素加载到操作数栈的指令:aload • 将操作数栈的值存储到数组元素:astore • 取数组长度的指令:arraylength • 检查实例类型的指令:instanceof checkcast
代码语言:javascript复制package jvm;
public class Demo2 {
public static void main(String[] args) {
User user = new User();
user.name = "h1";
String name = user.name;
User[] users = new User[10];
int[] ints = new int[10];
}
// public static void main(java.lang.String[]);
// descriptor: ([Ljava/lang/String;)V
// flags: ACC_PUBLIC, ACC_STATIC
// Code:
// stack=2, locals=5, args_size=1
// 0: new #2 // class jvm/User
// 3: dup
// 4: invokespecial #3 // Method jvm/User."<init>":()V 初始化方法
// 7: astore_1
// 8: aload_1
// 9: pop
// 10: ldc #4 // String h1 常量压栈
// 12: putstatic #5 // Field jvm/User.name:Ljava/lang/String;
// 15: aload_1
// 16: pop
// 17: getstatic #5 // Field jvm/User.name:Ljava/lang/String;
// 20: astore_2
// 21: bipush 10
// 23: anewarray #2 // class jvm/User 引用类型的数组
// 26: astore_3
// 27: bipush 10
// 29: newarray int //基本类型的数组
// 31: astore 4
// 33: return
// LineNumberTable:
// line 6: 0
// line 7: 8
// line 8: 15
// line 10: 21
// line 11: 27
// line 12: 33
// LocalVariableTable:
// Start Length Slot Name Signature
// 0 34 0 args [Ljava/lang/String;
// 8 26 1 user Ljvm/User;
// 21 13 2 name Ljava/lang/String;
// 27 7 3 users [Ljvm/User;
// 33 1 4 ints [I
}
操作数栈管理指令
• 操作数栈指令用于直接操作操作数栈 • 操作数栈的一个或两个元素出栈:pop pop2 • 复制栈顶一个或两个数值并将复制或双份复制值重新压入栈顶:dup dup2 dup_x1 dup_x2 • 将栈顶的两个数值替换:swap
控制转移指令
• 控制转移指令可以让java虚拟机有条件或无条件的从指定的位置指令而不是控制转移指令的下一条指令继续执行程序。可以认为控制转移指令就是在修改pc寄存器的值 • 条件分支:ifeq iflt ifle ifne ifgt ifnull ifcmple • 复合条件分支:tableswitch lookupswitch • 无条件分支:goto goto_w jsr jsr_w ret
方法调用指令
• Invokevirtual指令用于调用对象的实例方法,根据对象的实际类型进行分派 • Invokinterface指令用于调用接口方法,它会在运行时搜索一个实现了这个接口的对象,找出适合的方法进行调用 • Invokespecial指令用于调用一些需要特殊处理的实例方法,包括实例初始化方法、私有方法和父类方法 • Invokestatic指令用于调用类方法
代码语言:javascript复制package jvm;
public class Demo7 {
public static void main(String[] args) {
int count =10;
int i = 8;
if ( i>count) {
System.out.println("i>count");
}else {
System.out.println("i<count");
}
}
// public static void main(java.lang.String[]);
// descriptor: ([Ljava/lang/String;)V
// flags: ACC_PUBLIC, ACC_STATIC
// Code:
// stack=2, locals=3, args_size=1
// 0: bipush 10
// 2: istore_1
// 3: bipush 8
// 5: istore_2
// 6: iinc 2, 1
// 9: iload_2
// 10: iload_1
// 11: if_icmple 25
// 14: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
// 17: ldc #3 // String i>count
// 19: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
// 22: goto 33
// 25: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
// 28: ldc #5 // String i<count
// 30: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
// 33: return
// LineNumberTable:
// line 5: 0
// line 6: 3
// line 7: 6
// line 8: 14
// line 10: 25
// line 12: 33
// LocalVariableTable:
// Start Length Slot Name Signature
// 0 34 0 args [Ljava/lang/String;
// 3 31 1 count I
// 6 28 2 i I
// StackMapTable: number_of_entries = 2
// frame_type = 253 /* append */
// offset_delta = 25
// locals = [ int, int ]
// frame_type = 7 /* same */
}
方法返回指令
• 方法返回指令是根据返回值的类型区分的,包括 ireturn(当返回值是boolean、byte、char、short和int类型时使用)、lreturn、freturn、dreturn和areturn,另外还有一条return指令供声明为void的方法、实例初始化方法以及类和接口的类初始化方法使用。
异常处理指令
• 在Java程序中显示抛出异常的操作(throw 语句)都由athrow指令来实现,除了用throw语句显式抛出异常情况之外,Java 虚拟机规范还规定了许多运行时异常会在其他Java虚拟机指令检测到异常状况时自动抛出。例如,在整数运算中,当除数 为零时,虚拟机会在idiv或ldiv指令中抛出ArithmeticException异常。而在Java虚拟机中,处理异常(catch语句)不是由字节 码指令来实现的(很久之前曾经使用jsr和ret指令来实现,现在已经不用了),而是采用异常表来完成的。
同步指令
同步一段指令集序列通常是由Java语言中的synchronized语句块来表示的,Java虚拟机的指令集中有monitorenter和monitorexit两条指令来支持synchronized关键字的语义
代码语言:javascript复制package jvm;
public class Demo8 {
public static void main(String[] args) {
}
public synchronized void f1() {
}
public void f2() {
synchronized (this) {}
}
// Code:
// stack=2, locals=3, args_size=1
// 0: aload_0
// 1: dup
// 2: astore_1
// 3: monitorenter
// 4: aload_1
// 5: monitorexit
// 6: goto 14
// 9: astore_2
// 10: aload_1
// 11: monitorexit #锁的都是当前对象 所有只加锁一次 可重入锁
// 12: aload_2
// 13: athrow
// 14: return
// Exception table:
// from to target type
// 4 6 9 any
// 9 12 9 any
// LineNumberTable:
// line 14: 0
// line 15: 14
// LocalVariableTable:
// Start Length Slot Name Signature
// 0 15 0 this Ljvm/Demo8;
// StackMapTable: number_of_entries = 2
// frame_type = 255 /* full_frame */
// offset_delta = 9
// locals = [ class jvm/Demo8, class java/lang/Object ]
// stack = [ class java/lang/Throwable ]
// frame_type = 250 /* chop */
// offset_delta = 4
}