主页
  • 主页
  • 分类
  • 热文
  • 教程
  • 面试
  • 标签
Java

Java 基础

Java 主页
Java 概述
Java 历史
Java 功能
Java 与 C++
Java JVM(Java虚拟机)
Java JDK、JRE 和 JVM
Java Hello World 程序
Java 环境设置
Java 基本语法
Java 变量类型
Java 数据类型
Java 类型转换
Java Unicode 系统
Java 基本运算符
Java 注释
Java 用户输入
Java 日期和时间

Java 控制语句

Java 循环控制
Java 决策结构
Java if-else 语句
Java switch 语句
Java for 循环
Java for each 循环
Java while 循环
Java do...while 循环
Java break 语句
Java continue 语句

Java 面向对象编程

Java OOP概念
Java 类和对象
Java 类属性
Java 类方法
Java 方法
Java 变量作用域
Java 构造函数
Java 访问修饰符
Java 继承
Java 聚合
Java 多态
Java 覆盖
Java 方法重载
Java 动态绑定
Java 静态绑定
Java 实例初始化块
Java 抽象
Java 封装
Java 接口
Java 包
Java 内部类
Java 静态类
Java 匿名类
Java 单例类
Java 包装类
Java 枚举类
Java 枚举构造函数
Java 枚举字符串

Java 内置类

Java 数字
Java 布尔值
Java 字符
Java 数组
Java 数学类

Java 文件处理

Java 文件
Java 创建文件
Java 写入文件
Java 读取文件
Java 删除文件
Java 目录操作
Java I/O流

Java 错误和异常

Java 异常
Java Try Catch
Java try-with-resources
Java 多个 Catch
Java 嵌套 try
Java finally
Java 抛出异常
Java 异常传播
Java 内置异常
Java 自定义异常

Java 多线程

Java 多线程
Java 线程生命周期
Java 创建线程
Java 启动线程
Java 加入线程
Java 命名线程
Java 线程调度
Java 线程池
Java 主线程
Java 线程优先级
Java 守护线程
Java 线程组
Java JVM 关闭

Java 同步

Java 线程同步
Java 块同步
Java 静态同步
Java 线程间通信
Java 线程死锁
Java 中断线程
Java 线程控制
Java 可重入锁

Java 网络

Java 网络编程
Java 套接字编程
Java URL 处理
Java URL 类
Java URLConnection 类
Java HttpURLConnection 类
Java Socket 类
Java 泛型

Java 集合

Java 集合框架
Java 集合接口

Java 接口

Java 列表接口
Java 队列接口
Java 映射接口
Java SortedMap 接口
Java 集合(Set)接口
Java SortedSet 接口

Java 数据结构

Java 数据结构
Java 枚举接口

Java 集合算法

Java 迭代器
Java 比较器
Java Comparable 接口

Java 高级

Java 命令行参数
Java Lambda 表达式
Java 发送电子邮件
Java 小应用程序
Java Javadoc
Java 自动装箱和拆箱
Java mismatch() 方法
Java REPL
Java 多版本发布 JAR
Java 私有接口方法
Java 金刚石操作符
Java 多分辨率图像 API
Java 集合的工厂方法
Java 模块系统
Java Nashorn 引擎
Java Optional 类
Java 方法引用
Java 功能接口
Java 默认方法
Java Base64 工具类
Java Switch 表达式
Java Collectors.teeing() 方法
Java 基准测试
Java 文本块
Java 动态CDS
Java ZGC
Java NullPointerException
Java jpackage
Java 密封类
Java 记录
Java 隐藏类
Java instanceof
Java 紧凑数字格式化
Java 垃圾回收
Java JIT 编译器

Java 杂项

Java 递归
Java 正则表达式
Java 序列化
Java 字符串类
Java 进程 API
Java Stream API
Java @Deprecated 注释
Java CompletableFuture API
Java Streams
Java 日期时间 API

基础

Java 主页
Java 概述
Java 历史
Java 功能
Java 与 C++
Java JVM(Java虚拟机)
Java JDK、JRE 和 JVM
Java Hello World 程序
Java 环境设置
Java 基本语法
Java 变量类型
Java 数据类型
Java 类型转换
Java Unicode 系统
Java 基本运算符
Java 注释
Java 用户输入
Java 日期和时间

控制语句

Java 循环控制
Java 决策结构
Java if-else 语句
Java switch 语句
Java for 循环
Java for each 循环
Java while 循环
Java do...while 循环
Java break 语句
Java continue 语句

面向对象编程

Java OOP概念
Java 类和对象
Java 类属性
Java 类方法
Java 方法
Java 变量作用域
Java 构造函数
Java 访问修饰符
Java 继承
Java 聚合
Java 多态
Java 覆盖
Java 方法重载
Java 动态绑定
Java 静态绑定
Java 实例初始化块
Java 抽象
Java 封装
Java 接口
Java 包
Java 内部类
Java 静态类
Java 匿名类
Java 单例类
Java 包装类
Java 枚举类
Java 枚举构造函数
Java 枚举字符串

内置类

Java 数字
Java 布尔值
Java 字符
Java 数组
Java 数学类

文件处理

Java 文件
Java 创建文件
Java 写入文件
Java 读取文件
Java 删除文件
Java 目录操作
Java I/O流

错误和异常

Java 异常
Java Try Catch
Java try-with-resources
Java 多个 Catch
Java 嵌套 try
Java finally
Java 抛出异常
Java 异常传播
Java 内置异常
Java 自定义异常

多线程

Java 多线程
Java 线程生命周期
Java 创建线程
Java 启动线程
Java 加入线程
Java 命名线程
Java 线程调度
Java 线程池
Java 主线程
Java 线程优先级
Java 守护线程
Java 线程组
Java JVM 关闭

同步

Java 线程同步
Java 块同步
Java 静态同步
Java 线程间通信
Java 线程死锁
Java 中断线程
Java 线程控制
Java 可重入锁

网络

Java 网络编程
Java 套接字编程
Java URL 处理
Java URL 类
Java URLConnection 类
Java HttpURLConnection 类
Java Socket 类
Java 泛型

集合

Java 集合框架
Java 集合接口

接口

Java 列表接口
Java 队列接口
Java 映射接口
Java SortedMap 接口
Java 集合(Set)接口
Java SortedSet 接口

数据结构

Java 数据结构
Java 枚举接口

集合算法

Java 迭代器
Java 比较器
Java Comparable 接口

高级

Java 命令行参数
Java Lambda 表达式
Java 发送电子邮件
Java 小应用程序
Java Javadoc
Java 自动装箱和拆箱
Java mismatch() 方法
Java REPL
Java 多版本发布 JAR
Java 私有接口方法
Java 金刚石操作符
Java 多分辨率图像 API
Java 集合的工厂方法
Java 模块系统
Java Nashorn 引擎
Java Optional 类
Java 方法引用
Java 功能接口
Java 默认方法
Java Base64 工具类
Java Switch 表达式
Java Collectors.teeing() 方法
Java 基准测试
Java 文本块
Java 动态CDS
Java ZGC
Java NullPointerException
Java jpackage
Java 密封类
Java 记录
Java 隐藏类
Java instanceof
Java 紧凑数字格式化
Java 垃圾回收
Java JIT 编译器

杂项

Java 递归
Java 正则表达式
Java 序列化
Java 字符串类
Java 进程 API
Java Stream API
Java @Deprecated 注释
Java CompletableFuture API
Java Streams
Java 日期时间 API

Java JIT 编译器


上一章 下一章

JIT 编译器概述

即时编译器 (JIT Compiler) 是由 JVM 内部使用的一种编译器,用于将字节码中的热点代码翻译成机器可理解的代码。JIT 编译器的主要目的是对性能进行重度优化。

Java 编译的目标是 JVM。Java 编译器 javac 将 Java 代码编译成字节码。接着,JVM 解释这些字节码并在底层硬件上执行。对于一些需要反复执行的代码,JVM 会识别这些代码为热点,并使用 JIT 编译器进一步将其编译到本地机器代码级别,并在需要时重用已编译的代码。

首先,让我们了解编译语言与解释语言的区别以及 Java 如何结合两者的优势。

编译语言 vs 解释语言

像 C、C++ 和 FORTRAN 这样的语言是编译语言。它们的代码以二进制代码的形式交付,针对底层机器。这意味着高级代码由静态编译器一次性编译成特定架构的二进制代码。生成的二进制文件在其他架构上无法运行。

另一方面,像 Python 和 Perl 这样的解释语言可以在任何有合适解释器的机器上运行。它们逐行遍历高级代码,并将其转换为二进制代码。

通常情况下,解释代码的速度比编译代码慢。例如,在循环的情况下,解释器会对每次迭代的相应代码进行转换,而编译代码只会转换一次。此外,由于解释器一次只能看到一行代码,因此它无法执行任何显著的代码优化,如更改语句的执行顺序。

示例

考虑加法运算:由于访问内存可能消耗多个 CPU 周期,一个好的编译器会在数据可用时发出指令来从内存中获取数据并执行加法,而不是等待;与此同时,执行其他指令。而在解释过程中,由于解释器在任何时候都不知道整个代码,因此不会发生这样的优化。

然而,解释语言可以在任何具有有效解释器的机器上运行。

Java 是编译还是解释?

Java 试图找到一个中间点。由于 JVM 处于 javac 编译器和底层硬件之间,javac 或其他编译器将 Java 代码编译为 JVM 能够理解的字节码。随后,JVM 在代码执行时使用即时编译 (JIT Compilation) 将字节码编译为二进制代码。

热点代码 (HotSpots)

在一个典型的程序中,只有很小一部分代码会被频繁执行,而这部分代码往往对整个应用的性能影响显著。这样的代码段被称为热点代码 (HotSpots)。

如果某段代码只执行一次,则编译它是浪费资源,直接解释字节码会更快。但如果这段代码是一个热点且被多次执行,JVM 则会选择编译它。例如,如果一个方法被多次调用,那么编译代码所需的额外周期将会被生成的更快的二进制代码所抵消。

随着 JVM 更多地运行特定的方法或循环,它收集的信息越多,就越能做出各种优化,从而生成更快的二进制代码。

JIT 编译器的工作原理

JIT 编译器通过将某些热点代码编译为机器或本地代码来提高 Java 程序的执行时间。

JVM 扫描整个代码,识别出需要由 JIT 优化的热点代码,然后在运行时调用 JIT 编译器,从而提高程序效率并使其运行得更快。

由于 JIT 编译是一个处理器和内存密集型活动,因此 JIT 编译需要进行相应的规划。

编译级别

JVM 支持五种编译级别:

  • 解释器
  • C1 具有完全优化 (无分析)
  • C1 具有调用和后向边计数器 (轻量级分析)
  • C1 具有完整分析
  • C2 (使用前几步的分析数据)

如果您想禁用所有 JIT 编译器并仅使用解释器,请使用 -Xint。

客户端 JIT vs 服务器 JIT

使用 -client 和 -server 分别激活相应的模式。客户端编译器 (C1) 比服务器编译器 (C2) 更早开始编译代码。因此,在 C2 开始编译之前,C1 已经编译了一些代码段。 但在等待期间,C2 对代码进行了分析,以获得比 C1 更多的信息。因此,它等待的时间可以通过生成更快的二进制代码来补偿。

从用户的角度来看,权衡在于程序的启动时间和程序运行所需的时间。如果启动时间至关重要,则应使用 C1。如果预计应用程序将长时间运行(如部署在服务器上的应用程序),则最好使用 C2,因为它生成的代码速度要快得多,大大抵消了任何额外的启动时间。

对于 IDE(如 NetBeans、Eclipse)和其他 GUI 应用程序,启动时间至关重要。NetBeans 启动可能需要一分钟或更长时间。当启动像 NetBeans 这样的程序时,会编译数百个类。在这种情况下,C1 编译器是最好的选择。

注意,C1 有两个版本——32b 和 64b。C2 只有 64b 版本。

JIT 编译器优化示例

以下示例展示了 JIT 编译器的优化:

对象情况下的 JIT 优化

考虑以下代码:

for(int i = 0 ; i <= 100; i++) {
   System.out.println(obj1.equals(obj2)); //两个对象
}

如果这段代码被解释,解释器会在每次迭代时推断 obj1 的类。这是因为 Java 中的每个类都有一个继承自 Object 类的 .equals() 方法,并且可以被覆盖。即使 obj1 在每次迭代时都是一个字符串,这种推断仍然会发生。

另一方面,实际上 JVM 会注意到每次迭代时 obj1 都是 String 类型,因此会直接生成对应 String 类的 .equals() 方法的代码。因此,无需查找,编译后的代码将运行得更快。

这种行为只有在 JVM 知道代码的行为时才可能发生。因此,它在编译某些代码段前会稍作等待。

原始值情况下的 JIT 优化

以下是另一个例子:

int sum = 7;
for(int i = 0 ; i <= 100; i++) {
   sum += i;
}

对于每个循环,解释器都会从内存中获取 sum 的值,加上 i,然后将其存回内存。内存访问是一个昂贵的操作,通常需要多个 CPU 周期。由于这段代码多次执行,它是一个热点。JIT 将编译这段代码并进行如下优化:

sum 的本地副本将存储在一个特定于特定线程的寄存器中。所有的操作都将对寄存器中的值进行,当循环完成时,将值写回到内存中。

如果有其他线程也在访问该变量怎么办?由于其他线程正在更新变量的本地副本,它们可能会看到旧值。此时需要线程同步。一个基本的同步原语是将 sum 声明为 volatile。现在,在访问变量之前,线程会刷新其本地寄存器并从内存中获取值;访问后,立即将值写入内存。

JIT 编译器所做的优化

以下是一些 JIT 编译器通常进行的一般优化:

  • 方法内联
  • 死代码消除
  • 调用站点优化的启发式
  • 常量折叠
上一章 下一章
阅读号二维码

关注阅读号

联系二维码

联系我们

© 2024 Yoagoa. All rights reserved.

粤ICP备18007391号

站点地图