JVM浅谈
-
Java构造对象浅谈
在日常编程中,我们不假思索地使用new去新建一个对象,但 Java是如何在底层进行对象的创建?如何在底层完成对象的创建并进行初始化?下面我们来探讨以下这个问题:
Animal animal=new Animal()
上行代码是对象实例化的过程,在程序中执行这行代码,将会经历以下过程:
在方法区寻找Animal类的信息
判断 Java编译后的字节码里面是否有这个Animal类,如果没有,则类加载器会将当前这个类的字节码文件加载到文件中
new Animal()
创建对象时,对象在堆(heap)里面被分配内存空间该被创建的对象在堆中的地址会被压入操作数栈
在当前线程栈的方法栈帧的局部变量区域申请内存空间给animal
从操作数帧中弹出顶部操作数(就是被创建对象在堆中的地址)赋值给animal,从而使animal 指向新建的Animal对象
-
JVM理解
我们将从下面这个简单的代码的反编译的.class文件,深度理解Java是如何在内存中被执行的
public class TestPerson { public int doSomething(){ int a= 1; int b = 2; int c = (a+b)*5; return c; } public static void main(String[] args) { TestPerson testPerson=new TestPerson(); int result = testPerson.doSomething(); System.out.println(result); } }
.上述代码的class反编译文件
我们对一些可以见名知意的名词不做过多解释,不清楚的大家可以在简书中搜索,此处主要还是加深大家对代码的理解
Compiled from "JVM_03.TestPerson.java" public class JVM_03.TestPerson { public JVM_03.TestPerson(); // 这是默认构造函数 Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return public int doSomething(); Code: 0: iconst_1 // 把常量1压入操作数栈 1: istore_1 // 从操作数栈中弹出刚刚入栈的常量1 // 并把它赋值给局部变量表中索引为1的变量 // 即赋值给a (对应代码:int a=1) 2: iconst_2 3: istore_2 // 同上 4: iload_1 // 将局部变量表中下标为1的int变量压入操作数栈 5: iload_2 // 同上 6: iadd // 在操作数栈中完成两数相加 7: iconst_5 // 将常量5压入操作数栈 8: imul //在操作数栈中完成乘法运算 9: istore_3 //弹出运算结果并且赋值给局部变量表中索引为3的变量 10: iload_3 11: ireturn // 返回int类型的值 (ireturn 语句中 i就表示int) public static void main(java.lang.String[]); Code: 0: new #2 // class JVM_03.TestPerson 3: dup 4: invokespecial #3 // Method "<init>":()V 7: astore_1 8: aload_1 9: invokevirtual #4 // Method doSomething:()I 12: istore_2 13: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream; 16: iload_2 17: invokevirtual #6 // Method java/io/PrintStream.println:(I)V 20: return }
下面我们再挖深一些,进一步揭开JVM的面纱:
我们先了解一下Java的运行时数据区(Runtime Date Area),Java程序被运行时,内存中运行时数据区会被分为五部分,见下图。
Java程序运行是按线程来操作的,在线程栈中运行多个线程。每一个线程都有一个自己的栈帧,而每一个栈帧又包含局部变量、操作数栈、动态链接、方法出口四部分。上述就是在main线程里面的栈帧进行的。这里还要讲解一下,函数出口记录的问题。
就上述Java代码而言,显然,程序需要从main函数进入doSomething函数,再返回main函数,为了不迷路,Java采用”标记“的方法记录出口,上述的.class文件中的#2等等就是”标记“。当
new
一个新的对象时上述代码调用了默认构造函数,此时就需要进入该构造函数,Java离开main函数留下”标记“,java进入函数时又留下”标记“,如此,进入不同函数,它就知道运行时如何返回main函数了。 -
图表