Jvm之JIT优化详细解释

文章目录

  • 一、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.mfbz.cn/a/90609.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

NGINX的速率限制(限流)

NGINX 的速率限制&#xff08;限流&#xff09; NGINX最有用但经常被误解和配置错误的功能之一是限流。它允许您限制用户在给定时间段内可以发出的HTTP请求量。 限流可以用于安全目的&#xff0c;例如减慢暴力破解密码的攻击。它可以通过限制请求速率为真实用户的典型值来帮助…

OpenAI推出GPT-3.5Turbo微调功能并更新API;Midjourney更新局部绘制功能

&#x1f989; AI新闻 &#x1f680; OpenAI推出GPT-3.5Turbo微调功能并更新API&#xff0c;将提供GPT-4微调功能 摘要&#xff1a;OpenAI宣布推出GPT-3.5Turbo微调功能&#xff0c;并更新API&#xff0c;使企业和开发者能够定制ChatGPT&#xff0c;达到或超过GPT-4的能力。通…

c语言每日一练(11)

前言&#xff1a;每日一练系列&#xff0c;每一期都包含5道选择题&#xff0c;2道编程题&#xff0c;博主会尽可能详细地进行讲解&#xff0c;令初学者也能听的清晰。每日一练系列会持续更新&#xff0c;暑假时三天之内必有一更&#xff0c;到了开学之后&#xff0c;将看学业情…

C#-集合小例子

目录 背景&#xff1a; 过程: 1.添加1-100数: 2.求和: 3.平均值: 4.代码:​ 总结: 背景&#xff1a; 往集合里面添加100个数&#xff0c;首先得有ArrayList导入命名空间&#xff0c;这个例子分为3步&#xff0c;1.添加1-100个数2.进行1-100之间的总和3.求总和的平均值&…

安全帽穿戴检测人脸闸机联动

安全帽穿戴检测人脸闸机联动系统实通过yolov8网络深度学习算法模型&#xff0c;安全帽穿戴检测人脸闸机联动系统现对进入工地施工区域人员是否穿戴安全帽进行精准监测和身份识别&#xff0c;只有在满足这两个条件的情况下&#xff0c;闸机才会打开&#xff0c;允许其进入工地施…

ICT产教融合创新实训基地物联网实训室建设方案

一、概述 1.1物联网定义 物联网工程&#xff08;Internet of Things Engineering&#xff09;是一种以信息技术&#xff08;IT&#xff09;来改善实体世界中人们生活方式的新兴学科&#xff0c;它利用互联网技术为我们的日常生活活动提供服务和增益&#xff0c;从而让各种智能…

怎么借助ChatGPT处理数据结构的问题

目录 使用ChatGPT进行数据格式化转换 代码示例 ChatGPT格式化数据提示语 代码示例 批量格式化数据提示语 代码示例 ChatGPT生成的格式化批处理代码 使用ChatGPT合并不同数据源的数据 合并数据提示语 自动合并数据提示语 ChatGPT生成的自动合并代码 结论 数据合并是…

软件设计师学习笔记6-存储系统

1.层次化存储体系 1.1层次化存储结构 局部性原理是层次化存储结构的支持 时空局部性&#xff1a;刚被访问的内容&#xff0c;立即又被访问(eg: 循环体 ) 空间局部性&#xff1a;刚被访问的内容&#xff0c;临近的空间很快被访问(eg:数组) 1.2层次化存储结构的分类 DRAM&…

VAE原理 代码详解 pin_memory

VAE代码 import torch from torch import nn import torch.nn.functional as F class VAE(nn.Module):def __init__(self, input_dim784, h_dim400, z_dim20): # 28x28784,20可能是这个手写体一共有20类&#xff1f;super(VAE, self).__init__()self.input_dim input_dimsel…

微信开放注册微信小号功能,工作人群福音!

微信&#xff0c;这个坐拥数亿用户的社交巨头&#xff0c;最近终于开放了注册微信小号的功能。这个功能对于需要多个微信账号进行工作的人来说&#xff0c;无疑是一场及时雨&#xff0c;极大地提高了工作便利性。 在之前的版本中&#xff0c;每个微信账号都绑定了一个手机号&am…

主从、哨兵、集群模式有什么区别 ?

目录 1.Redis 多机部署的方式 2.主从、哨兵、集群模式有什么区别 2.1 主从同步 2.2 哨兵模式 2.3 集群模式 1.Redis 多机部署的方式 Redis 多机部署主要有 3 种方式&#xff1a; 1. 主从同步&#xff1a;主要存储数据的节点叫做主节点&#xff08;master&#xff09;&…

限时 180 天,微软为 RHEL 9 和 Ubuntu 22.04 推出 SQL Server 2022 预览评估版

导读近日消息&#xff0c;微软公司今天发布新闻稿&#xff0c;宣布面向 Red Hat Enterprise Linux&#xff08;RHEL&#xff09;9 和 Ubuntu 22.04 两大发行版&#xff0c;以预览模式推出 SQL Server 2022 评估版。 近日消息&#xff0c;微软公司今天发布新闻稿&#xff0c;宣布…

网络安全(黑客)零基础自学

网络安全是什么&#xff1f; 网络安全&#xff0c;顾名思义&#xff0c;网络上的信息安全。 随着信息技术的飞速发展和网络边界的逐渐模糊&#xff0c;关键信息基础设施、重要数据和个人隐私都面临新的威胁和风险。 网络安全工程师要做的&#xff0c;就是保护网络上的信息安…

数字 IC 设计职位经典笔/面试题(三)

共100道经典笔试、面试题目&#xff08;文末可全领&#xff09; 1. IC 设计中同步复位与异步复位的区别&#xff1f; 同步复位在时钟沿变化时&#xff0c;完成复位动作。异步复位不管时钟&#xff0c;只要复位信号满足条件&#xff0c;就完成复位动作。异步复位对复位信号要求…

开始MySQL之路——MySQL的DataGrip图形化界面

下载DataGrip 下载地址&#xff1a;Download DataGrip: Cross-Platform IDE for Databases & SQL 安装DataGrip 准备好一个文件夹&#xff0c;不要中文和空格 C:\Develop\DataGrip 激活DataGrip 激活码&#xff1a; VPQ9LWBJ0Z-eyJsaWNlbnNlSWQiOiJWUFE5TFdCSjBaIiwibGl…

用 Audacity 比较两段音频差异

工作中遇到相同的处理流程&#xff0c;处理同一段音频&#xff0c;看看处理结果是否一致&#xff0c;可以用audacity来处理。 假设待比较的音频分别为 1.wav 2.wav 1、用Audacity打开1.wav 2、用Audacity打开2.wav&#xff0c;选中音频&#xff0c;然后用 效果 -> 反向&am…

Linux内核学习(九)—— 虚拟文件系统(基于Linux 2.6内核)

虚拟文件系统&#xff08;VFS&#xff09;作为内核子系统&#xff0c;为用户空间程序提供了文件和文件系统相关的接口。通过虚拟文件系统&#xff0c;程序可以利用标准的 Unix 系统调用对不同的文件系统&#xff08;甚至不同介质上的文件系统&#xff09;进行读写操作。 一、通…

【算法系列篇】前缀和

文章目录 前言什么是前缀和算法1.【模板】前缀和1.1 题目要求1.2 做题思路1.3 Java代码实现 2. 【模板】二维前缀和2.1 题目要求2.2 做题思路2.3 Java代码实现 3. 寻找数组的中心下标3.1 题目要求3.2 做题思路3.3 Java代码实现 4. 除自身以外的数组的乘积4.1 题目要求4.2 做题思…

C++:构造方法(函数);拷贝(复制)构造函数:浅拷贝、深拷贝;析构函数。

1.构造方法(函数) 构造方法是一种特殊的成员方法&#xff0c;与其他成员方法不同: 构造方法的名字必须与类名相同&#xff1b; 无类型、可有参数、可重载 会自动生成&#xff0c;可自定义 一般形式:类名(形参)&#xff1b; 例: Stu(int age); 当用户没自定义构造方法时&…

apache的ab工具测试网页优化效果速度以及服务器承载

今天为大家介绍一款apache自带的一种的测试网页优化效果速度以及服务器承载的工具——ab.exe。 大家在工作中或者开发中可以使用apache的ab工具来测试自己的网站并发量大小&#xff0c;和某个页面的访问时间。 一、基本用法 如果你是用的是apache的话&#xff0c;那么只要进…
最新文章