Jvm之JIT优化详细解释

这篇具有很好参考价值的文章主要介绍了Jvm之JIT优化详细解释。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、JIT 产生的背景

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

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

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

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

jit优化,《Jvm》专栏,jvm

二、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

jit优化,《Jvm》专栏,jvm
图中显示的是自己本地机器上安装的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优化。等优化完成后,再逐渐增加流量。文章来源地址https://www.toymoban.com/news/detail-687476.html

到了这里,关于Jvm之JIT优化详细解释的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • JIT VS AOT

    JIT,即Just-in-time,动态(即时)编译, 边运行边编译 ;AOT,Ahead Of Time, 指运行前编译 ,是两种程序的编译方式 理解 jit、aot 程序主要有两种运行方式:静态编译与动态解释。 静态编译的程序在执行前全部被翻译为机器码,通常将这种类型称为AOT (Ahead of time)即 “提前编译”

    2024年02月08日
    浏览(31)
  • Performance Improvements in .NET 8 -- JIT部分翻译

    相关视频 动态PGO 在本文中,我包括微基准测试以突出讨论的各个方面。其中大部分基准测试都是使用BenchmarkDotNet v0.13.8实现的,除非另有说明,否则每个基准测试都有一个简单的设置。 要跟随本文,首先确保已安装.NET 7和.NET 8。对于本文,我使用了.NET 8 Release Candidate (8.0.0

    2024年02月06日
    浏览(34)
  • Python高效之JIT(Just-in-Time)

    目录 JIT是什么(Just-in-Time) JIT有什么用 JIT示例 矩阵乘法 图像处理 Python可能是简洁和表达力的代名词,但有时你渴望一些更强大的东西。这就是即时编译(JIT)编译器的作用,这是Python世界中的一颗隐藏的明珠,可以显著提升你的代码性能。 JIT代表\\\"Just-in-Time\\\",是一种即时

    2024年01月19日
    浏览(31)
  • 独家首发!openEuler 主线集成 LuaJIT RISC-V JIT 技术

    RISC-V SIG 预期随主线发布的 openEuler 23.09 创新版本会集成 LuaJIT RISC-V 支持。本次发版将提供带有完整 LuaJIT 支持的 RISC-V 环境并带有相关软件如 openResty 等软件的支持。 随着 RISC-V SIG 主线推动工作的进展,LuaJIT 和相关软件在 RISC-V 架构下的支持也被合入到 openEuler 主线代码中。这

    2024年02月10日
    浏览(29)
  • Redis 专栏、JVM 专栏、RocketMQ 专栏、ZooKeeper 专栏文章导读

    欢迎关注公众号(通过文章导读关注:【11来了】),及时收到 AI 前沿项目工具及新技术 的推送 发送 资料 可领取 深入理解 Redis 系列文章结合电商场景讲解 Redis 使用场景 、 中间件系列笔记 和 编程高频电子书 ! 文章导读地址:点击查看文章导读! 感谢你的关注! 下边这

    2024年02月02日
    浏览(37)
  • Python 潮流周刊#34:Python 3.13 的 JIT 方案又新又好

    △△请给“ Python猫 ”加星标 ,以免错过文章推送 你好,我是猫哥。这里每周分享优质的 Python、AI 及通用技术内容,大部分为英文。本周刊开源,欢迎投稿[1]。另有电报频道[2]作为副刊,补充发布更加丰富的资讯,欢迎关注。 1、简单聊聊 Python 3.13 的 JIT 方案[3] Python 3.13 将

    2024年02月20日
    浏览(38)
  • 使用@jit(nopython=True)装饰器报错:module ‘numpy‘ has no attribute ‘typeDict

    是因为numpy和scipy库之间存在版本冲突或不兼容的问题,或者其他与numpy有关的包导致了这个错误。

    2024年02月15日
    浏览(30)
  • 通俗解释 JVM CAS 机制

    JVM 的 CAS,即 Compare And Swap,是一种并发编程中常用的保证原子性的技术。 通俗地讲,CAS 用于在多线程环境下对共享变量的值进行原子操作,即多个线程同时访问同一个共享变量,但只有一个线程能修改这个变量的值,并且这个操作是原子的,不会被其他线程打断。 具体来说

    2023年04月10日
    浏览(34)
  • java jvm TLAB是什么?指针碰撞解释,空闲列表解释

    1.  对象的创建:                         当 Java 虚拟机遇到一条字节码 new 指令时,首先将去检查这个指令的参数是否能在常量池中定位到 一个类的符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化过。如果没有,那 必须先执行相应的类加

    2024年02月12日
    浏览(34)
  • JVM执行引擎——为什么Java是半编译半解释语言

            起初设计者的初衷是将字节码文件翻译为机器语言的指令来执行即可,就诞生了解释器。但是采用一行行来解释的 效率比较低 ,JIT编译器会将编译后的机器码做一个缓存的操作,放在方法区的JIT代码缓存中,是否需要启用JIT编译器直接将字节码编译为机器码,则

    2024年02月15日
    浏览(49)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包