一、前端编译

前端编译就是将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. Can't uninstall 'Pillow'. No files were found to uninstall.

    Can't uninstall 'Pillow'. No files were found to uninstall. Pillow卸载不掉的解决办法 1.进入python所在路径,进入scripts ...

  2. MMCM and PLL Dynamic Reconfiguration

    Reconfiguration is performed through the DRP. The DRP provides access to the configuration bits that ...

  3. 【模型部署 01】C++实现分类模型(以GoogLeNet为例)在OpenCV DNN、ONNXRuntime、TensorRT、OpenVINO上的推理部署

    深度学习领域常用的基于CPU/GPU的推理方式有OpenCV DNN.ONNXRuntime.TensorRT以及OpenVINO.这几种方式的推理过程可以统一用下图来概述.整体可分为模型初始化部分和 ...

  4. odoo开发教程十三:qweb报表

    一:概述 报表是使用qweb定义的,报表的pdf导出是使用wkhtmltopdf来完成的. 如果需要为一个模型创建报表,需要定义report及对应模板. 如果有需要的话还可以指定特定的纸张格式, 如果 ...

  5. 花了半天时间,使用spring-boot实现动态数据源,切换自如

      在一个项目中使用多个数据源的情况很多,所以动态切换数据源是项目中标配的功能,当然网上有相关的依赖可以使用,比如动态数据源,其依赖为, <dependency> <groupId& ...

  6. 读少写多的条件下 ConcurrentHashMap 和 ReadWriteLock 的选择

    场景是这样的:两个对象往一个 Map 里循环写入,另外一个对象偶尔读一次,写的频率比读的频率高很多.希望实现的是读的时候暂停写入.CocurrentHashMap 和 ReadWriteLock 各有 ...

  7. @SuppressWarnings注解的使用

    Java编译器在编译代码时,会产生一些安全警告信息.如果被@SuppressWarnings注解标记的元素,就可以告诉编译器抑制指定的警告. 先看看@SuppressWarnings注解在Java S ...

  8. AR技术的应用与未来

    目录 随着科技的不断进步,增强现实(AR)技术也在不断发展壮大.AR技术是一种通过计算机技术和传感器技术将虚拟信息融合到现实世界中的技术,可以为用户带来一种全新的.交互性更强的体验.本文将探讨AR技术 ...

  9. Windows服务启动exe无界面终极解决方案

      1.前言 我这个方案(C#操作)是彻底解决[从Windows服务启动程序exe,程序无界面]问题的终极解决方案,终极方案,绝对的终极方案,本来打算收钱的,还是算了,你们也不容易,关注我一下就行.后 ...

  10. 面由 AI 生|ZegoAvatar 捏脸技术解析

    一.AI"卷"进实时互动 2021年,元宇宙概念席卷全球,国内各大厂加速赛道布局,通过元宇宙为不同的应用场景的相关内容生态进行赋能.针对"身份"."沉 ...