文件结构
推荐官方文档:https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-3.html
ClassFile {
u4 magic;
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];
}
示例
编写一个helloworld类进行编译:
package com.cbry.classs;
public class ClassStruct {
public static void main(String[] args) {
System.out.println("Hello World");
}
}
安装HEX插件查看十六进制文件
magic:魔数
一个魔数对应一个类型的文件,java的class文件的魔数是:cafebabe ,0-3个字节(4个字节:u4)。其它的文件,比如说图片什么的。
00000000 ca fe ba be
00 00 00 34 00 22 07 00 02 01 00 1b
00000000 ca fe ba be 00 00 00 34 00 22 07 00 02 01 00 1b
version:版本信息
4-7个字节
minor_version:小版本
major_version:主版本: 00 34 == 52(jdk8)
00000000 ca fe ba be 00 00
00 34
00 22 07 00 02 01 00 1b
常量池
8-9个字节表示常量池长度
00000000 ca fe ba be 00 00 00 34 00 22
07 00 02 01 00 1b
十六进制22 = 十进制34
,表示常量池有#1 = #33项,注意**#0项不计入计算**,也没有值。该长度又称为常数计数器,常数计数器-1 = 常量池的项数;
常量池第一项
00000000 ca fe ba be 00 00 00 34 00 22 07 00 02 01 00
1b
07
表示一个方法信息,后面的 00 02
和01 00
表示方法所属类和方法名。
常量/方法初始化
常量赋值
当int取值**-1~5时,JVM采用iconst**指令将常量压入栈中。
当int取值**-15**采用iconst指令,取值**-128127采用bipush指令,取值-3276832767**采用sipush指令,取值**-21474836482147483647**采用 ldc 指令。
public class IfClasss {
public static void main(String[] args) {
int a = 0;
if (a == 9) {
a = 6;
}else {
a = 8;
}
}
}
可以看到先用指令iconst取出数 0 压入操作栈,再istore弹出栈里面的0
存放到局部变量表,iload在从局部变量表中取出0压入栈中。
bipush压入9进入操作栈,判断a9即是否09,如果等于,则将0原本所在的局部变量表的位置的数据改成6,然后goto跳转到line 17结束。反之则替换成8。
cinit指令
static int i = 0;
static{
i = 1;
}
static{
i = 2;
}
编译器会按从上至下的顺序,收集所有static静态代码块和静态成员赋值的代码,合并为一个特殊的方法<cinit>() V
。
putstatic
指令时给静态变量赋值。
init指令
public class InitClasss {
private int a = 0;
{
a = 1;
}
public InitClasss(int arg0) {
this.a = arg0;
}
public static void main(String[] args) {
InitClasss ic = new InitClasss(2);
System.out.println(ic.a);
}
}
输出结果是三,都是局部变量按顺序执行,new一个对象,先执行类里面的逻辑再执行构造函数。 a的值的变化是:0 -> 1-> 2 。
具体是执行main函数,然后new的时候构造类对象,然后进行a的值变化,传参。
main方法是一个特殊的方法,在程序开始运行时,系统会找到main方法所在的那个class文件,然后把main方法的代码装入内存,从main的第一条语句开始执行,直到main的最后一条语句结束。至于main所在的类不用管它,它在main装入内存时不起作用的,只有创建这个类的对象时才起作用,也就是使用new的时候。
aload_0把this装载到了操作数栈中aload_0是一组格式为aload_的操作码中的一个,这一组操作码把对象的引用装载到操作数栈中标志了待处理的局部变量表中的位置,但取值仅可为0、1、2或者3。
iload_、lload_、fload_和dload_,这里的i代表int型,l代表long型,f代表float型以及d代表double型。
iload_0、iload_1、aload_0之类的数字代表的时局部变量表中的Slot槽位中的数据。
0: aload_0 //相当于把this放到操作数栈 1: invokespecial #10 // Method java/lang/Object."<init>":()V //调用父类Object的构造方法 4: aload_0 //相当于把this放到操作数栈 5: iconst_0 //将常数0压入操作栈 6: putfield #13 // Field a:I //常数池中的13项:a属性:this.a,到这里就是将0赋值给了this.a 9: aload_0 10: iconst_1 11: putfield #13 // Field a:I , 即this.a = 1 14: aload_0 15: iload_1 //将slot_1中的传参3从局部变量表放入操作数 16: putfield #13 // Field a:I 19: return
方法
public class MethodClasss {
//空构造方法
public MethodClasss() {}
private void method1() {}
public final void method2() {}
public void method3() {}
private static void method4() {}
public static void main(String[] args) {
MethodClasss mc = new MethodClasss();
mc.method1();
mc.method2();
mc.method3();
mc.method4();
MethodClasss.method4();
}
}
可以看到private和static修饰的方法时是invokespecial和invokestatic方法(private final也是),而public修饰的则是invokevirtual方法。因为public的方法不能被唯一确认,需要运行时确定
。而其它的special方法和static则是静态绑定
,直接找到确定。
0: new #1 // class com/cbry/classs/MethodClasss 3: dup 4: invokespecial #20 // Method "<init>":()V 7: astore_1 8: aload_1 9: invokespecial #21 // Method method1:()V 12: aload_1 13: invokevirtual #23 // Method method2:()V 16: aload_1 17: invokevirtual #25 // Method method3:()V 20: invokestatic #27 // Method method4:()V 23: invokestatic #27 // Method method4:()V
这里的new
分两部分:1、分配对象内存;2、将对象的引用
压入操作栈;
dup则是复制一份对象的引用
在栈顶,这个复制的部分
是为了给构造函数操作,两个引用指向一个分配的对象内存空间。操作完(构造函数)操作后,复制的引用弹出,栈里面还有一个引用(他指向的对象已经被构造了
)。
然后就是引用不断的在局部变量表(astore)和操作栈(aload)中使用
—调用执行方法。其中static因为是静态方法不需要对象的调用,即不需要store和load操作。
多态原理