Java代码的执行机制

1 Java源码编译机制

java虚拟机中执行的是class文件,因此先熟悉JDK是如何将Java代码编译成class文件是很必要的。
图1是利用javac将java代码编译为class文件的步骤
编译步骤

1.1 分析和输入到符号表

分析过程包括词法分析与语法分析,词法分析的过程是将代码字符串转化为token序列,语法分析的过程是将token序列生成抽象语法树。
这里以一个例子来说明语法树,语法树是源代码语法结构的一种树形表示方式,如下所示代码:

public class Person {
    private String name;
    private int number;

    public Person(String name, int number) {
        this.name = name;
        this.number = number;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getNumber() {
        return number;
    }

    public void setNumber(int number) {
        this.number = number;
    }
}

其语法树大概如下图所示:
语法树
对其中一些符号的解释:
Compilation:Person.java文件
TypeDeclaration:Person类
FiledDeclaration: “private String name;”,"private int number;"语句
MethodDeclaration:表示Person类所有方法的声明和实现语句。

输入过程需要将当前类的父类、接口等信息输入到自身符号表中。

1.2 注解处理

该过程主要处理用户自定义的注解

1.3 语义分析和Class文件的生成

语义分析主要依靠之前第一步时生成的抽象语法树,主要分析的内容有变量使用前是否已经声明、语句是否都可到达等。
Class文件的生成
生成步骤:

  • 将实例成员构造器收集到该类构造器中
  • 将静态成员构造器收集为
  • 利用后续遍历将抽象语法树生成为字节码
  • 将符号表输出为class文件

Class文件的内容

  • 结构信息:版本号等
  • 元数据:全限定类名、方法名和属性、字段名和属性等常量池里的东西
  • 方法信息:字节码等

2 类加载机制

类加载机制是指将class文件加载到java虚拟机并形成Class对象的机制。
其过程可以划分为三个步骤:装载、链接、初始化。

2.1 装载

  1. 通过全限定类名获取其二进制字节流。
  2. 将字节流代表的静态存储结构转化为方法区的运行时数据结构
  3. 在内存中生成代表这个类的Class对象,作为方法区这个类的各种数据的访问入口。

2.2 链接

链接过程包括验证-准备-解析

  1. 验证:校验二进制字节码的格式是否正确。
  2. 准备:隐式初始化的过程
  3. 解析:将符号引用替换为直接引用

2.3 初始化

以下四种情况会触发初始化过程:

  • 调用了new
  • 反射调用了类中的方法
  • 子类调用了初始化
  • JVM启动过程中指定的初始化类

初始化时会进行显示赋值。

3 类执行机制

3.1 字节码解释执行

方法的调用有以下四个指令:

  • invokestatic:调用静态方法
  • invokeinterface:调用接口方法
  • invokespecial:调用构造方法
  • invokevirtual:调用除上面三种外的方法

以下面这个简单的程序为例看一下字节码的执行:

    public int solve(int a,int b){
        int c = 2;
        int d = c*(a+b);
        return d;
    }

在先使用javac编译过后,使用javap -c ClassName

	public int solve(int, int);
    Code:
       0: iconst_2//将int类型的常量2放入操作数栈
       1: istore_3//将操作数栈栈顶值弹出放入局部变量区第三个变量
       2: iload_3//装载局部变量区第三个变量到操作数栈
       3: iload_1//装载局部变量区第一个变量到操作数栈
       4: iload_2//装载局部变量区第二个变量到操作数栈
       5: iadd//将操作数栈上面两个数(需要是int类型)弹出做加法后得到的值再放入操作数栈
       6: imul //将操作数栈上面两个数(需要是int类型)弹出做乘法后得到的值再放入操作数栈
       7: istore        4//将操作数栈栈顶值弹出放入局部变量区第四个变量
       9: iload         4//将局部变量区第四个变量压入操作数栈
      11: ireturn //返回操作数栈顶值


3.2 编译执行

解释执行的效率不如编译执行,因此为提升代码的执行性能,通常使用JIT编译器对执行频率较高的代码进行编译,对不频繁的代码继续使用解释的方式。编译的方法Sun JDK提供了两种模式:client compilerserver compiler.

3.2.1 client compiler

client compiler又称为C1,较为轻量级,只做少量性能开销比高的优化。其优化方式主要有方法内联去虚拟化冗余消除

  • 方法内联:把调用的方法的指令直接植入当前方法中。
  • 去虚拟化:当调用的方法只存在一个实现类,其也可以用方法内联。
  • 冗余消除:根据运行时状况进行代码折叠或者删除
3.2.2 server compiler

server compiler又称C2,较为重量级,其中逃逸分析是其很多优化的基础。

  • 逃逸分析:一种确定指针动态范围的方法。

java中判断一个对象是否逃逸主要依据以下两点:

  1. 对象是否被赋值给堆中对象的字段或类的静态变量。
  2. 对象被传进了不确定的代码中去运行。

基于逃逸分析可以做标量替换、栈上分配、同步消除。

  • 标量替换:在对象未逃逸时,可用标量代替聚合量。
  • 栈上分配:在对象未逃逸时,可在栈上分配内存。
Person person = new Person(1, 2);
System.out.println("person.weight=" + person.weight + ";person.high=" + person.high);

因为person未逃逸,那么其可以分配在栈上;同时也可以使用标量替换的方式转化为下面这种形式:

int high = 1;
int weight = 2;
System.out.println("person.weight=" + weight + ";person.high=" + high);
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值