一个java程序是怎样运行起来的(3)
来源:互联网 发布:雷达回波数据 编辑:程序博客网 时间:2024/06/04 17:57
接上一篇 一个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
- 一个java程序是怎样运行起来的(3)
- 一个java程序是怎样运行起来的(1)
- 一个java程序是怎样运行起来的(2)
- 一个程序是怎样跑起来的(一)
- 程序是怎样跑起来的 简述
- 读《程序是怎样跑起来的》
- 程序是怎样跑起来的-读书笔记
- 读《程序是怎样跑起来的》
- 《程序是怎样跑起来的》读书笔记
- 【2】《程序是怎样跑起来的》
- 《程序是怎样跑起来的》 矢泽久雄
- 程序是怎样跑起来的-第07章 程序是在何种环境中运行的
- 程序是怎么运行起来的
- 电脑程序是如何运行起来的
- 程序是怎样运行的
- 读书笔记(二)--- 程序是怎样跑起来的
- 程序是怎样跑起来的-计算机组成原理概要
- 程序是怎样跑起来的 —— 预处理
- Angular 环境安装与配置
- 软件的国际化、Jstl国际化标签
- 一分钟了解“be 形容词1 and 形容词2 的用法”
- win10下MongoDB安装与配置
- lxml的另一种用法
- 一个java程序是怎样运行起来的(3)
- Linux初相识(一)
- php 声明变量
- HDU 5573-Binary Tree (构造)
- 简单封装template.js
- Yann LeCun:深度学习与人工智能的未来(附PPT与译文)
- 一分钟了解“matlab的switch-case语句”
- Android动画框架(一)----视图动画&帧动画
- LintCode C++代码旋转字符串