Java程序的运行机制
2020-08-09 15:34:29
一、Java程序的编译和执行
跟解释型语言相比,Java比较特殊,因为虽然Java编写的程序也需要经过编译的步骤,但它并不生成特定平台的机器码,而是生成与平台无关的字节码(.class)。这种字节码是不可执行的,必须使用Java解释器来解释执行。Java语言里负责解释执行字节码的是Java虚拟机(JVM),它是运行Java字节码的虚拟计算机。
其简单过程如下:
1. 编写java代码
public class HelloWorld{
public static void main(String args[]){
System.out.println("HelloWorld");
}
}
2. 编译Java程序
javac -d destDir srcFile
destDir:生成字节码(.class)文件存放的目标位置
srcFile:源代码文件
3. 运行Java程序
java HelloWorld
注意:这里的参数HelloWorld是类名,而不是文件名。
二、Java编译程序和运行过程详解
java整个编译以及运行的过程相当繁琐,我就举一个简单的例子说明:
编译原理简单过程:词法分析 --> 语法分析 --> 语义分析和中间代码生成 --> 优化 --> 目标代码生成
Java程序从源文件创建到程序运行要经过两大步骤:
1、Java文件会由编译器编译成class文件(字节码文件),会经过编译原理简单过程的前三步;
2、字节码由java虚拟机解释运行,解释执行即为目标代码生成并执行。因为java程序既要编译,同时也要经过JVM的解释执行,所以说Java被称为半解释语言!
第一步(编译):创建完源文件之后,程序先要被JVM中的java编译器进行编译为.class文件。
java编译一个类时,若这个类所依赖的类还没有被编译,编译器会自动的先编译这个所依赖的类,然后引用;若java编译器在指定的目录下找不到该类所依赖的类的 .class文件或者 .java源文件,就会报"Can't found sysbol"的异常错误。
public class Main {
public static void main(String[] args) {
Animal animal = new Animal("Tom");
animal.printName();
}
}
class Animal{
private String name;
public Animal(String name) {
super();
this.name = name;
}
public void printName(){
System.out.println("Animal = " + this.name);
}
}
编译后的字节码文件格式主要分为两部分:常量池和方法字节码。
常量池记录的是代码出现过的字面量(文本字符串、八种基本类型的值、被声明为final的常量等)以及符号引用(类和方法的全限定名、字段的名称和描述符、方法的名称和描述符);
方法字节码中放的是各个方法的字节码(依赖操作数栈和局部变量表,由JVM解释执行)
第二步(运行):java类运行的过程大概分为两个步骤:
(1)类的加载
加载 --> 验证 --> 准备 --> 解析 --> 初始化(其中验证、准备、解析统称为类的连接);
加载:通过一个类的全限定名来获取定义此类的二进制字节流(Class文件);将这个二进制字节流所代表的静态存储结果转化为方法区的运行时数据结构;在内存中生成一个java.lang.Class对象,注意:存放在方法区!
验证:验证目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全;使用纯粹的Java代码无法做到诸如访问数组边界意外的数据、将一个对象转型为它未实现的类型、跳转到不存在的代码之类的事情,如果这样做了,编译器将拒绝编译!
准备:准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在方法区中进行分配。首先这时候进行内存分配的仅包括类变量(static修饰的变量),而不是实例变量,实例变量将会在对象实例化时随着对象一起分配在Java堆中。
public static int value = 123;
变量value在准备阶段过后的初始值为0而不是123,因为这时候尚未开始执行任何Java方法,在类初始化的时候才会将value的值赋为123.
解析:解析阶段是虚拟机将class常量池内的符号引用替换为直接引用的过程。
符号引用:符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义的定位到目标即可;
直接引用:是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。有了直接引用,那引用的目标必定已经在内存中存在。
初始化:类初始化阶段是类加载过程的最后一步;在准备阶段,变量已经赋过一次系统要求的初始值,而在初始化阶段,则根据程序员通过程序制定的主观计划去初始化类变量和其他资源:初始化阶段是执行类构造器( )方法的过程。
( )方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static { }块)中的语句合并产生的,编译器收集的顺序是由语句在源文件中出现的顺序所决定的。
(2)类的执行
需要说明的一点的是:JVM主要在程序第一次运行时主动使用类的时候,才会立即去加载,加载完毕就会生成一个java.lang.Class对象,并且存放在方法区。换言之,JVM并不是在运行时就会把所有使用到的类都加载到内存中,而是用到,不得不加载的时候,才加载进来,而且只加载一次,初始化类构造器()方法也只执行一次,所以static{} 块,类变量赋值语句也就只执行一次,只生成一个java.lang.Class对象!
由Java虚拟机的执行引擎来解释执行Java字节码,过程:输入字节码文件,字节码解析,输出执行完的结果!