首页 > 其他分享 >面试官:什么是JIT、逃逸分析、锁消除、栈上分配和标量替换?

面试官:什么是JIT、逃逸分析、锁消除、栈上分配和标量替换?

时间:2024-01-31 10:33:28浏览次数:26  
标签:面试官 代码 编译器 栈上 JIT JVM 标量 优化 分配

JIT、逃逸分析、锁消除、栈上分配和标量替换等都属于 JVM 的优化手段,JVM 优化手段是指在运行 Java 程序时,通过对字节码的编译和执行过程进行优化,以提升程序的性能和效率。

JVM 优化手段主要有以下几个:

  1. JIT(Just-In-Time,即时编译):是一种在程序运行时将部分热点代码编译成机器代码的技术,以提高程序的执行性能的机制。
  2. 逃逸分析:用于确定对象动态作用域是否超过当前方法或线程,通过逃逸分析,编译器可以决定一个对象的作用范围,从而进行相应的优化,但确定对象没有逃逸时,可以进行以下优化:
    1. 栈上分配:如果编译器可以确定一个对象不会逃逸出方法,它可以将对象分配在栈上而不是堆上。在栈上分配的对象在方法返回后就会自动销毁,不需要进行垃圾回收,提高了程序的执行效率。
    2. 锁消除:如果对象只在单线程中使用,那么同步锁可能会被消除,提高程序性能。
    3. 标量替换:将原本需要分配在堆上的对象拆解成若干个基础数据类型存储在栈上,进一步减少堆空间的使用。
  3. 字符串池(String Pool)优化:JVM 通过共享字符串常量,重用字符串对象,以减少内存占用和提升字符串操作的性能。

1.JIT优点和热点代码

JIT 优点包含以下两个:

  1. 性能优化:由于编译成本地机器代码,程序的执行速度通常比解释性执行或预编译的代码要快得多。
  2. 平台无关性:JIT 编译器可以根据不同的硬件平台生成不同的机器代码,使得相同的程序可以在不同的计算机上运行,而无需重新编写。

什么是热点代码?

在 HotSpot 虚拟机中,热点代码(Hot Code)是指那些被频繁执行的代码。
热点代码的执行次数在不同的 JDK 版本和不同的 JVM 中是不同的,例如,它在 JDK 21 Client 模式下为 1500 次,Server 模式下为 10000 次,这个值可以通过 JVM 参数设置。
通常来说,热点代码的识别基于以下两种策略:

  1. 方法调用次数:当一个方法被调用一定次数后,会被视为热点代码并触发即时编译。这个次数在不同 JDK 版本中可能有所变化,并且可以通过 JVM 参数 -XX:CompileThreshold 进行设置。
  2. 回边计数:对于循环体等热点区域,通过统计从循环体返回到循环条件检查点的次数(即回边次数),达到一定次数也会触发即时编译。同样,这个阈值也可以通过 JVM 参数 -XX:OnStackReplacePercentage 进行设置。回边计数器有一个计算公式【回边计数器阈值=方法调用计数器阈值 * (OnStackReplacePercentage - InterpreterProfilePercentage)】,通过计算,在 JDK 21 Server 模式下,虚拟机回边计数器的阈值为 10700【10000*(140-33)】。

可以使用 java -XX:+PrintFlagsFinal -version 命令查看 JVM 默认配置。

2.栈上分配 VS 标量替换

栈上分配和标量替换是编译器的两种优化技术,它们虽然有一些相似之处,但并不完全相同。

  • 栈上分配(Stack Allocation):一种优化技术,它将对象分配在栈上而不是堆上。这种技术适用于编译器可以确定对象不会逃逸出方法,并且对象的生命周期在方法内部结束的情况。通过在栈上分配对象,可以避免在堆上进行内存分配和垃圾回收的开销,从而提高程序的性能和内存使用效率。
  • 标量替换(Scalar Replacement):与栈上分配类似,也是一种优化技术。它将一个复杂对象拆分成独立的成员变量,使其成为基本类型或基本类型数组的局部变量。这种技术适用于编译器可以确定对象的成员变量不会逃逸的情况。标量替换可以提供更细粒度的控制,使得编译器可以对独立的成员变量进行更精细的优化,例如寄存器分配和代码优化。

也就是说栈上分配,只是将对象从堆上分配到栈上了;而标量替换是更进一步的优化技术,将对象拆解成基本类型分配到栈上了。

2.1 锁消除代码演示

锁消除(Lock Elimination)也叫做同步消除,是一种编译器优化技术,它可以消除对于变量的不必要的锁定操作。锁消除的目的是减少锁的开销,提高程序的性能。
例如以下代码:

public void method() {
    Object lock = new Object();
    synchronized(lock){
        System.out.println("www.javacn.site");
    }
}

而锁消除之后的代码如下:

public void method(){
    System.out.println("www.javacn.site");
}

2.2 标量替换代码演示

未优化前的代码如下:

private static class Point {
    private int x;
    private int y;
}
public static void main(String[] args) {
    Point point = createPoint(10, 20);
    int sum = point.x + point.y;
    System.out.println("Sum: " + sum);
}
private static Point createPoint(int x, int y) {
    Point point = new Point();
    point.x = x;
    point.y = y;
    return point;
}

标量替换优化后的代码如下:

public static void main(String[] args) {
    int x = 10;
    int y = 20;
    int sum = x + y;
    System.out.println("Sum: " + sum);
}

通过逃逸分析的优化能够减少垃圾回收的压力、减少内存分配和释放带来的性能损耗,并且有可能减少对锁的依赖,以及实现标量替换等,从而整体上提升了应用程序的运行效率。

课后思考

Java 为什么不把所有代码提前都编译成二进制的机器码呢?这样岂不是运行更快?新 Java 虚拟机 GraalVM 中的 AOT 和 JIT 又有什么区别呢?

本文已收录到我的面试小站 www.javacn.site,其中包含的内容有:Redis、JVM、并发、并发、MySQL、Spring、Spring MVC、Spring Boot、Spring Cloud、MyBatis、设计模式、消息队列等模块。

标签:面试官,代码,编译器,栈上,JIT,JVM,标量,优化,分配
From: https://www.cnblogs.com/vipstone/p/17998691

相关文章

  • 面试官:请说一下Mysql中count(1)、count(*)以及count(列)的区别?
    近期在Review项目代码时,发现同事们在查询MySQL行数时存在多样的方式,有的使用COUNT(1),有的用COUNT(id),还有人选择了COUNT(*)。这混杂的选择引发了我的思考。当然这三种count的方式也是众说纷纭,其中最大的分歧点就是COUNT(*)和COUNT(1)查询性能上,有人觉得COUNT(*)需要转换为COUN......
  • 面试官:说一下零拷贝技术的实现原理?
    零拷贝(Zero-copy)技术是一种计算机操作系统中用于提高数据传输效率的优化策略。在传统的数据传输过程中,需要将数据从一个缓冲区拷贝到另一个缓冲区,然后再传输给目标。这涉及到多次的CPU和内存之间的数据拷贝操作,会消耗CPU的时间和内存带宽。而零拷贝技术通过直接共享数据的内......
  • 面试官:小伙子来说一说Java中final关键字,以及它和finally、finalize()有什么区别?
    写在开头面试官:“小伙子,用过final关键字吗?”我:“必须用过呀”面试官:“好,那来说一说你对这个关键字的理解吧,再说一说它与finally、finalize()的区别”我:“好嘞!”final中文释义:最终的,最后的;在Java中作为关键字的一种,被用来修饰变量、方法、类,final语义是不可改变的。final......
  • 面试官:Redis持久化能关吗?怎么关?
    数据持久化是指将数据从内存中,保存到磁盘或其他持久存储介质的过程,这样做的目的是为了保证数据不丢失。而Redis的持久化功能默认是开启的,这样做的目的也是为了保证程序的稳定性(防止缓存雪崩、缓存击穿等问题)和数据不丢失。Redis持久化能关吗?怎么关?Redis持久化默认是开启的,......
  • day40 如何运维管理超1k+node节点 - 站在面试官角度谈面试 (13-14)
    13、如何运维管理超1k+node节点(四节)一、数据背景100,000+Pods1300+Nodes3集群(单:11Master+17ETCD)ToC服务行业二、瓶颈问题Apiserver调度,延迟问题;Controller不能及时从APIServer感知到最新的变化,处理的延时较高;Scheduler延迟高、吞吐低,无法适应业务日常需求;ET......
  • 面试官:SpringBoot如何实现缓存预热?
    缓存预热是指在SpringBoot项目启动时,预先将数据加载到缓存系统(如Redis)中的一种机制。那么问题来了,在SpringBoot项目启动之后,在什么时候?在哪里可以将数据加载到缓存系统呢?实现方案概述在SpringBoot启动之后,可以通过以下手段实现缓存预热:使用启动监听事件实现缓存预热。使......
  • 面试官:分库分表后如何生成全局ID?
    分库分表后就不能使用自增ID来作为表的主键了,因为数据库自增ID只适用于单机环境,但如果是分布式环境,是将数据库进行分库、分表或数据库分片等操作时,那么数据库自增ID就会生成重复ID,从而导致业务查询上的问题。所以此时,可以使用UUID或雪花ID来作为全局主键ID。1.UUID作为......
  • 面试官:如何保证本地缓存的一致性?
    有人可能看到“本地缓存”这四个字就会觉得不屑,“哼,现在谁还用本地缓存?直接用分布式缓存不就完了嘛”。然而,这就像你有一辆超级豪华的房车一样,虽然它空间很大,设备很全,但你去市中心的时候,依然会开小轿车一样,为啥?好停车啊!所以,不同的缓存类型是有不同得使用场景的。并且,为了防止缓存雪......
  • Jax框架:通过显存分析判断操作是否进行jit编译
    相关:https://jax.readthedocs.io/en/latest/device_memory_profiling.html代码:importjaximportjax.numpyasjnpimportjax.profilerdeffunc1(x):returnjnp.tile(x,10)*0.5deffunc2(x):y=func1(x)returny,jnp.tile(x,10)+1x=jax.random.......
  • 面试官:请聊一聊String、StringBuilder、StringBuffer三者的区别
    面试官:“小伙子,在日常的写代码过程中,使用过String,StringBuilder和StringBuffer没?”我:“用过的呀!”面试官:“那你就来聊一聊,他们之间有什么区别,不同场景下如何选择吧”我:“好嘞!”在Java的开发过程中,使用频率最高的就是String字符串,但由于在字符串存储和拼接的过程中,涉及到很多场......