Java虚拟机-Java程序执行流程

一.概述

程序执行流程我把它划分为以下几个步骤:编辑源码、编译生成class文件、(加载class文件、运行class字节码文件),其中后两个步骤都是在jvm虚拟机上执行的。 


二.编辑

编辑源代码,在任何一个工具上编写源代码,可以是记事本,最后命名为Student.java。


/**
 创建日期:2018.1.13
 创建人:zzg
 功能概述:Test
*/
class Person {
	
	private String name;
	private int age;
	
	public Person (int age, String name) {
		this.name = name;
		this.age = age;
	}
	
	public void run () {
		
	}
}

interface IStudyable {
	public int study (int a, int b);
}

public class Student extends Person implements IStudyable{
	
	private static int count = 5;
	private String sid;
	
	static {
		count ++;
	}
	
	public Student (int age, String name, String sid) {
		super(age, name);
		this.sid = sid;
	}
	
	@Override
	public void run () {
		System.out.println("run...");
	}
	
	@Override
	public int study (int a, int b) {
		int c = 10;
		int d = 20;
		return a+b*c-d;
	}
	
	public static int getCount () {
		return count;
	}
	
	public static void main (String[] args) {
		Student zzg = new Student(24, "zzg", "057655");
		zzg.study(5,6);
		Student.getCount();
		zzg.run();
	}
	
}
三.编译

编译生成class字节码文件,在桌面上,按住shift,然后按下鼠标右键:

四.字节码文件

字节码文件,看似很微不足道的东西,却真正实现了java语言的跨平台。各种不同平台的虚拟机都统一使用这种相同的程序存储格式。更进一步说,jvm运行的是class字节码文件,只要是这种格式的文件就行,所以,实际上jvm并不像我之前想象地那样与java语言紧紧地捆绑在一起。如果非常熟悉字节码的格式要求,可以使用二进制编辑器自己写一个符合要求的字节码文件,然后交给jvm去运行;或者把其他语言编写的源码编译成字节码文件,交给jvm去运行,只要是合法的字节码文件,jvm都会正确地跑起来。所以,它还实现了跨语言……

通过jClassLib可以直接查看一个.class文件中的内容,也可以给JDK中的javap命令指定参数,来查看.class文件的相关信息:

javap –v Student

可以输出重定向下:javap –v Student > Student.class.txt


部分class文件内容,从上面图中,可以看到这些信息来自于Student.class,编译自Student.java,编译器的主版本号是52,也就是jdk1.8,这个类是public,然后是存放类中常量的常量池,各个方法的字节码等,这里就不一一记录了。总之,字节码文件很简单很强大,它存放了这个类的各种信息:字段、方法、父类、实现的接口等各种信息。

五.运行

Java虚拟机要运行字节码指令,就要先加载字节码文件,谁来加载,怎么加载,加载到哪里……谁来运行,怎么运行,同样也要考虑……

六.Java虚拟机的基本结构及其内存分区


JVM中把内存分为直接内存、方法区、Java栈、Java堆、本地方法栈、PC寄存器等。

       直接内存:就是原始的内存区

       方法区:用于存放类、接口的元数据信息,加载进来的字节码数据都存储在方法区

       Java栈:执行引擎运行字节码时的运行时内存区,采用栈帧的形式保存每个方法的调用运行数据

       本地方法栈:执行引擎调用本地方法时的运行时内存区

       Java堆:运行时数据区,各种对象一般都存储在堆上

       PC寄存器:功能如同CPU中的PC寄存器,指示要执行的字节码指令。

       JVM的功能模块主要包括类加载器、执行引擎和垃圾回收系统

3.类加载器加载Student.class到内存:

       1)类加载器会在指定的classpath中找到Student.class这个文件,然后读取字节流中的数据,将其存储在方法区中。
       2)会根据Student.class的信息建立一个Class对象,这个对象比较特殊,一般也存放在方法区中,用于作为运行时访问Student类的各种数据的接口。
       3)必要的验证工作,格式、语义等
       4)为Student中的静态字段分配内存空间,也是在方法区中,并进行零初始化,即数字类型初始化为0,boolean初始化为false,引用类型初始化为null等。
在Student.java中只有一个静态字段:
 private static int cnt=5; 
此时,并不会执行赋值为5的操作,而是将其初始化为0。
       5)由于已经加载到内存了,所以原来字节码文件中存放的部分方法、字段等的符号引用可以解析为其在内存中的直接引用了,而不一定非要等到真正运行时才进行解析。
       6)在编译阶段,编译器收集所有的静态字段的赋值语句及静态代码块,并按语句出现的顺序拼接出一个类初始化方法<clinit>()。此时,执行引擎会调用这个方法对静态字段进行代码中编写的初始化操作。
在Student.java中关于静态字段的赋值及静态代码块有两处:


可以通过jClassLib这个工具看到生成的<clinit>()方法的字节码指令或者命令行形式:


iconst_5指令把常数5入栈
putstatic #6将栈顶的5赋值给Student.cnt这个静态字段
getstatic #6 获取Student.cnt这个静态字段的值,并将其放入栈顶
iconst_1 把常数1入栈
iadd 取出栈顶的两个整数,相加,结果入栈
putstatic #6 取出栈顶的整数,赋值给Student.cnt
return 从当前方法中返回,没有任何返回值。
从字节码来看,确实先后执行了cnt =5 及 cnt++这两行代码。

在这里有一点要注意的是,这里笼统的描述了下类的加载及初始化过程,但是,实际中,有可能只进行了类加载,而没有进行初始化工作,原因就是在程序中并没有访问到该类的字段及方法等。
此外,实际加载过程也会相对来说比较复杂,一个类加载之前要加载它的父类及其实现的接口:加载的过程可以通过java –XX:+TraceClassLoading参数查看:
如:java -XX:+TraceClassLoading Student,信息太多,可以重定向下:


 可以看到最先加载的是Object.class这个类---所有类的父类。

 直到第390行才看到自己定义的部分被加载,先是Student实现的接口IStudyable,然后是其父类Person,然后才是Student自身,然后是一个启动类的加载,然后就是找到main()方法,执行了。 

七.执行引擎找到main()这个入口方法,执行其中的字节码指令:

要了解方法的运行,需要先稍微了解下java栈:JVM中通过java栈,保存方法调用运行的相关信息,每当调用一个方法,会根据该方法的在字节码中的信息为该方法创建栈帧,不同的方法,其栈帧的大小有所不同。栈帧中的内存空间还可以分为3块,分别存放不同的数据:

局部变量表:存放该方法调用者所传入的参数,及在该方法的方法体中创建的局部变量。

操作数栈:用于存放操作数及计算的中间结果等。

其他栈帧信息:如返回地址、当前方法的引用等。

只有当前正在运行的方法的栈帧位于栈顶,当前方法返回,则当前方法对应的栈帧出栈,当前方法的调用者的栈帧变为栈顶;当前方法的方法体中若是调用了其他方法,则为被调用的方法创建栈帧,并将其压入栈顶。注意:局部变量表及操作数栈的最大深度在编译期间就已经确定了,存储在该方法字节码的Code属性中。


查看Student.main()的运行过程--看下main()方法:


八.总结

一个类文件首先加载到方法区,一些符号引用被解析(静态解析)为直接引用或者等到运行时分派(动态绑定),经过一系列的加载过程(class文件的常量池被加载到方法区的运行时常量池,各种其它的静态存储结构被加载为方法区运行时数据解构等等)

然后程序通过Class对象来访问方法区里的各种类型数据,当加载完之后,程序发现了main方法,也就是程序入口,那么程序就在栈里创建了一个栈帧,逐行读取方法里的代码所转换为的指令,而这些指令大多已经被解析为直接引用了,那么程序通过持有这些直接引用使用指令去方法区中寻找变量对应的字面量来进行方法操作。

操作完成后方法返回给调用方,该栈帧出栈。内存空间被GC回收,堆里被new的那些也就被来及回收机制GC了。

全流程包括以下几步:源码编写–编译(javac编译和jit编译,java语法糖)—类文件被加载到虚拟机(类Class文件结构,虚拟机运行时内存分析,类加载机制)—-虚拟机执行二进制字节码(虚拟机字节码执行系统)—垃圾回收(JVM垃圾回收机制)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值