JVM解析:class结构和常量及方法初始化

文件结构

推荐官方文档: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");
}
}

JVM解析:class结构和常量及方法初始化_第1张图片

安装HEX插件查看十六进制文件

JVM解析:class结构和常量及方法初始化_第2张图片

magic:魔数

一个魔数对应一个类型的文件,java的class文件的魔数是:cafebabe ,0-3个字节(4个字节:u4)。其它的文件,比如说图片什么的。

00000000 ca fe ba be00 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 0201 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;
	}
}
}

JVM解析:class结构和常量及方法初始化_第3张图片

可以看到先用指令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静态代码块和静态成员赋值的代码,合并为一个特殊的方法() V

JVM解析:class结构和常量及方法初始化_第4张图片

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的时候。

JVM解析:class结构和常量及方法初始化_第5张图片

在这里插入图片描述

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."":()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();
	}
	
}

JVM解析:class结构和常量及方法初始化_第6张图片

可以看到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 "":()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则是复制一份对象的引用JVM解析:class结构和常量及方法初始化_第7张图片
在栈顶,这个复制的部分是为了给构造函数操作,两个引用指向一个分配的对象内存空间。操作完(构造函数)操作后,复制的引用弹出,栈里面还有一个引用(他指向的对象已经被构造了)。

然后就是引用不断的在局部变量表(astore)和操作栈(aload)中使用—调用执行方法。其中static因为是静态方法不需要对象的调用,即不需要store和load操作。

多态原理

JVM解析:class结构和常量及方法初始化_第8张图片

你可能感兴趣的:(JVM,jvm)