首页 > 其他分享 >从原理聊 JVM(五):JVM 的编译过程和优化手段

从原理聊 JVM(五):JVM 的编译过程和优化手段

时间:2023-08-28 14:47:58浏览次数:34  
标签:point 方法 代码 编译 编译器 JVM 优化

一、前端编译

前端编译就是将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发现方法中这个循环过热,那么仅仅编译这个循环体就好了,执行引擎也会在进入下一个循环时跳转到新编译的代码中去。

作者:京东科技 康志兴

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

标签:point,方法,代码,编译,编译器,JVM,优化
From: https://www.cnblogs.com/Jcloud/p/17662213.html

相关文章

  • 批量python爬虫采集性能优化之减少网络延迟的方法
    今天,我们将一起探讨批量爬虫采集的性能优化,特别关注减少网络延迟的方法。网络延迟是爬虫程序中一个常见的性能瓶颈,通过优化网络延迟,我们可以提高爬虫程序的采集速度和效率。让我们一起来看看如何实现这一目标。1.使用异步请求传统的同步请求方式会导致爬虫程序在等待服务器响应时浪......
  • 介绍一下Oracle的操作符优化
    IN:IN写出来的SQL比较容易写及清晰易懂但是性能总是比较低的,从ORACLE执行的步骤来分析用IN的SQL与不用IN的SQL有以下区别:ORACLE试图将IN转换成多个表的连接,如果转换不成功会先执行IN里面的子查询,再查询外层的表记录,如果转换成功,则直接采用多个表的连接方式查询。所以用IN的SQL至......
  • 10条SQL优化技巧
    一、一些常见的SQL实践(1)负向条件查询不能使用索引select*fromorderwherestatus!=0andstauts!=1notin/notexists都不是好习惯可以优化为in查询:select*fromorderwherestatusin(2,3)(2)前导模糊查询不能使用索引select*fromorderwheredesclike‘%XX’而非前导模糊......
  • IDEA设置JAVA使用的编译语言Language level为8
       设置这个为8不然每次pom文件修改都会变更为5pom.xml增加<properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><java.version>8</java.version></properties>  增加插件配置<!--打包配置--&g......
  • 使用python监控HDFS文件的增量【优化中】
    1.目录1、需求和步骤2、项目结构3、项目代码    3.1建表语句hdfs_Ctreate_table    3.2删除文件记录hdfs_delete_file_record.py    3.3文件路径的小时监控hdfs_path_Monitor.py    3.4文件路径的天监控hdfs_path_Monitor_day.py    3.5文......
  • 从原理聊JVM(五):JVM的编译过程和优化手段 | 京东云技术团队
    一、前端编译前端编译就是将Java源码文件编译成Class文件的过程,编译过程分为4步:1准备初始化插入式注解处理器(AnnotationProcessingTool)。2解析与填充符号表将源代码的字符流转变为标记(Token)集合,构造出抽象语法树(AST)。抽象语法树每个节点都代表着程序代码中的一个语法结构,包含包......
  • JVM调优实战及常量池详解
    阿里巴巴Arthas详解 Arthas 是 Alibaba 在2018年9月开源的 Java诊断工具。支持 JDK6+,采用命令行交互模式,可以方便的定位和诊断线上程序运行问题。Arthas 官方文档十分详细,详见:https://alibaba.github.io/arthas  Arthas使用场景得益于Arthas强大且丰富的功......
  • .NET-10-反编译、IL深入学习
    目录前言前言理论与实际相结合,好好的也看了看,蛮有意思的。反编译:.dll=>.cs(??)参考链接:IL指令官方、ILSpy参考blog:One、Two、IL指令中文解释......
  • JVM 内存大对象监控和优化实践
    作者:vivo互联网服务器团队-LiuZhen、YeWenhao服务器内存问题是影响应用程序性能和稳定性的重要因素之一,需要及时排查和优化。本文介绍了某核心服务内存问题排查与解决过程。首先在JVM与大对象优化上进行了有效的实践,其次在故障转移与大对象监控上提出了可靠的落地方案。最后,总......
  • SQL 优化 (总)
    一、SQL语句1、Select:尽量不用Select星号语句,因为Select星号会全表扫描,导致sql语句性能大大降低。使用具体的列代替星号,避免多余的列2、Where:(1) where条件避免前面加函数和操作(2)避免在where子句中使用!=或<>操作符,否则将引擎放弃索引进行全表扫描3、Like:like避免开头%......