一、前端编译

前端编译就是将Java源码文件编译成Class文件的过程,编译过程分为4步:

1 准备

初始化插入式注解处理器(Annotation Processing Tool)。

2 解析与填充符号表

将源代码的字符流转变为标记(Token)集合,构造出抽象语法树(AST)。

抽象语法树每个节点都代表着程序代码中的一个语法结构,包含包、类型、修饰符、运算符、接口、返回值、代码注释等内容。

编译器的后续行为都是基于抽象语法树来进行。

符号表可以理解为一个K-V结构的集合,存储了以下信息:

  • 变量名和常量
  • 过程和函数名称
  • 文字常量和字符串
  • 编译器生成的临时文件
  • 源语言中的标签

编译器在运行过程中会通过符号表来方便查找所有标识。

3 注解处理器

注解处理器可以看做是一组编译器的插件,用来读写抽象语法树中任意元素。

简单来说,注解处理器的作用就是让编译器对特定注解执行特定逻辑,一般用来生成代码,比如常用的lombok和mapstruct都是基于此。

如果在这期间语法树被修改了,编译器将回到“解析与填充符号表”的过程重新处理,这个循环被称作“轮次(Round)”。

这是开发人员唯一能控制编译器行为的方式。

4 分析与字节码生成

前置步骤可以成功生成一个结构正确的语法树,语义分析则是校验语法树是否符合逻辑。

语义分析又分为四步:

4.1 标注检查

标注检查主要用来检查表量是否被声明、变量与赋值是否匹配等等。

在这个阶段,还会进行被称作“常量折叠”的优化,比如Java代码int a = 1 + 2;,实际编译后会被折叠为int a = 3;

4.2 数据及控制流分析

数据流分析和控制流分析是对程序上下文逻辑更进一步的验证,它可以检查出诸如程序局部变量在使用前是否有赋值、方法的每条路径是否都有返回值、是否所有的受查异常都被正确处理了等问题。

4.3 解语法糖

Java中存在非常多的语法糖用来简化代码实现,比如自动的装箱拆箱、泛型、变长参数等等。这些语法糖会在编译器被还原为基础语法结构,这个过程被称为解语法糖。

4.4 字节码生成

这是javac编译过程的最终阶段,编译器会在这个阶段把前面生成的抽象语法树、符号表生成为class文件,还进行了少量的代码添加和转换。

二、运行时编译

运行时编译的主要目的是为了将代码编译成本地代码,从而节省解释执行的时间。

但是JVM并不是启动后立刻开始执行编译,而是为了执行效率先进行解释执行。等到程序运行过程中,根据热点探测,找出热点代码后,对其进行针对性的编译来逐渐代替解释执行。所以HotSpot JVM采用的是解释器和即时编译器并存的架构。

1 使用编译执行的时机

Sun JDK主要根据方法上的一个计数器来计算是否超过阈值,如果超过则采用编译执行的方式。

  • 调用计数器

记录方法调用次数,在client模式下默认为1500次,在server模式下默认为10000次,可通过-XX:CompileThreshold=10000来设置

  • 回边计数器

循环执行部分代码的执行次数,默认在client模式时为933,在server模式下为140,可通过-XX:OnStackReplacePercentage=140来设置

2 编译模式

在编译上,Sun JDK提供两种模式:client compiler(-client)和server compiler(-server)

2.1 Client compiler

又称C1,较为轻量级,主要包括以下几方面:

2.1.1 方法内联

编译器所做最重要的优化是方法内联

遵循面向对象设计,属性访问通常通过setter/getter方法而非直接调用,而此类方法调用的开销很大,特别是相对方法的代码量而言。

现在的JVM通常都会用内联代码的方式执行这些方法,举个例子:

Order o = new Order();
o.setTotalAmount(o.getOrderAmount() * o.getCount());

而编译后的代码本质上执行的是:

Order o = new Order();
o.orderAmount = o.orderAmount * o.count;

内联默认是开启的,可通过-XX:-Inline关闭,然而由于它对性能影响巨大,并不建议关闭。

方法是否内联取决于它有多热以及它的大小。

2.1.2 去虚拟化

如发现类中的方法只提供了一个实现类,那么对于调用了此方法的代码,将进行方法内联

public interface Animal {
void eat();
} public class Cat implements Animal {
@Override
public void eat() {
System.out.println("Cat eat !");
}
} public class Demo {
public void execute(Animal animal){
animal.eat();
}
}

如果JVM中只有Cat类实现了Animal接口,execute()方法被编译时,会演变成类似如下结构:

public void execute() {
System.out.println("Cat eat !");
}

即execute()方法直接内联了Cat类中eat()方法的内部逻辑。

2.1.3 冗余消除

冗余消除指在编译时,根据运行情况进行代码折叠或者消除

例如:

private static final boolean isDebug = false;

public void execute() {
if (isDebug) {
log.debug("do execute.");
}
System.out.println("done");
}

在执行C1编译后,会演变成如下结构:

public void execute() {
System.out.println("done");
}

这就是为什么,通常不建议直接调用log.debug(),而要先判断的原因。

2.2 Server complier

又称C2,较C1更为重量级,C2更多在于全局优化,而非代码块的优化。

逃逸分析

逃逸分析指的是根据运行状况来判断方法中变量是否会被方法外部读取,如果被外部读取,则认为是逃逸的。

如果通过命令-XX:+DoEscapeAnalysis(默认为true)开启逃逸分析,server编译器会执行较为激进的优化措施。

2.2.1 标量替换

Point point = new Point(1, 2);
System.out.println("point.x = " + point.x + "; point.y" + point.y);

当point对象在后面执行过程中未被使用到时,代码经过编译会演变为如下结构:

int x = 1, y = 2;
System.out.println("point.x = " + x + "; point.y" + y);

2.2.2 栈上分配

在上面的例子中,如果point没有逃逸,那么C2会选择在栈上直接创建point对象,而非堆上。

在栈上分配的好处一方面是对象创建更加快速,另一方面是回收时随着方法的结束,对象也被回收了。

2.2.3 同步削除

Point point = new Point(1, 2);
synchronized(point) {  
System.out.println("point.x = " + point.x);
}

经过分析如果发现point未逃逸,则代码会在编译后变成如下结构:

Point point = new Point(1, 2);
System.out.println("point.x = " + point.x);

2.3 OSR(On Stack Replace,栈上替换)

OSR和C1、C2主要不同在于,OSR仅仅替换循环代码体的入口,而C1、C2替换的是方法调用的入口。

因此在OSR编译后会出现的现象是,方法的整段代码被编译了,但只有在循环代码体部分才执行编译后的机器码,而其他部分仍然是解释执行方式。

如果对方法进行编译优化,等JVM在某个方法中发现这个方法很热,需要编译,那么只有下次调用这个方法才能享受到被优化后的代码,而本次调用依旧使用优化前的代码。OSR主要就是解决这个问题,比如JVM发现方法中这个循环过热,那么仅仅编译这个循环体就好了,执行引擎也会在进入下一个循环时跳转到新编译的代码中去。

作者:京东科技 康志兴

来源:京东云开发者社区 转载请注明来源

从原理聊 JVM(五):JVM 的编译过程和优化手段的更多相关文章

  1. 插件开发遇到的坑------final 型变量,编译过程被优化

    android 插件开发遇到的坑 今天遇到一个坑,pdf 插件,调用了主工程的一个静态final 字符串,但是主工程里面已经没有这个字符串了,却没有崩溃. 后来同事说,因为字符串可能已经直接被写死了. ...

  2. 深入理解JVM(五)JVM优化策略

    5.2一些案例: 1.高性能硬件部署策略: (1)背景:某公司升级了硬件(CPU升级为4核,内存增加为16G),发现不定期出现网页失去响应. (2)原因:①内存增加之后,项目中有在内存中处理文件的大对 ...

  3. 从原理聊JVM(一):染色标记和垃圾回收算法

    作者:京东科技 康志兴 1 JVM运行时内存划分 1.1 运行时数据区域 • 方法区 属于共享内存区域,存储已被虚拟机加载的类信息.常量.静态变量.即时编译器编译后的代码等数据.运行时常量池,属于方法 ...

  4. 从原理聊JVM(二):从串行收集器到分区收集开创者G1

    作者:京东科技 康志兴 1 前言 随着Java的进化过程,涌现出各种不同的垃圾回收器,从串行执行到并行执行,从高吞吐到低延迟,终极目标就是让开发人员专注于程序的代码书写而无需关注内存管理. JDK早期 ...

  5. 从原理聊JVM(三):详解现代垃圾回收器Shenandoah和ZGC

    作者:京东科技 康志兴 Shenandoah Shenandoah一词来自于印第安语,十九世纪四十年代有一首著名的航海歌曲在水手中广为流传,讲述一位年轻富商爱上印第安酋长Shenandoah的女儿的故 ...

  6. JVM系列五:JVM监测&工具

    JVM系列五:JVM监测&工具[整理中]  http://www.cnblogs.com/redcreen/archive/2011/05/09/2040977.html 前几篇篇文章介绍了介 ...

  7. JVM运行原理及Stack和Heap的实现过程

    Java语言写的源程序通过Java编译器,编译成与平台无关的‘字节码程序’(.class文件,也就是0,1二进制程序),然后在OS之上的Java解释器中解释执行,而JVM是java的核心和基础,在ja ...

  8. 《Java虚拟机原理图解》4.JVM机器指令集

    0. 前言 Java虚拟机和真实的计算机一样,执行的都是二进制的机器码:而我们将.java 源码编译成.class 文件,class文件便是Java虚拟机可以认识的二进制机器码,Java可以识别cla ...

  9. JVM(五):探究类加载过程-上

    JVM(五):探究类加载过程-上 本文我们来研究一个Java字节码文件(Class文件)是如何加载入内存中的,在這個过程中涉及类加载过程中的加载,验证,准备,解析(连接),初始化,使用,销毁过程,并探 ...

  10. JVM系列之二:编译过程

    1. Java的编译和执行 编译包括两种情况: 1,源码编译成字节码2,字节码编译成本地机器码(符合本地系统专属的指令) 解释执行也包括两种情况: 1,源码解释执行2,字节码解释执行 解释和编译执行的 ...

随机推荐

  1. UnorderedObjectListWarning: Pagination may yield inconsistent results with an unordered object_list

    错误: UnorderedObjectListWarning: Pagination may yield inconsistent results with an unordered object_l ...

  2. Vue 异步通信Axios

    使用Axios实现异步通信需要先导入cdn: <script src="https://unpkg.com/axios@1.4.0/dist/axios.min.js"> ...

  3. Python基础 - 输入和输出

    输出   Python提供了 print() 内置函数完成输出 1 print("你好") 2 3 4 # 你好 n = "你好" print(n) # 你好 ...

  4. 代码随想录算法训练营Day52 动态规划

    代码随想录算法训练营 代码随想录算法训练营Day52 动态规划| 300.最长递增子序列 674. 最长连续递增序列 718. 最长重复子数组 300.最长递增子序列 题目链接:300.最长递增子序列 ...

  5. 【踩坑记录】字节流数据按照string的方式读取然后按照string的方案存储,编码导致二进制数据发生变化,原理记录

    ​ 目录 问题缘由 背后原理 C#代码示例 总结 问题缘由 由于公司需求,需要读取游戏Redis数据做内外网数据迁移,没有与游戏组过多的沟通.  使用的数据类型是Hash, key是string,va ...

  6. 新风向标:学术界开始从 Python 转向 Rust

    作者 | Jeffrey M. Perkel 策划 | Tina 来源 | Rust语言中文社区 Rust 现在已经越来越受到科学家们的欢迎了,比起 Python,Rust 有着更高效的性能,同时在社 ...

  7. DASCTF二进制专项部分Writeup

    easynote create:堆大小可以任意分配只要不超过0xFFF create()  unsigned __int64 create() { int i; // [rsp+0h] [rbp-20 ...

  8. 之江实验室: 如何基于 JuiceFS 为超异构算力集群构建存储层 ?

    今天,高性能计算结合人工智能技术正在推动科研创新.例如通过破解水稻基因密码推动作物育种从"试验选优"向"计算选优"发展,在医药领域快速分析分子与蛋白之间的相互作 ...

  9. 【HMS Core】Health Kit云测数据接入相关问题

    ​[问题描述1] 1.由于存在IOS.android.微信小程序,计划接入"云侧数据开放服务",使用模式为我们自己的服务端去同步华为健康数据,终端通过服务端获取最新的数据. 2.在 ...

  10. 尚医通day13【预约挂号】(内附源码)

    页面预览 预约挂号 根据预约周期,展示可预约日期,根据有号.无号.约满等状态展示不同颜色,以示区分 可预约最后一个日期为即将放号日期 选择一个日期展示当天可预约列表 预约确认 第01章-预约挂号 接口 ...