即时编译器JIT

类编译加载执行过程

如下图所示,一个Java代码从编译到运行大抵会经历以下几个过程。具体每个过程笔者会在下文站展开讨论。

类编译

首先是类编译阶段,这个阶段会将Java文件变为class文件,这个class文件包含一个常量池和方法表集合,而方法表集合里面会包含方法访问权限、返回类型、JVM执行指令以及属性集合等信息。

类加载

对于没有加载的类,JVM就会拿着这个class文件进行类加载,JDK自带的本地方法在双亲委派机制下,会用根加载器(Bootstrp loader)进行加载,而JDK扩展方法则会由扩展加载器(ExtClassLoader ),我们应用程序自己写的方法则是由系统加载器(AppClassLoader )完成加载。

完成加载后,常量池或者每个类的字段描述符、方法描述符等信息都会加载到JVM的方法区,同时会在堆区生成一个代表这个类的java.lang.Class对象,作为这个类的各种数据的访问入口。

类连接

类连接就是验证、准备、初始化3个过程了。

  1. 验证:验证类符合 Java 规范和 JVM 规范,在保证符合规范的前提下,避免危害虚拟机安全。
  2. 准备: 为类的静态变量分配内存,初始化为系统初始值。private final static int value=123,在这一步就完成空间分配和初始赋值为0。而private final int num=123则会在这一步直接赋值为123。因为它是一个常量。
  3. 解析:将编译器每个类的符号引用(包括类和接口的全限定名、类引用、方法引用以及成员变量引用等)等信息转为直接引用(JVM可直接获取的内存地址或指针)
类初始化

JVM会执行构造器的<cinit>,收集所有类、方法、静态变量的初始化静态变量赋值、静态代码块、静态方法,然后按顺序从上到下执行。

注意笔者说的,按顺序从上到下,这就意味的静态变量的完成初始化后的结果是以最后一个初始化语句为准。如下所示

赋值语句在后,结果为1


public class Main {

    static  {
        num = 2;
    }
    private static int num = 1;



    public static void main(String[] args) {
        System.out.println(num);//1
    }
}

 静态代码块在后,结果为2

public class Main {


    private static int num = 1;

   static  {
        num = 2;
    }

    public static void main(String[] args) {
        System.out.println(num);//1
    }
}

即时编译(重点)

在初始化阶段完成后,执行引擎不断将调用到的字节码翻译成机器码交由计算机执行。Java字节码转为机器码之间还有一步转换,我们称之为既时编译

最初Java字节码文件是直接通过解释器( Interpreter )解释为机器码直接运行的。
后来设计者们考虑到某些执行频率比较高的代码,我们可以称之为热点代码,可以进行某些机制进行优化(例如对代码逻辑进行优化缓存到本地内存中)
所以,我们如今编写的Java代码若执行频率非常高的话,就会被判定为热点代码,那么即时编译器,就会对这类代码进行逻辑优化,编译为最优的本地机器码保存到内存中。


著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

 

即时编译器类型有哪些?

HotSpot 虚拟机中内置了两个JIT编译器,分别为:

  1. C1编译器:主要关注点在于局部性优化,常用于那些执行时间短,或者要求快速启动的应用程序,例如GUI应用程序。
  2. C2编译器:常用于长期运行且对峰值性能有高要求的服务器。

所以我们也称C1编译器和C2编译器为 Client Compiler或者Server Compiler

在 Java7 之前,需要根据程序的特性来选择对应的 JIT,虚拟机默认采用解释器和其中一个编译器配合工作。
Java7 引入了分层编译,这种方式综合了 C1 的启动性能优势和 C2 的峰值性能优势,我们也可以通过参数 “-client”“-server” 强制指定虚拟机的即时编译模式。分层编译将 JVM 的执行状态分为了 5 个层次:
第 0 层:程序解释执行,默认开启性能监控功能(Profiling),如果不开启,可触发第二层编译;
第 1 层:可称为 C1 编译,将字节码编译为本地代码,进行简单、可靠的优化,不开启 Profiling;
第 2 层:也称为 C1 编译,开启 Profiling,仅执行带方法调用次数和循环回边执行次数 profiling 的 C1 编译;
第 3 层:也称为 C1 编译,执行所有带 Profiling 的 C1 编译;
第 4 层:可称为 C2 编译,也是将字节码编译为本地代码,但是会启用一些编译耗时较长的优化,甚至会根据性能监控信息进行一些不可靠的激进优化。

在 Java8 中,默认开启分层编译,-client 和 -server 的设置已经是无效的了。如果只想开启
C2,可以关闭分层编译(-XX:-TieredCompilation),如果只想用
C1,可以在打开分层编译的同时,使用参数:-XX:TieredStopAtLevel=1。

我们可以使用java -version查看当前编译的编译模式,可以看到笔者服务器的JVM使用的就是混合编译模式


[root@iZ8vb7bhe4b8nhhhpavhwpZ ~]# java -version
java version "1.8.0_202"
Java(TM) SE Runtime Environment (build 1.8.0_202-b08)
Java HotSpot(TM) 64-Bit Server VM (build 25.202-b08, mixed mode)
如果我们想强制运行JIT编译模式,也可以使用
java -Xint -version

如果我们想强制运行JIT编译模式,也可以使用

java -Xcomp -version

热点探测了解过吗(重点)

HotSpot 虚拟机判定热点代码是基于两种计数器进行的,分别是方法调用计数器(Invocation Counter)回边计数器(Back Edge Counter),只有执行代码符合他们的标准且达到他的设置的阈值时才会进行JIT编译优化。

方法调用计数器

这个计数器工作机制非常好理解,当某个方法执行次数超过阈值时,就会触发JIT编译优化,这个阈值我们可以通过jinfo查看,如下所示,可以看到笔者JVM设置的方法调用计数器判定是否是热点代码的条件为调用次数达到10000次。

[root@xxx~]# jinfo -flag CompileThreshold 2341
-XX:CompileThreshold=10000
回边计数器

在字节码遇到控制流后跳转的操作我们称之为回边。回边计数器判定代码为热点代码的条件是:一个代码在循环体内达到回边计数器要求的阈值,而这个阈值我们也可以通过jinfo查看

[root@xxx~]# jinfo -flag OnStackReplacePercentage 2341
-XX:OnStackReplacePercentage=140

当这段代码被判定为热点代码时,JVM就会进行一种栈上编译的优化操作,它会将这段代码编译为最优逻辑保存到本地内存,在执行循环体的期间,直接使用缓存中的机器码。

JIT自动进行的编译优化技术(重要)

方法内联

我们都知道方法调用会经历一个压栈和出栈的操作,执行调用方法时会将地址转移到存储该方法的起始地址上,待调用结束后,在返回原来的位置。
这就意味着一个方法调用另一个方法时,就需要保存当前方法执行位置,栈上压入被调用方法,执行完成后,恢复现场继续执行之前执行的方法。因此方法调用期间是有一定的时间和空间的开销的。

所以JIT会对那些方法调用方法非常频繁的代码执行方法内敛,如下所示:

private int add1(int x1, int x2, int x3, int x4) {
    return add2(x1, x2) + add2(x3, x4);
}
private int add2(int x1, int x2) {
    return x1 + x2;
}

最终会被优化为:

private int add1(int x1, int x2, int x3, int x4) {
    return x1 + x2 + x3 + x4;
}

但是方法内敛优化也是有条件的,除了必须是热点代码(达到XX:CompileThreshold的阈值)以外,还要达到以下要求:

  1. 对于经常执行的方法,方法体要小于325字节,这个字节数可以通过-XX:MaxFreqInlineSize=N来调整。
  2. 对于不经常执行的方法,方法体要小于35字节,这个字节数可以由-XX:MaxInlineSize=N 来调整。

我们不妨看一段代码,可以看到add1执行了1000000

public class JVMJit {
    public static void main(String[] args) {
        for (int i = 0; i < 1000000; i++) {
            add1(1, 2, 3, 4);
        }
    }

    private static int add1(int i, int i1, int i2, int i3) {
        return i + i1 + i2 + i3;
    }


}

我们可以对这段程序添加这样一段参数查详情-XX:+PrintCompilation -XX:+UnlockDiagnosticVMOptions -XX:+PrintInlining

他们的含义分别是

-XX:+PrintCompilation // 在控制台打印编译过程信息
-XX:+UnlockDiagnosticVMOptions // 解锁对 JVM 进行诊断的选项参数。默认是关闭的,开启后支持一些特定参数对 JVM 进行诊断
-XX:+PrintInlining // 将内联方法打印出来

可以看到这段代码被判定为热点代码,说明他已经被JVM优化了

所以这就要求我们平时写代码时:

  1. 方法体尽可能小
  2. 尽可能使用privatefinalstatic修饰,避免一些没必要的类是否继承等相关检查。
栈上分配

在将栈上分配前,我们需要先了解一个叫逃逸分析(Escape Analysis)的技术。
逃逸分析就是判断当前操作的对象是否有被外部方法引用或外部线程访问的一种技术,若逃逸分析判定当前对象并没有被其他引用或者线程使用到的话,某些机制就可以开始进行优化,比如我现在要说的栈上分配。

我们都知道创建一个对象,都是在堆上分配的,假如这个对象使用封闭,GC就会将其回收,而创建和回收这一来一回的操作也是有一定开销的。而栈则不一样,它使用的引用或者各种变量随着调用的结束就消亡。

而栈上分配就是抓住这一特点,当他经过逃逸分析技术发现这个对象并没有被外部引用且仅在当前线程使用,那么它就会将该对象分配在栈上。如下面这样一段代码:

public static void main(String[] args) {
    for (int i = 0; i < 200000 ; i++) {
    	getAge();
    }
}
 
public static int getAge(){
	Student person = new Student(" 小明 ",18,30);   
    return person.getAge();
}
 
static class Student {
    private String name;
    private int age;
   
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
 
...get set
}

但是,在 HotSpot 中暂时没有实现这项优化。随着即时编译器的发展与逃逸分析技术的逐渐成熟,相信不久的将来 HotSpot 也会实现这项优化功能。

锁消除

同样在逃逸分析某些没有被外部方法或者其他线程引用的情况下,会将某些锁消除。例如下面这段代码,实际上你在运行时可以发现StringBufferStringBuilder 性能上没有什么区别,这正是因为锁消除为我们做的优化工作。

public static String getString(String s1, String s2) {
        StringBuffer sb = new StringBuffer();
        sb.append(s1);
        sb.append(s2);
        return sb.toString();
    }
标量替换

当一个代码的对象在方法上可以拆分,并且代码仅仅是对这个对象的变量进行各种操作的话,编译器可能会执行标量替换,如下所示

  public void foo() {
        TestInfo info = new TestInfo();
        info.id = 1;
        info.count = 99;
          ...//to do something
    }

由于上述代码仅仅是创建一个对象后操作对象的变量,实际上这个工作似乎和对象没有任何关联,编译器识别到这点之后就不去创建没必要的对象,进而使用标量替换的方式将对象的成员变量放到栈上,避免没必要的对象创建和销毁。

  
   public void foo() {
        id = 1;
        count = 99;
        ...//to do something
    }

我们可以通过设置 JVM 参数来开关逃逸分析,还可以单独开关同步消除和标量替换,在 JDK1.8 中 JVM 是默认开启这些操作的。

-XX:+DoEscapeAnalysis 开启逃逸分析(jdk1.8 默认开启,其它版本未测试)
-XX:-DoEscapeAnalysis 关闭逃逸分析
 
-XX:+EliminateLocks 开启锁消除(jdk1.8 默认开启,其它版本未测试)
-XX:-EliminateLocks 关闭锁消除
 
-XX:+EliminateAllocations 开启标量替换(jdk1.8 默认开启,其它版本未测试)
-XX:-EliminateAllocations 关闭就可以了

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

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

相关文章

Android数据对象序列化原理与应用

序列化与反序列化 序列化是将对象转换为可以存储或传输的格式的过程。在计算机科学中&#xff0c;对象通常是指内存中的数据结构&#xff0c;如数组、列表、字典等。通过序列化&#xff0c;可以将这些对象转换为字节流或文本格式&#xff0c;以便在不同的系统之间进行传输或存…

【机器学习可解释性】4.SHAP 值

机器学习可解释性 1.模型洞察的价值2.特征重要性排列3.部分依赖图4.SHAP 值5.SHAP 值 高级使用 正文 理解各自特征的预测结果&#xff1f; 介绍 您已经看到(并使用)了从机器学习模型中提取一般解释技术。但是&#xff0c;如果你想要打破模型对单个预测的工作原理? SHAP 值…

Postman —— 配置环境变量

PostMan是一套比较方便的接口测试工具&#xff0c;但我们在使用过程中&#xff0c;可能会出现创建了API请求&#xff0c;但API的URL会随着服务器IP地址的变化而改变。 这样的情况下&#xff0c;如果每一个API都重新修改URL的话那将是非常的麻烦&#xff0c;所以PostMan中也提供…

Sprint Cloud Stream整合RocketMq和websocket实现消息发布订阅

1.引入RocketMQ依赖&#xff1a;首先&#xff0c;在pom.xml文件中添加RocketMQ的依赖&#xff1a; <dependency><groupId>org.apache.rocketmq</groupId><artifactId>rocketmq-spring-boot-starter</artifactId><version>2.2.0</versi…

J2EE项目部署与发布(Windows版本)->会议OA单体项目Windows部署,spa前后端分离项目Windows部署

会议OA单体项目Windows部署spa前后端分离项目Windows部署 1.会议OA单体项目Windows部署&#xff08;以实施的角度&#xff09; 将项目放入webapp&#xff0c;项目能够访问: 首先拿到war包和数据库脚本&#xff0c;并检查是否有什么问题。 如何查看项目报错信息&#xff08;当你…

Nginx性能优化

简介 nginx作为常用的web代理服务器&#xff0c;某些场景下对于性能要求还是蛮高的&#xff0c;所以本片文章会基于操作系统调度以及网络通信两个角度来讨论一下Nginx性能的优化思路。 基于操作系统调度进行Nginx优化 CPU工作方式 对于用户进程&#xff0c;CPU会按照下面的…

深入浅出排序算法之堆排序

目录 1. 算法介绍 2. 执行流程⭐⭐⭐⭐⭐✔ 3. 代码实现 4. 性能分析 1. 算法介绍 堆是一种数据结构&#xff0c;可以把堆看成一棵完全二叉树&#xff0c;这棵完全二叉树满足&#xff1a;任何一个非叶结点的值都不大于(或不小于)其左右孩子结点的值。若父亲大孩子小&#x…

计算机操作系统重点概念整理-第三章 进程同步【期末复习|考研复习】

第三章 进程同步 【期末复习|考研复习】 计算机操作系统系列文章传送门&#xff1a; 第一章 计算机系统概述 第二章 进程管理 第三章 进程同步 第四章 内存管理 第五章 文件管理 第六章 输出输出I/O管理 文章目录 第三章 进程同步 【期末复习|考研复习】前言三、进程同步3.1 临…

C# 递归算法使用简介_常用整理

一、递归简介 递归算法是一种直接或者间接调用自身函数或者方法的算法。 递归算法的实质是把问题分解成规模缩小的同类问题的子问题&#xff0c;然后递归调用方法来表示问题的解。递归算法对解决一大类问题很有效&#xff0c;它可以使算法简洁和易于理解。 递归本质是循环&a…

Visual Studio Code的下载与安装

Visual Studio Code&#xff08;简称 VS Code&#xff09;是由 Microsoft 开发的免费、开源的文本编辑器&#xff0c;适用于多种操作系统&#xff0c;包括 Windows、macOS 和 Linux。它的设计目标是成为一款轻量级、高效的代码编辑工具&#xff0c;同时提供丰富的扩展和功能&am…

MySQL初始化之后启动报错(mysqld: Table ‘mysql.plugin‘ doesn‘t exist)

报错场景 初始化之后&#xff0c;服务无法启动。错误日志error-log 报错如下&#xff1a;&#xff08;mysql库下的系统表不存在&#xff09; 2023-10-26T06:03:08.150163-00:00 1 [System] [MY-013576] [InnoDB] InnoDB initialization has started. 2023-10-26T06:03:08.496…

Vite+Vue3项目全局引入scss文件

前言 Sass 是世界上最成熟、最稳定、最强大的专业级CSS扩展语言&#xff01;在日常项目开发过程中使用非常广泛&#xff0c;今天主要讲一下 ViteVue3 项目中该如何全局引入 scss 文件&#xff0c;引入混合 mixin 文件的不同配置。捎带说一下 Vue2 中的引入方式做一下简单的对比…

vue3从基础到入门(一)

文章目录 简介提升使用创建脚手架vite 常用Composition APIsetuprefreactive函数响应式vue2响应式vue3实现响应式 reactive对比ref注意计算属性computed函数 监视watch函数watchEffect函数 生命周期hook函数toRef 简介 2020年9月18日&#xff0c;Vue.js发布3.0版本&#xff0c…

PHP聊天系统源码 在线聊天系统网站源码 后台自适应PC与移动端

这个源码提供了前台和后台的自适应布局&#xff0c;可以在PC和移动端上完美展示。它支持一对多的交流&#xff0c;用户可以自由地创建新的房间并解散已创建的房间。 该程序还集成了签到功能和等级功能&#xff0c;让用户享受更多的互动乐趣。房间创建者具有禁言和拉黑用户的权…

LibreOffice编辑excel文档如何在单元格中输入手动换行符

用WPS编辑excel文档的时候&#xff0c;要在单元格中输入手动换行符&#xff0c;可以先按住Alt键&#xff0c;然后回车。 而用LibreOffice编辑excel文档&#xff0c;要在单元格中输入手动换行符&#xff0c;可以先按住Ctrl键&#xff0c;然后回车。例如&#xff1a;

K8s 部署 CNI 网络组件+k8s 多master集群部署+负载均衡

------------------------------ 部署 CNI 网络组件 ------------------------------ ---------- 部署 flannel ---------- K8S 中 Pod 网络通信&#xff1a; ●Pod 内容器与容器之间的通信 在同一个 Pod 内的容器&#xff08;Pod 内的容器是不会跨宿主机的&#xff09;共享同一…

node模块导出引入两种方式和npm包管理

模块化的好处 在 Node.js 中每个文件都被当做是一个独立的模块&#xff0c;模块内定义的变量和函数都是独立作用域的&#xff0c;因为 Node.js 在执行模块代码时&#xff0c;将使用如下所示的函数封装器对其进行封装 (function(exports,require,module,__filename,_dirname){//…

解密Kubernetes:探索开源容器编排工具的内核

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

x210项目重新回顾之十七升级到linux4.19.114 +buildroot2018再讨论

代码参考https://github.com/colourfate/x210_bsp/ 他的是linux_4.10(dtb为 s5pv210-x210..dtb)我打算用linux4.19.114(dtb为 s5pv210-smdkv210.dtb) &#xff0c;所以修改build.sh ------------------------------------------------------------------------------ 5 M…

实用篇-认识微服务

一、服务架构演变 1. 单体架构 单体架构&#xff1a;将业务的所有功能集中在一个项目中开发&#xff0c;打成一个包部署 单体架构的优点&#xff1a; 架构简单部署成本低 单体架构的缺点&#xff1a; 耦合度高 2. 分布式架构 分布式架构&#xff1a; 根据业务功能对系…