Java面试题之关于JIT编译器的那些个优化项

简介: 目录一、关于解释器和编译器1、概述2、JIT(Just In Time)编译器二、JIT优化之锁粗化 & 锁消除三、JIT优化之逃逸分析 & 栈上分配 & 同步消除 & 标量替换1、概念描述2、逃逸分析代码示例3、逃逸分析失效示例四、JIT优化之方法内联(Inline Method)1、概述2、方法内联示例3、方法内联的优点

目录

一、关于解释器和编译器

1、概述

2、JIT(Just In Time)编译器

二、JIT优化之锁粗化 & 锁消除

三、JIT优化之逃逸分析 & 栈上分配 & 同步消除 & 标量替换

1、概念描述

2、逃逸分析代码示例

3、逃逸分析失效示例

四、JIT优化之方法内联(Inline Method)

1、概述

2、方法内联示例

3、方法内联的优点

一、关于解释器和编译器



1、概述

Java到底是一门解释型语言还是编译型语言呢?这取决于Java虚拟机,现在主流的商用虚拟机如HotSpot,都同时包含解释器与编译器,我们可以认为Java即是一门解释型语言,也是一门编译型语言。


关于解释器与编译器的优势:


解释器:当程序需要迅速启动和执行的时候,解释器可以首先发挥作用,省去编译的时间,立即执行。

编译器:程序运行后,随着时间的推移,编译器逐渐发挥作用,把越来越多的代码编译成本地机器码,获取更高的执行效率。


2、JIT(Just In Time)编译器

当虚拟机发现某个方法或代码块的运行特别频繁时,就会把这些代码认定为“热点代码“。为了提高热点代码的执行效率,虚拟机会把这些代码编译成与本地平台相关的机器码,并进行各种层次的优化,完成这个任务的编译器称为即时编译器(Just In Time Compiler)。


备注:为什么叫HotSpot虚拟机呢?字面意思就是热点虚拟机,其中的一部分功能就是会对热点代码进行优化。

二、JIT优化之锁粗化 & 锁消除



锁粗化锁消除都是针对synchronized平台级锁所做的优化,具体可参考:Java面试题之synchronized关键字原理以及锁相关

三、JIT优化之逃逸分析 & 栈上分配 & 同步消除 & 标量替换



1、概念描述

逃逸分析:所谓逃逸分析简单来说就是,分析创建的对象除了在线程栈中能访问,还能不能被其它线程线程所引用,用于确定创建的对象到底在堆中分配还是栈上分配。


栈上分配:我们都知道对象一般都是在堆上分配,如果逃逸分析发现对象只能在线程栈中访问,则会直接在栈上分配。


同步消除:如果逃逸分析发现一个变量无法被其它线程访问,那这个变量的读写就不会有竞争,对这个变量实施的同步措施也就可以消除掉。比如不保存对象头的Mark Word信息。


标量替换:把对象拆散,将其使用到的成员变量恢复原始类型来访问。如果逃逸分析发现对象只能在当前线程栈中可访问,且对象可以被拆散,那么程序可能不创建这个对象,而是直接在栈上分配成员变量。


备注:所谓标量是指一个数据已经无法再分解成更小的数据来表示了,比如原始数据类型等,都不能再分解,可以称之为标量。如果一个数据可以再分解,我们称作为聚合量,Java中的对象就是最典型的聚合量。

2、逃逸分析代码示例

@Data
class User {
 private Integer id;
 private String name;
}
/**
 * 关闭逃逸分析JVM参数:-XX:-DoEscapeAnalysis
 */
public class EscapeAnalysisExample {
 public static void createUser() {
  User user = new User();
  user.setId(1);
  user.setName("Nick");
 }
 public static void main(String[] args) {
  long start = System.currentTimeMillis();
  for (int count = 0; count < 1024 * 1024 * 50; count++) {
   createUser();
  }
  long end = System.currentTimeMillis();
  System.out.println("耗时为: " + (end - start) + " ms");
 }
}
  • 默认逃逸分析是开启的,运行程序的执行时间为:5ms
  • 加上虚拟机参数-XX:-DoEscapeAnalysis,关闭逃逸分析,运行程序的执行时间为:267ms

3、逃逸分析失效示例

@Data
class User {
 private Integer id;
 private String name;
 private List<String> hobbies = new ArrayList<>();
}


开启逃逸分析,当我们在User类中加上一行代码后,再次执行程序,发现程序执行时间又变成了277ms。

为什么程序的执行时间又会变慢呢?


我们都知道,线程栈的大小是有限的,由虚拟机参数-Xss决定。由于动态集合里无法确定有多少个对象,会占用多少内存,所以此时创建的对象是在堆上分配,而不是在栈上分配。


四、JIT优化之方法内联(Inline Method)



1、概述

关于方法内联的介绍我们在Java官方提供的文档里可以看到,运行时机器码生成器或者优化器会内联final修饰的方法,把方法调用替换成方法体里的代码。6d18aea07b29494ebb1ed0bb91e0f6d2.png

备注:官方文档地址可参考Java语言规范

疑问:是不是只有final方法才有可能在运行时优化被内联呢?


其实并不是的,final方法可以被内联是因为final方法不能被重写,在编译时即可以确定。

除了final方法,非虚方法可以直接被内联,这些方法可以在解析阶段确定唯一的版本。

遇到虚方法,如果该方法只有一个版本(Ps:由于多态可能会有多个版本),也可以进行内联。如果该方法有多个版本,使用内联缓存完成方法内联。

非虚方法定义:静态方法、私有方法、实例构造器、父类方法4类。


虚方法定义:实例方法都是虚方法。


备注:更多关于内联缓存的概念请参考周志明的《深入了解Java虚拟机第2版》。

2、方法内联示例

class Point {
 int x, y;
 void move(int dx, int dy) {
  x += dx;
  y += dy;
 }
}
/**
 * 方法内联示例
 * @author 刘亚楼
 * @date 2022/6/29
 */
public class MethodInlineExample {
 public static void main(String[] args) {
  Point[] points = new Point[1000];
  for (int count = 0; count < points.length; count++) {
   points[count] = new Point();
   points[count].move(count, points.length - 1 - count);
  }
 }
}

上面main方法里的代码会被替换成如下:

public static void main(String[] args) {
 Point[] points = new Point[1000];
 for (int count = 0; count < points.length; count++) {
  points[count] = new Point();
  points[count].x += count;
  points[count].y += points.length - 1 - count;
 }
}

我们都知道方法在调用时会创建一个栈帧(Stack Frame),栈帧中会保存局部变量表,操作数栈,方法出口等信息,每一个方法从调用到执行完成的过程,就代表着一个栈帧在虚拟机栈中入栈到出栈的过程。


通过方法内联,可以减少栈帧的创建,方法调用入栈和出栈的流程,提升程序运行效率。


相关文章
|
29天前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
67 2
|
16天前
|
监控 算法 Java
Java虚拟机(JVM)垃圾回收机制深度剖析与优化策略####
本文作为一篇技术性文章,深入探讨了Java虚拟机(JVM)中垃圾回收的工作原理,详细分析了标记-清除、复制算法、标记-压缩及分代收集等主流垃圾回收算法的特点和适用场景。通过实际案例,展示了不同GC(Garbage Collector)算法在应用中的表现差异,并针对大型应用提出了一系列优化策略,包括选择合适的GC算法、调整堆内存大小、并行与并发GC调优等,旨在帮助开发者更好地理解和优化Java应用的性能。 ####
25 0
|
23天前
|
存储 算法 Java
Java内存管理深度剖析与优化策略####
本文深入探讨了Java虚拟机(JVM)的内存管理机制,重点分析了堆内存的分配策略、垃圾回收算法以及如何通过调优提升应用性能。通过案例驱动的方式,揭示了常见内存泄漏的根源与解决策略,旨在为开发者提供实用的内存管理技巧,确保应用程序既高效又稳定地运行。 ####
|
16天前
|
存储 监控 小程序
Java中的线程池优化实践####
本文深入探讨了Java中线程池的工作原理,分析了常见的线程池类型及其适用场景,并通过实际案例展示了如何根据应用需求进行线程池的优化配置。文章首先介绍了线程池的基本概念和核心参数,随后详细阐述了几种常见的线程池实现(如FixedThreadPool、CachedThreadPool、ScheduledThreadPool等)的特点及使用场景。接着,通过一个电商系统订单处理的实际案例,分析了线程池参数设置不当导致的性能问题,并提出了相应的优化策略。最终,总结了线程池优化的最佳实践,旨在帮助开发者更好地利用Java线程池提升应用性能和稳定性。 ####
|
18天前
|
Java 程序员
Java社招面试题:& 和 && 的区别,HR的套路险些让我翻车!
小米,29岁程序员,分享了一次面试经历,详细解析了Java中&和&&的区别及应用场景,展示了扎实的基础知识和良好的应变能力,最终成功获得Offer。
48 14
|
8天前
|
存储 Java
Java 11 的String是如何优化存储的?
本文介绍了Java中字符串存储优化的原理和实现。通过判断字符串是否全为拉丁字符,使用`byte`代替`char`存储,以节省空间。具体实现涉及`compress`和`toBytes`方法,前者用于尝试压缩字符串,后者则按常规方式存储。代码示例展示了如何根据配置决定使用哪种存储方式。
|
28天前
|
存储 缓存 算法
面试官:单核 CPU 支持 Java 多线程吗?为什么?被问懵了!
本文介绍了多线程环境下的几个关键概念,包括时间片、超线程、上下文切换及其影响因素,以及线程调度的两种方式——抢占式调度和协同式调度。文章还讨论了减少上下文切换次数以提高多线程程序效率的方法,如无锁并发编程、使用CAS算法等,并提出了合理的线程数量配置策略,以平衡CPU利用率和线程切换开销。
面试官:单核 CPU 支持 Java 多线程吗?为什么?被问懵了!
|
1月前
|
存储 算法 Java
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
本文详解自旋锁的概念、优缺点、使用场景及Java实现。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
|
17天前
|
存储 算法 Java
Java 内存管理与优化:掌控堆与栈,雕琢高效代码
Java内存管理与优化是提升程序性能的关键。掌握堆与栈的运作机制,学习如何有效管理内存资源,雕琢出更加高效的代码,是每个Java开发者必备的技能。
45 5
|
15天前
|
存储 监控 算法
Java虚拟机(JVM)垃圾回收机制深度解析与优化策略####
本文旨在深入探讨Java虚拟机(JVM)的垃圾回收机制,揭示其工作原理、常见算法及参数调优方法。通过剖析垃圾回收的生命周期、内存区域划分以及GC日志分析,为开发者提供一套实用的JVM垃圾回收优化指南,助力提升Java应用的性能与稳定性。 ####