Jvm之JIT优化详细解释

news2024/11/15 15:40:53

文章目录

  • 一、JIT 产生的背景
  • 二、HotSpot虚拟机内置JIT编译器
    • 1. Client Compiler
    • 2. Server Compiler
    • 3. 查看本地编译器模式
  • 三、常见热点探测技术
    • 1. 基于计数器的热点探测
    • 2. 基于采样的热点探测
      • 2.1 方法调用计数器
      • 2.2 回边计数器
  • 四、常见JIT优化手段
    • 1. 公共子表达式消除
    • 2. 方法内联
    • 3. 逃逸分析
      • 3.1 逃逸分析之标量替换
      • 3.2 逃逸分析之栈上分配
      • 3.3 逃逸分析之同步消除
  • 五、JIT优化可能引发的问题
    • 1. 提升JIT优化的效率
    • 2. 降低瞬时请求量

一、JIT 产生的背景

我们知道,将高级语言转换成计算机可识别的机器语言有两种方式,即编译和解释。尽管在Java中,代码需要编译成字节码才能执行,但字节码本身并不能直接在机器上执行。

因此,JVM内置了解释器(interpreter),在运行时对字节码进行解释,将其翻译成机器码,然后执行。

解释器的执行方式是边翻译边执行,因此执行效率较低。为了解决这个低效问题,HotSpot引入了JIT技术(即时编译)。

有了JIT技术之后,JVM仍然使用解释器进行解释执行。但是,当JVM发现某个方法或代码块在运行时频繁执行时,会将其标记为"热点代码"。然后JIT将部分热点代码翻译成本地机器相关的机器码,并进行优化,再将翻译后的机器码缓存起来供下次使用。

JVM之JIT优化

二、HotSpot虚拟机内置JIT编译器

HotSpot虚拟机内置了两个JIT编译器:Client Compiler和Server Compiler, 分别用于客户端和服务器端。在当前主流的HotSpot虚拟机中,默认采用解释器与其中一个编译器直接配合的方式工作。

当JVM执行代码时,并不会立即开始编译代码。首先,如果代码只会被执行一次,那么编译代码相对于将代码翻译成Java字节码来说是一种浪费。因为将代码翻译成字节码的过程比编译和执行代码的过程要快很多。其次,JVM在编译代码时会进行优化。当某个方法或循环被执行的次数越多,JVM就会对代码结构有更深入的了解,并在编译代码时进行相应的优化。

1. Client Compiler

Client Compiler(也称为C1编译器或Client JIT)主要优化启动速度和内存占用。它会在程序运行初期进行编译,以快速生成可执行代码,但对于性能优化的程度较低。

2. Server Compiler

Server Compiler(也称为C2编译器或Server JIT)则注重在运行时对代码进行更深层次的优化,以提高程序的执行效率。它会在程序运行过程中,通过动态分析和优化来生成高性能的机器码。

3. 查看本地编译器模式

如果想要查看机器上安装的JDK中JIT采用的是哪种模式,可以执行"java -version"命令。这个命令将显示JDK的版本信息,其中也包括了JIT编译器的模式。

java -version

编译器模式
图中显示的是自己本地机器上安装的JDK 1.8,并且 JIT 编译器的模式是Server Compiler。 然而,需要指出的是,无论是Client Compiler还是Server Compiler,解释器与编译器都是以混合模式配合使用的,即图中显示的是mixed mode。

三、常见热点探测技术

为了触发JIT编译,首先需要识别出热点代码。目前,主要使用热点探测(Hot Spot Detection)来实现热点代码的识别,其中有两种常见的方式。

1. 基于计数器的热点探测

其中一种是基于计数器的热点探测,通过对方法的调用次数进行计数,当达到一定阈值时,将该方法标记为热点代码。这种方式简单直接,适用于识别一些简单的热点场景。

2. 基于采样的热点探测

HotSpot虚拟机使用周期性检测各个线程的栈顶的方法来判断热点方法。如果某个方法经常出现在栈顶,就被认为是热点方法。这种方法的好处在于简单易懂,但缺点是无法精确确定一个方法的热度。此外,它容易受到线程阻塞或其他原因的干扰,从而影响热点探测的准确性。

在HotSpot虚拟机中,使用的是基于计数器的热点探测方法,因此为每个方法准备了两个计数器:方法调用计数器和回边计数器。

2.1 方法调用计数器

方法调用计数器,顾名思义,是用来记录一个方法被调用的次数的计数器。它会统计方法的调用次数,并当达到一定阈值时将该方法标记为热点代码。

2.2 回边计数器

回边计数器则是用来记录方法中的循环结构(如for或while循环)的运行次数的计数器。它会统计循环结构的迭代次数,通过迭代次数的多少来判断循环是否是热点代码。

这两个计数器的作用是为了帮助HotSpot虚拟机识别热点代码,从而触发JIT编译进行优化。方法调用计数器用于识别频繁调用的方法,回边计数器用于识别运行次数较多的循环结构。通过对这些热点代码的识别,可以提高程序的执行效率。

总的来说,HotSpot虚拟机使用方法调用计数器和回边计数器作为基于计数器的热点探测方法,以识别热点代码并进行优化,从而提高Java应用程序的性能。

四、常见JIT优化手段

1. 公共子表达式消除

公共子表达式消除是JVM JIT编译器的一种优化技术,用于减少重复计算,提高程序的执行效率。

公共子表达式是指在一个程序中多次出现的计算表达式,通过公共子表达式消除优化,可以将重复的计算合并为一次计算,减少不必要的计算开销。

以下是一个简单的示例代码,展示了公共子表达式的消除优化:

public class CommonSubexpressionEliminationDemo {
    public static void main(String[] args) {
        int a = 5;
        int b = 3;
        int c = a * b + 2; // 公共子表达式 a * b
        int d = a * b + 2; // 公共子表达式 a * b
        
        System.out.println(c);
        System.out.println(d);
    }
}

在上述代码中,变量c和d都进行了相同的计算表达式 a * b + 2,通过公共子表达式消除优化,JVM JIT编译器会将重复的计算合并为一次计算。

总结:

公共子表达式消除可以减少重复计算,提高程序的执行效率。
JVM JIT编译器会通过识别重复的计算表达式,并将其优化为一次计算。
使用公共子表达式消除优化可以减少不必要的计算开销,特别在循环中使用同样的表达式时效果更为明显。

2. 方法内联

方法内联是JVM JIT编译器的一种优化技术,用于减少方法调用的开销,提高程序的执行效率。

方法内联是指将某个方法的代码直接插入到调用该方法的地方,而不是通过方法调用的方式执行。这样可以减少方法调用的开销,包括栈帧的创建和销毁、参数传递等操作。

以下是一个简单的示例代码,展示了方法内联的优化:

public class MethodInliningDemo {
    public static void main(String[] args) {
        int a = 5;
        int b = 3;
        int c = add(a, b); // 方法调用
        int d = a + b; // 方法内联
        
        System.out.println(c);
        System.out.println(d);
    }
    
    public static int add(int a, int b) {
        return a + b;
    }
}

在上述代码中,变量c通过方法调用的方式计算结果,而变量d直接将方法的代码内联到调用处进行计算。

总结:

方法内联可以减少方法调用的开销,提高程序的执行效率。
JVM JIT编译器会通过识别适合内联的方法,并将其优化为直接插入到调用处执行。
使用方法内联优化可以减少方法调用的开销,特别是在频繁调用的方法中效果更为明显。
需要注意的是,过多的方法内联可能会导致代码膨胀,增加编译时间和内存消耗。因此,在使用方法内联时需要权衡代码的大小和性能的提升。

3. 逃逸分析

逃逸分析是JVM JIT编译器的一种优化技术,用于分析对象的作用域,确定对象是否会逃逸出方法的范围,从而对对象的内存分配进行优化。

逃逸分析的目的是找出那些不会逃逸出方法的对象,将它们分配在栈上而不是堆上,以减少垃圾回收的开销。

以下是一个简单的示例代码,展示了逃逸分析的优化:

public class EscapeAnalysisDemo {
    public static void main(String[] args) {
        User user = createUser("Alice"); // 对象逃逸
        
        System.out.println(user.getName());
    }
    
    public static User createUser(String name) {
        return new User(name); // 对象逃逸
    }
    
    static class User {
        private String name;
        
        public User(String name) {
            this.name = name;
        }
        
        public String getName() {
            return name;
        }
    }
}

在上述代码中,createUser方法中创建的User对象会逃逸出方法的范围,即被外部引用所使用。

总结:

  • 逃逸分析是JVM JIT编译器的一种优化技术,用于分析对象的作用域,确定对象是否会逃逸出方法的范围。

  • 逃逸分析的目的是找出那些不会逃逸出方法的对象,将它们分配在栈上而不是堆上,以减少垃圾回收的开销。

  • 逃逸分析可以减少堆的分配和垃圾回收的开销,提高程序的执行效率。

  • 需要注意的是,逃逸分析并不是一项绝对有效的优化技术,它只能在特定的场景下才能生效,而且对于大部分应用程序来说,堆的分配和垃圾回收开销并不是性能瓶颈,因此逃逸分析的作用有限。

  • 逃逸分析在JVM中是通过参数来进行控制的。在JDK7及以后的版本中,默认情况下逃逸分析是开启的。

以下是一些与逃逸分析相关的JVM参数:

  • -XX:+DoEscapeAnalysis:启用逃逸分析。默认情况下是开启的。
  • -XX:-DoEscapeAnalysis:禁用逃逸分析。
  • -XX:+PrintEscapeAnalysis:打印逃逸分析的相关信息。
    -XX:+EliminateLocks:通过逃逸分析来消除不必要的锁。
  • 需要注意的是,逃逸分析的效果是与具体的JVM实现相关的,不同的JVM可能对逃逸分析的支持和优化程度有所不同。因此,对于一些特定的场景,可能需要根据实际情况进行适当的调整和优化。

3.1 逃逸分析之标量替换

标量替换是逃逸分析的一种优化技术,它将对象拆解成独立的标量(单个的基本类型或对象引用),并将这些标量分别分配在栈上或寄存器中,从而避免了对象的创建和访问操作。

下面是一个简单的示例代码,用于演示标量替换的效果:

public class ScalarReplacementDemo {
    public static void main(String[] args) {
        long startTime = System.currentTimeMillis();

        for (int i = 0; i < 10000000; i++) {
            Point point = new Point(i, i); // 创建一个Point对象
            int sum = point.x + point.y; // 使用Point对象的属性进行计算
        }

        long endTime = System.currentTimeMillis();
        System.out.println("Time taken: " + (endTime - startTime) + "ms");
    }

    static class Point {
        int x;
        int y;

        public Point(int x, int y) {
            this.x = x;
            this.y = y;
        }
    }
}

在上述代码中,我们循环创建了10000000个Point对象,并对每个对象的属性进行了相加操作。如果逃逸分析开启且标量替换生效,JVM会将Point对象的属性x和y分别替换为两个独立的局部变量,并将它们分配在栈上,从而避免了对Point对象的创建和访问。

总结:

  • 标量替换是逃逸分析的一种优化技术,将对象拆解成独立的标量并在栈上或寄存器中分配。
  • 标量替换能够避免对象的创建和访问操作,从而提高程序的性能。
  • 要启用标量替换,需要确保逃逸分析开启,并且JVM在运行时会自动进行标量替换的优化。
  • 在编写代码时,可以通过适当的代码设计来帮助JVM进行标量替换优化,例如使用不可变对象或局部变量等。

3.2 逃逸分析之栈上分配

栈上分配是逃逸分析的另一种优化技术,它将某些对象的内存分配在栈上而不是堆上。栈上分配可以减少对象在堆上的分配和回收的开销,提高程序的性能。

下面是一个简单的示例代码,用于演示栈上分配的效果:

public class StackAllocationDemo {
    public static void main(String[] args) {
        long startTime = System.currentTimeMillis();

        for (int i = 0; i < 10000000; i++) {
            Point point = createPoint(i, i); // 创建一个Point对象,并返回其引用
            int sum = point.x + point.y; // 使用Point对象的属性进行计算
        }

        long endTime = System.currentTimeMillis();
        System.out.println("Time taken: " + (endTime - startTime) + "ms");
    }

    static Point createPoint(int x, int y) {
        return new Point(x, y);
    }

    static class Point {
        int x;
        int y;

        public Point(int x, int y) {
            this.x = x;
            this.y = y;
        }
    }
}

在上述代码中,我们循环创建了10000000个Point对象,并对每个对象的属性进行了相加操作。如果逃逸分析开启且栈上分配生效,JVM会将Point对象的内存分配在栈上而不是堆上,从而减少了堆上对象的分配和回收的开销。

总结:

  • 栈上分配是逃逸分析的一种优化技术,将某些对象的内存分配在栈上而不是堆上。
  • 栈上分配能够减少对象在堆上的分配和回收的开销,提高程序的性能。
  • 要启用栈上分配,需要确保逃逸分析开启,并且JVM在运行时会自动进行栈上分配的优化。
  • 在编写代码时,可以通过适当的代码设计来帮助JVM进行栈上分配优化,例如将对象的作用域限制在方法内部、使用局部变量等。

3.3 逃逸分析之同步消除

同步消除是逃逸分析的另一种优化技术,它通过分析代码中的同步操作,判断是否可以消除这些同步操作从而提高程序的性能。

下面是一个简单的示例代码,用于演示同步消除的效果:

public class SynchronizationEliminationDemo {
    public static void main(String[] args) {
        long startTime = System.currentTimeMillis();

        for (int i = 0; i < 10000000; i++) {
            synchronizedMethod();
        }

        long endTime = System.currentTimeMillis();
        System.out.println("Time taken: " + (endTime - startTime) + "ms");
    }

    static void synchronizedMethod() {
        synchronized (SynchronizationEliminationDemo.class) {
            // 同步块中的代码
        }
    }
}

在上述代码中,我们循环调用了10000000次synchronizedMethod方法,该方法包含了一个同步块。如果逃逸分析开启且同步消除生效,JVM会判断到synchronizedMethod方法没有逃逸到其他线程中,因此可以消除同步操作,从而提高程序的性能。

总结:

  • 同步消除是逃逸分析的一种优化技术,通过分析代码中的同步操作,判断是否可以消除这些同步操作从而提高程序的性能。
  • 同步消除的前提是逃逸分析开启,并且JVM能够确定同步操作没有逃逸到其他线程中。
  • 同步消除可以减少线程间的同步开销,提高程序的并发性能。
    在编写代码时,可以通过避免不必要的同步操作、合理设计对象的作用域等方式帮助JVM进行同步消除优化。

五、JIT优化可能引发的问题

一旦我们理解了JIT编译的原理,就会明白JIT优化是在运行时进行的,并且并非在Java进程刚启动时就能立即进行优化的。它需要一定的执行时间来确定哪些代码是热点代码。

因此,在JIT优化开始之前,所有的请求都需要经过解释执行,这个过程相对较慢。尤其是在应用的请求量较大时,这个问题会更加明显。在应用启动过程中,大量的请求涌入会导致解释器持续努力工作。

如果解释器对CPU资源占用较高,就会间接导致CPU和负载等指标飙升,进而降低应用的性能。这也是为什么在应用发布过程中,刚刚重启好的应用会出现大量超时问题的原因。

随着请求不断增加,JIT优化会被触发,这使得后续的热点请求不再需要解释执行,而是直接运行JIT优化后缓存的机器码。

✨主要有两种解决思路:✨

1. 提升JIT优化的效率

一种方法是借鉴阿里研发的JDK Dragonwell,它相比于OpenJDK提供了一些专有特性,其中包括JwarmUp技术。该技术通过记录上一次Java应用运行时的编译信息到文件中,在下次应用启动时读取该文件,实现提前完成类加载、初始化和方法编译,跳过解释阶段,直接执行编译好的机器码。

2. 降低瞬时请求量

在应用刚启动时,通过调节负载均衡,逐渐增加流量,让应用在小流量下触发JIT优化,等优化完成后再逐渐增加流量。

这种方法类似于缓存预热的思想。在应用刚启动时,不要立即将大量流量分发给它,而是先分配一小部分流量,通过这部分流量触发JIT优化。等优化完成后,再逐渐增加流量。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

vue登录验证码组件,前端验证

效果图 点击可以切换验证码 自定义组件 <template><div class"s-canvas"><canvas id"s-canvas" :width"contentWidth" :height"contentHeight"></canvas></div> </template> <script> e…

左偏树\可并堆

https://www.luogu.com.cn/problem/P3377 作用&#xff1a;可并堆 形态&#xff1a;堆满二叉树 即左节点最小深度大于等于右节点最小深度 合并过程&#xff1a;

谷歌浏览器响应Failed to load response data: no resource with given identifer found

1、如下问题展示&#xff0c;这个是新版谷歌浏览器的不知道啥时候出现的问题&#xff0c;以前旧版未出现过&#xff0c;所以降版本浏览器可能可以 2、但是&#xff0c;博主的方法是换一个浏览器&#xff0c;换成edge就没问题了&#xff0c;由于用习惯了谷歌&#xff0c;所以这…

算法竞赛入门【码蹄集新手村600题】(MT1220-1240)C语言

算法竞赛入门【码蹄集新手村600题】(MT1220-1240&#xff09;C语言 目录MT1221 分数的总和MT1222 等差数列MT1223 N是什么MT1224 棋盘MT1225 复杂分数MT1226 解不等式MT1227 宝宝爬楼梯MT1228 宝宝抢糖果MT1229 搬家公司MT1230 圆周率MT1231圆周率IIMT1232 数字和MT1233 数字之…

OLED透明屏:在广告领域中的应用,为品牌注入更强的视觉冲击

OLED透明屏作为一项引人注目的技术创新&#xff0c;其独特的透明度和高清晰度为各行各业带来了全新的展示和创意空间。 本文将详细介绍其透明度、高对比度、超薄柔性设计以及强大的颜色表现力&#xff0c;并探讨其在零售、汽车和建筑等领域的应用前景。 一、透明度&#xff1a…

大数据Flink(六十七):SQL Table 简介及运行环境

文章目录 SQL & Table 简介及运行环境 一、​​​​​​​​​​​​​​简介 二、案例

Ansible 生成主机文件

生成主机文件 将一个初始模板文件从 http://materials/hosts.j2 下载到 /home/greg/ansible 完成该模板&#xff0c;以便用它生成以下文件&#xff1a;针对每个清单主机包含一行内容&#xff0c;其格式与 /etc/hosts 相同 创建名为 /home/greg/ansible/hosts.yml 的 playboo…

谁才是Python最强IDE?

本文概述了大量优秀的 Python IDE 和其他工具。决定使用哪一种工具取决于工具的便利性和对工具的熟练程度。当然&#xff0c;也有大家都喜欢的工具&#xff08;如 PyCharm&#xff09;&#xff0c;但是你可以多尝试几种工具&#xff0c;看看哪种最适合你。 每个人都知道这一点…

张量分解--CP、Tucker分解

目录 符号和准备工作 张量 实例 Fiber Slice norm 张量内积 秩一张量 回顾一下SVD分解 对角张量 张量的矩阵化与向量化 Kolda水平展开 具体例子 mode-n product CP分解 张量CP分解的定义 向量的外积 CP秩 张量CP分解的理解 Tucker分解 线性变化改变向量维度…

再见 Xshell替代工具Tabby

替代Xshell 之前经常使用Xshell来操作Linux虚拟机&#xff0c;基本上是够用了。但是Xshell免费使用只供非商业用途&#xff0c;而且如果你想用FTP来进行文件传输的话&#xff0c;还需单独下载Xftp。 无意中发现了另一款开源的终端工具Tabby&#xff0c;它直接集成了SFTP功能&…

一个简单的web应用程序的创建

一个简单的web应用程序的创建 1、数据库设计与创建1.1、数据库系统1.2、Navicat Premium1.3、Power Designer2、使用maven创建SpringBoot项目2.1、配置maven2.2、安装idea2.3、使用idea创建maven项目2.4、根据需要配置pom.xml文件、配置项目启动相关的文件2.5、写SpringBoot项目…

Redis之stream类型解读

目录 基本介绍 数据结构 消息 消费组 消费者 基本使用命令 概述 xadd 命令 xtrim 命令 xdel 命令 xlen 命令 xrange 命令 xread 命令 xgroup 命令 xreadgroup 命令 xack 命令 基本介绍 Redis stream&#xff08;流&#xff09;是一种数据结构&#xff0c;其…

【dasctf】easy_log

base解码可得压缩包密码 二分法盲注 import urllib.parse,re with open(raccess.log,r) as f:logf.readlines() dict1{} count0 #判断逻辑&#xff0c;最后一个fasle则取自身&#xff1b;最后一个为true则加1&#xff1b; for each in log:resre.findall(rflag\),(\d),1\)\)…

CEdit 选中文字实时更新到另一个控件中

有时候&#xff0c;我们会遇到需求&#xff0c;软件中需要让选中一个CEdit控件中的文字实时更新到另一个控件中&#xff0c;实现效果如下所示&#xff1a; 代码如下&#xff1a; BOOL CEditDemoDlg::PreTranslateMessage(MSG* pMsg) { CEdit* pOldEdit (CEdit*)GetDlgIte…

Linux驱动开发一、RK3568把hello编译到Linux内核中运行。‘rk_vendor_read’未定义的引用

1、在字符设备目录下建立hello目录 ~/Linux/rk356x_linux/kernel/drivers/char/hello 2、进入hello目录&#xff0c;新建hello.c、Makefile、Kconfig三个文件 3、Kconfig是打开make menuconfig配置界面是后的选项&#xff0c;这Kconfig是在字符设备下的。 config HELLOtrist…

哪些测试仪器可以用于检测静电中和设备的性能

静电设备性能测试通常需要使用一些专门的仪器来进行。以下是一些常见的静电设备性能测试仪器&#xff1a; 1. 静电电压测试仪&#xff1a;用于测量物体表面的静电电压。它通常可以测量正负电压&#xff0c;并具有高精度和快速响应的特点。 2. 静电电荷仪&#xff1a;用于测量物…

探讨uniapp的页面问题

1 新建页面 uni-app中的页面&#xff0c;默认保存在工程根目录下的pages目录下。 每次新建页面&#xff0c;均需在pages.json中配置pages列表&#xff1b; 未在pages.json -> pages 中注册的页面&#xff0c;uni-app会在编译阶段进行忽略。pages.json的完整配置参考&am…

新能源发电变流关键技术开发

新能源发电变流技术开发 文章目录 新能源发电变流技术开发前言新能源并网电力质量控制电能储存风能、光伏等行业新能源汽车新型城区的能源建设因此,可以说,新能源发电变流技术在电力系统与现代新能源行业中具有重要的应用地位,它对提高新能源发电的可靠性、电力质量、储能等…

基于Java+SpringBoot+Vue前后端分离常规应急物资管理系统设计和实现

博主介绍&#xff1a;✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、Java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专…

理解图傅里叶变换和图卷积

图神经网络&#xff08;GNN&#xff09;代表了一类强大的深度神经网络架构。在一个日益互联的世界里&#xff0c;因为信息的联通性&#xff0c;大部分的信息可以被建模为图。例如&#xff0c;化合物中的原子是节点&#xff0c;它们之间的键是边。 图神经网络的美妙之处在于它们…