深入理解JVM执行引擎

前端编译与后端编译

前端编译

前端编译主要负责解析Java源代码(.java文件)并将其转换为一种中间表示形式,通常是字节码(.class文件)。这个过程主要包括词法分析、语法分析、语义分析和字节码生成等步骤。

  1. 词法分析:将源代码的字符流转换为一系列的标记(tokens)。
  2. 语法分析:根据Java的语法规则,将标记转换为抽象语法树(Abstract Syntax Tree,AST)。
  3. 语义分析:检查AST是否符合Java语言的语义规则,例如类型检查、变量作用域检查等。
  4. 字节码生成:将AST转换为Java字节码。这些字节码可以在JVM上运行。

后端编译

后端编译则主要负责优化和执行字节码。JVM在执行字节码时,会将其转换为机器码,这一过程也称为即时编译(Just-In-Time Compilation,JIT)。

JIT编译器会分析字节码的运行情况,例如哪些方法被频繁调用,哪些循环被重复执行等,然后对这些热点代码进行优化。优化后的代码将被转换为机器码并缓存起来,以便下次执行时可以直接使用。

后端编译的目的是提高代码的执行效率。通过分析和优化热点代码,JIT编译器可以生成更高效的机器码,从而提高Java程序的运行速度。


小结:

前端编译主要关注源代码到字节码的转换,而后端编译则关注字节码到机器码的优化和执行。这两个阶段共同构成了Java编译器的完整工作流程。

解释执行与编译执行

解释执行

解释执行是指JVM逐条读取字节码,并将其解释(或翻译)为对应平台上的机器码,然后立即执行。这种方式下,不需要事先将整个程序编译成机器码,因此启动速度快,但执行效率相对较低。

解释执行主要用于以下情况:

  1. 程序启动初期:当程序刚刚启动时,JVM通常首先采用解释执行的方式,以便快速启动程序。
  2. 热点代码不频繁:如果程序中的热点代码(即频繁执行的代码)较少,或者热点代码的执行频率不高,那么解释执行可能是一个更合适的选择。

编译执行

编译执行是指JVM将字节码编译成本地机器码,并缓存起来,以便后续直接执行。这种方式下,虽然启动速度可能较慢(因为需要编译过程),但执行效率较高。

编译执行主要用于以下情况:

  1. 热点代码:JVM通过热点探测机制,发现频繁执行的代码(热点代码),然后将其编译成本地机器码。这样,热点代码的执行效率会得到显著提高。
  2. 性能敏感的代码:对于性能要求较高的代码段,JVM也可能采用编译执行的方式,以提高执行效率。

JIT编译器

JVM中的JIT(Just-In-Time)编译器是实现编译执行的关键组件。JIT编译器会根据程序的运行情况,动态地将热点代码编译成本地机器码。这种编译方式既保证了程序的快速启动,又能在运行时实现高效的代码执行。

热点代码识别

JVM使用一种称为热点探测(Hot Spot Detection)的机制来识别这些热点代码。热点探测通常基于两个计数器:

  • 方法调用计数器:记录每个方法被调用的次数。
  • 回边计数器:记录循环回边的执行次数。

当计数器达到一定的阈值时,JIT编译器就会将这些热点代码编译成本地机器码,以便提高执行效率。

热点代码识别主要基于两个方面的考虑:方法调用频率和循环回边执行频率。

  1. 方法调用频率
    JVM会跟踪每个方法的调用次数。如果一个方法被频繁调用,它就会被认为是热点方法。这种方法的调用次数通常通过一个计数器来统计。当计数器达到一定的阈值时,该方法就会被标记为热点方法,并可能被JIT编译器编译成本地代码。
  2. 循环回边执行频率
    除了方法调用,循环内部的代码也是性能优化的重要目标。循环中的"回边"(loop back edge)是指循环体结束后跳回循环开始部分的代码位置。JVM同样会跟踪这些回边的执行次数。如果一个回边的执行次数非常高,说明该循环非常频繁,因此循环体内的代码也可能是热点代码。

然而,热点代码识别并不是完美的。有时候,JIT编译器可能会误判某些代码为热点代码,导致不必要的编译开销。另外,有些代码虽然不常执行,但执行起来非常耗时(例如,复杂的算法或数据结构操作),这些代码可能不会被识别为热点代码,因此得不到及时的优化。

为了解决这个问题,JVM提供了一些调优选项,如调整热点探测的阈值、手动指定需要编译的方法等。此外,JVM的JIT编译器也在不断进化,以更好地识别和优化热点代码。

小问

HotSpot虚拟机为什么不采用执行效率更快的编译执行,而是默认采用一种混合执行的方式?

答:编译执行需要较长的预热过程,对内存有更多的资源限制,需要额外的性能消耗来识别热点代码。

小结

JVM通过结合解释执行和编译执行两种方式,实现了Java程序的高效运行。在程序启动初期和热点代码不频繁的情况下,解释执行能够快速启动程序;而在热点代码和性能敏感的代码段上,编译执行则能够显著提高执行效率。这种灵活的执行方式使得JVM能够在不同的场景下实现最优的性能表现。

客户端编译器与服务端编译器

在JVM中,编译器扮演着非常重要的角色。JVM有两种类型的编译器:客户端编译器(Client Compiler)和服务端编译器(Server Compiler),简称为C1编译器和C2编译器。

  1. 客户端编译器(Client Compiler):
    客户端编译器通常用于桌面应用程序和客户端应用程序,它的优化级别较低,但启动速度较快。客户端编译器的目标是快速启动和响应,因此在编译时不会进行太多的优化工作。它主要关注于生成简单、快速的字节码,以便应用程序可以迅速启动并运行。
  2. 服务端编译器(Server Compiler):
    服务端编译器通常用于服务器应用程序,它的优化级别较高,可以生成更高效的代码。服务端编译器的目标是提高应用程序的性能和吞吐量,因此在编译时会进行更多的优化工作。它会分析代码的运行时行为和性能特征,然后生成更优化的字节码,以提高应用程序的执行效率。

后端编译优化技术

方法内联Inline

把目标方法的代码复制到发起调用的方法之中,避免发生真实的方法调用。可减少频繁创建栈帧的开销。

注意:

  1. 在编程中尽量写小方法,大方法不会内联且成为热点方法后会占用更多CodeCache
  2. 在内存不紧张时,调整JVM参数,减少热点阈值或增加方法体阈值,让更多方法内联。
  3. 尽量使用final,private,static关键字修饰方法。

逃逸分析Escape Analysis

左侧的代码中,t对象不会被外部引用,只会在方法中使用,所以不会逃逸。而右侧代码中,t对象很明显被其他方法使用,就会产生逃逸。

对象一般在堆中分配

对象在栈上分配,进行标量替换

对象分配总体过程

锁消除lock elision

针对synchronized关键字,当JVM检测到一个锁的代码不存在多线程竞争时,会对这个对象的锁进行锁消除。

相关推荐
cdut_suye1 分钟前
Linux工具使用指南:从apt管理、gcc编译到makefile构建与gdb调试
java·linux·运维·服务器·c++·人工智能·python
苹果醋313 分钟前
2020重新出发,MySql基础,MySql表数据操作
java·运维·spring boot·mysql·nginx
小蜗牛慢慢爬行14 分钟前
如何在 Spring Boot 微服务中设置和管理多个数据库
java·数据库·spring boot·后端·微服务·架构·hibernate
azhou的代码园17 分钟前
基于JAVA+SpringBoot+Vue的制造装备物联及生产管理ERP系统
java·spring boot·制造
wm10431 小时前
java web springboot
java·spring boot·后端
smile-yan1 小时前
Provides transitive vulnerable dependency maven 提示依赖存在漏洞问题的解决方法
java·maven
老马啸西风1 小时前
NLP 中文拼写检测纠正论文-01-介绍了SIGHAN 2015 包括任务描述,数据准备, 绩效指标和评估结果
java
Earnest~1 小时前
Maven极简安装&配置-241223
java·maven
皮蛋很白1 小时前
Maven 环境变量 MAVEN_HOME 和 M2_HOME 区别以及 IDEA 修改 Maven repository 路径全局
java·maven·intellij-idea
青年有志1 小时前
JavaWeb(一) | 基本概念(web服务器、Tomcat、HTTP、Maven)、Servlet 简介
java·web