接触过java的童鞋,你是否也有这样的疑问我们的java程序是怎样执行的?
今天我们来揭开java程序执行流程的神秘面纱,首先来看一下程序执行的流程图
从流程图中我们可以大概知道程序的执行流程,首先我们写好java程序放在工作空间
然后通过输入 javac - className.java 把java文件编译成class文件,此时class文件还是保存在工作空间(编译器比如eclipse点击编译时会自动完成这个操作)。
当我们输入java className执行我们的程序时,JRE的类加载器从工作空间中读取class文件,载入到系统分配给JVM的内存区域–运行数据区(Runtime Data Areas). 然后执行引擎解释或者编译类文件,转化成特定CPU的机器码,CPU执行机器码,至此完成整个过程。
Java程序都运行在虚拟机上,下面我们详细的描述一下虚拟机的工作原理(即流程图中的红色方框里的流程)。先来介绍一下类加载器, 类加载器作用是将类的字节码文件(.class)中的二进制数据读入内存,将其放在运行时数据区的方法区内,然后在堆上创建java.lang.Class对象,封装类在方法区内的数据结构。类加载的最终产品是位于堆中的类对象,类对象封装了类在方法区内的数据结构,并且向JAVA程序提供了访问方法区内数据结构的接口。
类加载器的层次关系图。
扩展类加载器(Extension ClassLoader):该类加载器负责加载JDK安装目录下的\jre\lib\ext的类,或者由java.ext.dirs系统变量指定路径中的所有类库,开发者也可以直接使用扩展类加载器。
应用程序类加载器(AppClassLoader):负责加载用户类路径(Classpath)所指定的类,开发者可以直接使用该类加载器,如果应用程序中没有定义过自己的类加载器,该类加载器为默认的类加载器。
用户自定义类加载器(User ClassLoader):JVM自带的类加载器是从本地文件系统加载标准的java class文件,而自定义的类加载器可以做到在执行非置信代码之前,自动验证数字签名,动态地创建符合用户特定需要的定制化构建类,从特定的场所(数据库、网络中)取得java class。
通过上面的层次图发现类加载是一种委托模式,当我们的虚拟机加载一个类时,下层的加载会委托上一级的类加载器。上一级的加载器会检查命名空间中是有已经加载了这个类,如果加载了就直接使用这个类,如果没有加载则继续委托到上一级直到最顶级,如果检查到最顶级(Bootstrap加载器)都没有找到需要的类,那么则会从最顶级开始加载,依次向下加载直到找到这个类(这个检查过程就类似在公司出了问题,下级能解决就解决,不能解决就依次向上级汇报。加载过程类似与android中view的事件分发机制,依次往下传递)。对于某个特定的类加载器来说,一个Java类只能被载入一次,也就是说在Java虚拟机中,类的完整标识是(classLoader,package,className)。一个类可以被不同的类加载器加载。整个检查和加载过程如下所示
类加载好了以后虚拟机继续做如下操作:
类加载到运行时数据区,我们继续探讨一下运行时数据区。
运行时数据区:当运行一个JVM示例时,系统将分配给它一块内存区域(这块内存区域的大小可以设置的),这一内存区域由JVM自己来管理。从这一块内存中分出一块用来存储一些运行数据,例如创建的对象,传递给方法的参数,局部变量,返回值等等。分出来的这一块就称为运行数据区域。
运行时区域主要分为6大块:方法区、Java堆、虚拟机栈、本地方法栈、程序计数器,运行常量池。其中方法区和Java堆一样,是各个线程共享的内存区域,而虚拟机栈、本地方法栈、程序计数器是线程私有的内存区。运行常量池本应该属于方法区,但是由于其重要性,JVM规范将其独立出来说明。
程序计数器:它是一块较小的工作空间,他可以看作是当前线程所执行的字节码的行号指示器,字节码解释器工作时就是通过改变这个计数值来选取下一条需要执行的字节码指令,分支,循环,跳转,异常处理、线程恢复等功能都需要依赖这个计数器。每条线程都需要有一个独立的程序计数器,各条线程之间计数器互不影响,独立存储,如果线程正在执行的是一个java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是native方法,这个计数器值则为空,此内存区域是唯一一个没有任何内存溢出的区域
Java虚拟机栈:与程序计数器一样,Java虚拟机栈也是线程私有的,他的生命周期与线程相同,每个方法在执行的同时都会创建一个栈帧,用于存储局部变量,操作数,动态链接,方法出口等信息。每个方法被调用到执行完成就对应一个栈帧在虚拟机中从入栈道出栈。
本地方法栈:与虚拟机栈的作用类似。不同点在于虚拟机栈为执行Java方法服务,而本地方法则为虚拟机使用native方法服务。
Java堆:一般来说,Java堆是java虚拟机所管理的最大一块内存区域,在虚拟机启动时创建。此内存区域唯一的目的就是存放对象实例,几乎所有的对象都是在这里分配内存,Java虚拟机规范中是这样描述:所有的对象实例以及数组都要在堆上分配,但是随着逃逸分析技术的逐渐成熟,所有的对象实例以及数组都要在堆上分配也变成不是绝对的。
方法区:方法区和Java堆一样,是各个线程共享的 内存区域,他用于存储已被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据。
运行时常量池:方法区的一部分,Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池,用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。
继续探讨 执行引擎(Execution Engine)
执行引擎负责具体的代码调用及执行过程。就目前而言,所有的执行引擎的基本一致:
1. 输入:字节码文件
2. 处理:字节码解析
3. 输出:执行结果。
物理机的执行引擎是由硬件实现的,和物理机的执行过程不同的是虚拟机的执行引擎由于自己实现的,这部分后续在详细说明