一个java程序是怎样运行起来的(3)

接上一篇 一个java程序是怎样运行起来的(2),在jvm创建好后,就可以开始执行程序了。我们知道,程序执行的入口在main函数,所以我们首先得找到main函数,这得有个前提,main函数对应的类已经被jvm加载了,所以jvm做的第一件事就是去加载类。先来看下java类加载的机制,主要有以下几个阶段:

1,加载:

加载阶段可以参考java.lang.ClassLoader中loadClass方法,采用的是双亲委托进制进行加载,这个阶段首先找到对应的class文件,以二级制方式读入内存,按照jvm规范解析出所表达的数据结构,在内存中生成一个代表该类的java.lang.Class对象.

2,验证:

验证是确保当前class文件格式符合jvm规范,不会对jvm产生危害。验证工作并不是在加载之后才开始的,比如从class文件读入到内存后,解析其代表的数据结构时,我们首先会去校验魔数是否正确,以及版本号是否符合要求等

3,准备

准备阶段主要是为类的静态变量分配内存,设定初始值等工作

4,解析

常量池中的符号引用替换为直接引用,比如String str = "test",str指向常量池中"test"的地址

5,初始化

这个过程主要是执行类构造器的方法,静态类的赋值,静态代码块的执行。如果初始化一个类时,发现父类还没有初始化,则需要先初始化父类

根据一个java程序是怎样运行起来的(1),类加载完成后,就可以找到main方法了,这时就可以开始执行main方法中的jvm指令了,下面以一个例子来解释其执行过程。

测试代码如下:

public class TestAdd{
	public static void main(String[] args){
		int a = 1;
		int b = 2;
		int c = a+b;
		print(c);
	}
	
	public static void print(int c){
		System.out.println(c);
	}
}
javac编译后,利用命令javap -c TestAdd,我们来看下在运行时究竟执行了哪些jvm指令

public class TestAdd {
  public TestAdd();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return


  public static void main(java.lang.String[]);
    Code:
       0: iconst_1
       1: istore_1
       2: iconst_2
       3: istore_2
       4: iload_1
       5: iload_2
       6: iadd
       7: istore_3
       8: iload_3
       9: invokestatic  #2                  // Method print:(I)V
      12: return


  public static void print(int);
    Code:
       0: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: iload_0
       4: invokevirtual #4                  // Method java/io/PrintStream.println:(I)V
       7: return
}
构造函数先不看,直接看main方法。函数的执行是在栈帧中执行的,执行的时候由程序计数器记录当前执行到哪个位置。栈帧存放在stack中,只有stack顶的栈帧当前有效,里面存放了本地变量表,操作数栈,返回地址等,本地变量表的大小,以及从操作数栈的深度在编译时就已经确定,运行时不会改变,如下图:


他们之间的调用关系是栈帧1的函数调用了栈帧2中的函数,栈帧2中国的函数调用了栈帧3中的函数。有了这个基础,接下来看下上面的指令是如何执行的,入口在main方法,此时分配新的栈帧我把它标记为栈帧1,栈帧1处在stack顶,即为当前栈帧,执行main方法中jvm指令之前,栈帧1中的有关数据结构如下,本地变量表index为0变量存放的是函数的参数args:


执行指令iconst_1,将int类型数字1push到操作数栈,此时栈帧1的数据结构为:


执行指令istore_1,将栈帧1中操作数栈执行退栈操作,所得的值放入到本地变量表第1个变量中,此时栈帧1的数据结构:


iconst_2,istore_2与上面同理,执行后栈帧:


iload_1和iload_2分别把本地变量表中第一个和第二个变量的值压入到操作数栈,


iadd指令,连续两次操作数栈执行退栈操作,将所得的值相加得到结果3再次压入操作数栈

istore_3,将操作数栈栈顶元素退栈,所得的值存入第3个本地变量中


iload_3,将本地变量表中第三个变量的值压入操作数栈中

invokestaic 调用静态方法,此处存在方法调用,需要新开辟一个栈帧压入stack,并把变量值3入本地变量表,其返回地址为main方法中即将执行的指令return的地址,这个暂按照指令集的排列,标记为frame1_12吧,此时当前栈帧为栈帧2,运行时数据结构为:


接下来看下print方法的执行,

getstatic获取指定的field,

iload_0,将变量0的值3压入操作数栈,此时数据结构为:


invokevirtual执行打印方法,会新开辟一个栈帧栈帧3,将变量值3入栈帧3的本地变量表,其返回地址为frame2_7,执行return后,当前栈帧栈帧3退栈,栈帧2变成当前栈帧,发现当前执行的指令为return执行退栈操作,当前栈帧为栈帧1,此时栈帧1要执行的指令为return,退栈,至程序结束退出。

至此,把java程序的执行过程简单过了一遍,过程非常粗糙,我目前对jvm的了解有限,后续有更好更深入的理解后,再回过头来丰富下。

上面方法调用时的栈帧等数据结构可以参见本人编写的jvm尝试: https://github.com/reverence/czvm



  • 3
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值