一、前端编译

前端编译就是将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. 域名配置动态代理后,为什么每次 ping 还是相同的 ip?

    当你配置了域名的动态代理后,ping 命令所显示的 IP 地址不会随着代理服务器的变化而变化. 这是因为 ping 命令使用了 DNS 缓存,它会将域名解析结果缓存到本地,直到缓存过期或者手动清除缓存 ...

  2. 万字长文详述ClickHouse在京喜达实时数据的探索与实践

    1 前言 京喜达技术部在社区团购场景下采用JDQ+Flink+Elasticsearch架构来打造实时数据报表.随着业务的发展 Elasticsearch开始暴露出一些弊端,不适合大批量的数据查询,高 ...

  3. django 如何提升性能(高并发)

    django 如何提升性能(高并发) 对一个后端开发程序员来说,提升性能指标主要有两个一个是并发数,另一个是响应时间网站性能的优化一般包括 web 前端性能优化,应用服务器性能优化,存储服务器优化. ...

  4. < Python全景系列-9 > Python 装饰器:优雅地增强你的函数和类

    欢迎来到我们的系列博客<Python全景系列>第九篇!在这个系列中,我们将带领你从Python的基础知识开始,一步步深入到高级话题,帮助你掌握这门强大而灵活的编程语法.无论你是编程新手,还 ...

  5. Galaxy 生信平台(二):生产环境部署

    在 上一篇文章中,我们介绍了适合单个用户进行使用和开发的 Galaxy 在线平台,今天我们来聊一下在为多用户生产环境设置 Galaxy 时,我们应采取的一些可以让 Galaxy 获得最佳性能的额外步骤 ...

  6. ChatGLM 拉取清华git项目

    windows使用nvdia显卡运行ChatGLM 1. 安装nvidia显卡驱动 https://developer.nvidia.com/cuda-11-8-0-download-archive? ...

  7. App性能测试之SoloPi

    SoloPi简介 SoloPi是蚂蚁金服开发的一款无线化.非侵入.免Root的Android专项测试工具.直接操控安卓系统的手机或智能设备,即可完成自动化的功能.性能.兼容性.以及稳定性测试等工作,降 ...

  8. JUC同步锁原理源码解析三----CountDownLatch、CyclicBarrier

    JUC同步锁原理源码解析三----CountDownLatch.CyclicBarrier CountDownLatch.CyclicBarrier的来源 1.CountDownLatch的来源 A ...

  9. 前端树形结构图treeShapeStruct,可拖拽移动,点击展开收缩,无限添加子集

    快速实现树形结构图,可拖拽移动,点击展开收缩,无限添加子集; 下载完整代码请访问uni-app插件市场地址:https://ext.dcloud.net.cn/plugin?id=12650 效果图如 ...

  10. 让AI支持游戏制作流程:从游戏设计到发布一个完整的生态系统

    目录 引言 随着游戏产业的快速发展,人工智能(AI)技术在游戏开发中的应用越来越广泛.游戏设计人员可以通过利用AI技术来自动化游戏中的某些流程,提高游戏制作的效率,降低开发成本,同时还可以创造出更加具 ...