【Java JMM】编译和优化

1 前端编译

在 Java 技术下, “编译期” 是一个比较含糊的表述, 因为它可能指的是

  1. 前端编译器 (“编译器的前端” 更准确一些) 把 *.java 文件转变成 *.class 文件的过程
  2. Java 虚拟机的即时编译器 (常称 JIT 编译器, Just In Time Compiler) 运行期把字节码转变成本地机器码的过程
  3. 使用静态的提前编译器 (常称 AOT 编译器, Ahead Of Time Compiler) 直接把程序编译成与目标机器指令集相关的二进制代码的过程

这三者的代表性编译器产品

  1. 前端编译器: JDK 的 Javac, Eclipse JDT 中的增量式编译器 (ECJ)
  2. 即时编译器: HotSpot 虚拟机的 C1, C2 编译器, Graal 编译器
  3. 提前编译器: JKD 的 Jaotc, GNU Compiler for the Java (GCJ), Excelsior JET

这 3 类过程中最符合普通程序员对 Java 程序编译认知的应该是第一类。下面讨论的基本都限制在第一种情况。

1.1 Javac 源码

在 JDK8 中, Javac 的源码主要存放在 $JAVA_HOME/lib/tools.jar, 拷贝一份到一个地方, 解压后, 依次进入 com/sun/tools/javac, 这里就是源
码的地方。当然最简单的方式就是打开一包编译器, 顺便建立一个 Java 项目, 搜索 com.sun.tools.javac.Main 就可以了。

从 Javac 代码的总体结构来看, 编译过程大致可以分为 1 个准备过程和 3 个处理过程, 它们分别如下所示

  1. 准备过程: 初始化插入式注解处理器
  2. 解析与填充符号表过程, 包括

2.1 词法, 语法分析。将源代码的字符流转变为标记集合, 构造出抽象语法树
2.2 填充符号表。产生符号地址和符号信息

  1. 插入式注解处理器的注解处理过程: 插入式注解处理器的执行阶段
  2. 分析与字节码生成过程, 包括

4.1 标注检查。对语法的静态信息进行检查
4.2 数据流及控制流分析。对程序动态运行过程进行检查
4.3 解语法糖。将简化代码编写的语法糖还原为原有的形式
4.4 字节码生成。将前面各个步骤所生成的信息转化成字节码

上述 3 个处理过程里, 执行插入式注解时又可能会产生新的符号, 如果有新的符号产生, 就必须转回到之前的解析, 填充符号表的过程中重新处理这些新符号。
大体的流程如下:
Alt 'Java 处理器处理过程'

代码的入口: com.sun.tools.javac.main.JavaCompiler, 后面整个详细的流程省略。

流程:

  1. 词法分析, 源代码的字符流转变为标记 (Token) 集合, 主要由 com.sun.tools.javac.parser.Scanner 实现

  2. 语法分析, 根据标记序列构造抽象语法树, 主要由 com.sun.tools.javac.parser.Parser, 产生的抽象树为 com.sun.tools.javac.tree.JCTree

  3. 填充符号表, 对符号表进行填充, 主要由 com.sun.tools.javac.parser.Enter

  4. 注解处理器, 通过注解处理器, 可以对抽象语法树中的元素进行读取, 修改, 添加, 在处理注解期间对语法树进行过修改, 编译器将回到解析及填充符号表
    的过程重新处理, 直到所有插入式注解处理器都没有再对语法树进行修改为止。插入式注解处理器的初始主要为 com.sun.tools.javac.main.JavaCompiler.initPorcessAnnotations,
    而执行过程则为 com.sun.tools.javac.main.JavaCompiler.processAnnotions

  5. 语义分析, 是对结构上正确的源程序进行上下文相关性质的检查, 大体可以分为 2 个流程 标注检查 (JavaCompiler.attribute) 和控制流分析 (JavaCompiler.flow)。
    标注检查中, 会对源代码中做一个 “常量折叠 (Constant Folding)” 的优化 (int a = 1 + 4; ==> int a = 5)

  6. 解语法糖, 把语法糖还原回原始的基础语法结构, 由 com.sun.tools.javac.comp.TransTypes 和 com.sun.tools.javac.comp.Lower 完成

  7. 字节码生成, 由 com.sun.tools.javac.jvm.Gen 完成, 把前面各个步骤生成的信息 (语法树, 符号表) 转为字节码, 还会进行少了代码的添加和转换,
    类似于 和 , 将字符串的 + , 替换为 StringBuffer 或者 StringBuilder 的 append 操作

  8. 最终由 com.sun.tools.javac.jvm.ClassWriter 输出字节码, 生成 Class 文件。

2 后端编译

编译器无论在何时, 在何种状态下把 Class 文件转换成与本地基础设施 (硬件指令集, 操作系统) 相关的二进制机器码, 它都可以视为整个编译过程的后端。

2.1 即时编译器

目前主流的两款商用 Java 虚拟机 (HotSpot, OpenJ9) 里, Java 程序最初都是通过解释器 (Interpreter) 进行解释执行的, 当虚拟机发现某个方法或
代码块的运行特别频繁, 就会把这些代码认定为 “热点代码 (Hot Spot Code)”, 为了提高热点代码的执行效率, 在运行时, 虚拟机将会把这些代码编译成本
地机器码, 并以各种手段尽可能地进行代码优化, 运行时完成这个任务的后端编译器被称为即时编译器。

现在主流的 Java 虚拟机内部都同时包含解释器和编译器, 2 者各有好处。
当程序需要迅速启动和执行的时候, 解释器可以首先发挥作用, 省去编译的时间, 立即运行。当程序启动后, 随着时间的推移, 编译器逐渐发挥作用, 把越来
越多的代码编译成本地代码, 这样可以减少解释器的中间损耗, 获得更高的执行效率。
当运行环境的内存资源限制较大时, 使用解释执行可以节约内存, 反之, 可以使用编译执行提高效率。
解释器可以作为编译器激进优化 (不能保证所有情况都正确, 但大多数时候都能提升运行速度的优化) 的后备"逃生门", 通过逆优化退回到解释状态执行。

HotSpot 虚拟机中内置了两个 (或三个) 即时编译器: 客户端编译器 (Client Compiler, 简称 C1), 服务端编译器 (Service Compiler, 简称 C2),
还有 JDK10 出现的长期目标是替代 C2 的 Graal 编译器。

热点代码

  • 被多次调用的方法
  • 被多次执行的循环体

对于这两种情况, 编译的目标对象都是整个方法体, 而不会是单独的循环体。

HotSpot 采用基于计数器的热点探测 (Counter Based Hot Spot Code Detection) 的方式确定方法是否为 “热点代码”。 虚拟机会为每个方法 (甚至是代码块)
建立计数器, 统计方法的执行次数, 如果执行次数超过一定的阈值就认为它是 “热点方法”。

为了实现热点计数, HotSpot 为每个方法准备了两类计数器: 方法调用计数器 (Invocation Counter) 和回边计数器 (Back Edge Counter, “回边” 的
意思就是指在循环边界往回跳转)。调用计数器 + 回边计数器之和超过了阈值 (客户端模式, 默认为 1500 次, 服务端模式: 10000 次), 就会向即时编译器
提交一个该方法的代码编译请求。

2.2 提前编译器

2 种作法:
一条分支是做与传统 C, C++ 编译器类似的, 在程序运行之前把程序代码编译成机器码的静态翻译工作;
另外一条分支是把原本即时编译器在运行时要做的编译工作提前做好并保存下来, 下次运行到这些代码 (譬如公共库代码在被同一台机器其他 Java 进程使用)
时直接把它加载进来使用

3 参考

《深入理解Java虚拟机》- 周志明

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/264613.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

《Python》面试常问:深拷贝、浅拷贝、赋值之间的关系(附可变与不可变)【用图文讲清楚!】

背景 想必大家面试或者平时学习经常遇到问python的深拷贝、浅拷贝和赋值之间的区别了吧?看网上的文章很多写的比较抽象,小白接收的难度有点大,于是乎也想自己整个文章出来供参考 可变与不可变 讲深拷贝和浅拷贝之前想讲讲什么是可变数据类型…

Pytorch常用的函数(五)np.meshgrid()和torch.meshgrid()函数解析

Pytorch常用的函数(五)np.meshgrid()和torch.meshgrid()函数解析 我们知道torch.meshgrid()函数的功能是生成网格,可以用于生成坐标; 在numpy中也有一样的函数np.meshgrid(),但是用法不太一样,我们直接上代码进行解释。 1、两者…

如何在Window系统下搭建Nginx服务器环境并部署前端项目

1.下载并安装Nginx 在nginx官网nginx: download 下载稳定版本至自己想要的目录。 解压后进入目录 2.启动Nginx服务器 启动方式有两种: (1)直接进入nginx安装目录下,双击nginx.exe运行,此时命令行窗口一闪而过&…

浏览器 cookie 的原理(详)

目录 1,cookie 的出现2,cookie 的组成浏览器自动发送 cookie 的条件 3,设置 cookie3.1,服务端设置3.1,客户端设置3.3,删除 cookie 4,使用流程总结 整理和测试花了很大时间,如果对你有…

python调用GPT API

每次让gpt给我生成一个调用api的程序时,他经常会调用以前的一些api的方法,导致我的程序运行错误,所以这期记录一下使用新的方法区调用api 参考网址 Migration Guide,这里简要地概括了一下新版本做了哪些更改 OpenAI Python API l…

引领汽车营销新趋势,3DCAT实时云渲染助力汽车三维可视化

当前,汽车产业发展正从电动化的上半场,向智能化的下半场迈进。除了车机技术体验的智能化之外,观车体验的智能化也不容忽视。 这是因为,随着数字化、智能化、个性化的趋势,消费者对汽车的需求和期待也越来越高&#xf…

2016年第五届数学建模国际赛小美赛B题直达地铁线路解题全过程文档及程序

2016年第五届数学建模国际赛小美赛 B题 直达地铁线路 原题再现: 在目前的大都市地铁网络中,在两个相距遥远的车站之间运送乘客通常需要很长时间。我们可以建议在两个长途车站之间设置直达班车,以节省长途乘客的时间。   第一部分&#xf…

Qt的简单游戏实现提供完整代码

文章目录 1 项目简介2 项目基本配置2.1 创建项目2.2 添加资源 3 主场景3.1 设置游戏主场景配置3.2 设置背景图片3.3 创建开始按钮3.4 开始按钮跳跃特效实现3.5 创建选择关卡场景3.6 点击开始按钮进入选择关卡场景 4 选择关卡场景4.1场景基本设置4.2 背景设置4.3 创建返回按钮4.…

Java面向对象(初级)

面向对象编程(基础) 面向对象编程(OOP)是一种编程范式,它强调程序设计是围绕对象、类和方法构建的。在面向对象编程中,程序被组织为一组对象,这些对象可以互相传递消息。面向对象编程的核心概念包括封装、继承和多态。…

2023.12.21 关于 Redis 常用数据结构 和 单线程模型

目录 各数据结构具体编码方式 查看 key 对应 value 的编码方式 Reids 单线程模型 经典面试题 IO 多路复用 Redis 常用数据结构 Redis 中所有的 key 均为 String 类型,而不同的是 value 的数据类型却有很多种以下介绍 5 种 value 常见的数据类型 注意&#xff1…

阿里云 ACK One 新特性:多集群网关,帮您快速构建同城容灾系统

云布道师 近日,阿里云分布式云容器平台 ACK One[1]发布“多集群网关”[2](ACK One Multi-cluster Gateways)新特性,这是 ACK One 面向多云、多集群场景提供的云原生网关,用于对多集群南北向流量进行统一管理。 基于 …

虚拟机的下载、安装(模拟出服务器)

下载 vmware workstation(收费的虚拟机) 下载vbox 网址:Oracle VM VirtualBox(免费的虚拟机) 以下选择一个下载即可,建议下载vbox,因为是免费的。安装的时候默认下一步即可(路径最好…

hiveserver负载均衡配置

一.安装nginx 参数我的另一篇文章:https://mp.csdn.net/mp_blog/creation/editor/135152478 二.配置nginx服务参数 worker_processes 1; events { worker_connections 1024; } stream { upstream hiveserver2 { # least_conn; # 使用最少连接路由…

八大排序算法@直接插入排序(C语言版本)

目录 直接插入排序概念算法思想代码实现核心算法:直接插入排序的算法实现: 特性总结 直接插入排序 概念 算法思想 把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新…

【Spring实战】配置多数据源

文章目录 1. 配置数据源信息2. 创建第一个数据源3. 创建第二个数据源4. 创建启动类及查询方法5. 启动服务6. 创建表及做数据7. 查询验证8. 详细代码总结 通过上一节的介绍,我们已经知道了如何使用 Spring 进行数据源的配置以及应用。在一些复杂的应用中,…

文档 - - - Docsify文档创建

目录 1. Docsify 介绍2. 创建 Docsify 项目2.1 安装 Node.js2.1 安装 docsfiy-cli2.3 初始化项目2.4 运行项目2.5 使用 Python 运行项目(扩展,不推荐有bug) 3. 配置 Docsify 项目3.1 修改等待加载文字3.2 添加网站 ico 图标3.3 创建新页面写文…

python 用OpenCV 将图片转视频

import os import cv2 import numpy as npcv2.VideoWriter()参数 cv2.VideoWriter() 是 OpenCV 中用于创建视频文件的类。它的参数如下: filename:保存视频的文件名。 fourcc:指定视频编解码器的 FourCC 代码&#xf…

SVM —— 代码实现

SMO 算法的实现步骤: 代码如下: import numpy as np import matplotlib.pyplot as plt import seaborn as sns import random# 设置中文字体为宋体,英文字体为 times new roman sns.set(font"SimSun", style"ticks", fo…

webpack学习-7.创建库

webpack学习-7.创建库 1.暴露库1.1概念1.2验证1.2.1 不导出方法1.2.2 导出方法 2.外部化 lodash3.外部化的限制4.最终步骤5.使用自己的库5.1坑 6.总结 1.暴露库 这个模块学习有点坑。看名字就是把自己写的个包传到npm,而且还要在项目中使用到它,支持各种…

java类和对象的思想概述

0.面向对象Object OOP——名人名言:类是写出来的,对象是new出来的 **> 学习面向对象的三条路线 java类以及类成员:(重点)类成员——属性、方法、构造器、(熟悉)代码块、内部类面向对象特征&…