【随便聊聊 JVM原理】 虚拟机做的那些优化 - intrinsic

2023-11-10 06:21

本文主要是介绍【随便聊聊 JVM原理】 虚拟机做的那些优化 - intrinsic,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

    • 前言
    • 一、我是怎么了解到intrinsic
    • 二、由intrinsic我了解到什么
    • 三、intrinsic与CPU指令
      • 3.1 StringLatin1.indexof()
      • 3.2 Math中的大量方法
      • 3.3 Integer.bigCount()
        • 3.4 Unsafe 类中经常会被用到的便是 compareAndSwap 方法
        • 3.5 String类、StringLatin1类、StringUTF16类和Arrays类的方法。
    • 四、intrinsic与方法内联
    • 五、现有的intrinsic
    • 参考资料

前言

最近公司企业微信给我们开通了极客时间的SVIP,我就顺便看了一些,突然就想起来之前一段时间找工作没有去记录,这次正好重新出发!
之前也写过一个类似的关于Java的JIT(即时编译器)知识整理虚拟机相关的知识,这次正好了解到他的另外一个特性, intrinsic

一、我是怎么了解到intrinsic

一天,我在认(mo)真(yu)工(hua)作(shui)的时候,突然看的一个注解,就正常的百度了下,一查不要紧,一堆不懂的,之后一发不可收拾,首先看的的是这个注解 @HotSpotIntrinsicCandidate,听说是JDK8有,就是比较少(JDK8有intrinsic - 直接看最后的参考资料),但是没有这个注解),找办法没找到,我就下了个JDK11,在String.java中找到了
在这里插入图片描述
这里顺便提一嘴,点进注解,才发现,是从JDK9开始有的
在这里插入图片描述
顺带提一嘴,因为以下原因

JDK9 之前的库的 String 类的实现使用了 char 数组来存放字符串,是为了兼容非英文字符,而大多数Java程序中的字符串都是由Latin1(ISO-8859-1别名 每个字符占1字节)字符组成的,char 占用16位,即两字节。
这就存在了空间浪费,所以Java9提出了Compact String的概念,新增一个属性private final byte coder; 当字符串仅包含 Latin1 字符时,使用一个字节代表一个字符的编码格式,使得内存使用效率大大提高。
在这里插入图片描述

String的底层实现由JDK8的char[]变成了byte[]

二、由intrinsic我了解到什么

看完这些,其实开头所说的intrinsic就已经出现了,就是@HotSpotIntrinsicCandidate, 它的作用很简单,就是将使用这个注解的方法,在调用时,会被Hotspot虚拟机替换成高效的指令序列,忽略原本的实现。

而类似的,就像我在Java内存模型 -底层原理中提到的JVM的重排序

CPU避免内存访问延迟最常见的技术是将指令管道化,然后尽量重排这些管道的执行以最大化利用缓存,从而把因为缓存未命中引起的延迟降到最小。
当一个程序执行时,只要最终的结果是一样的,指令是否被重排并不重要。

⚠️ 需要注意的是

  • 并不是所有虚拟机都维护了同样的高效实现 —— 目前主流的商用虚拟机(HotSpot(Oracle)、J9 VM(IBM))
  • 并不是所有的CPU架构都适用这些优化 —— 目前讨论的为x86架构(amd64同),像苹果的M系列以及其他的ARM架构的CPU就需要单独适配指令
  • 如果没有对应的指令,就会直接使用JDK的实现
  • 因为这些差异,这些优化才没有被直接放在源代码中

三、intrinsic与CPU指令

前面说那么多,优化,到底是怎么优化的

3.1 StringLatin1.indexof()

StringLatin1.indexof() 会被替换成 X86_64体系结构下的SSE4.2指令集中的PCMPESTRI —— 简单来说,就是它能够在16位以下的字符串中,查找另一个16字节以下的字符串,并返回命中的索引
在这里插入图片描述

3.2 Math中的大量方法

Java的Math类库中有着很多的intrinsic的方法,我门就看看其中的
Math.addExact —— 这个方法有两个重载分别位接收两个int和long值,并返回相加的值,当结果和溢出时,抛出 ArithmeticException(“integer overflow”);

  /*** Returns the sum of its arguments,* throwing an exception if the result overflows an {@code int}.** @param x the first value* @param y the second value* @return the result* @throws ArithmeticException if the result overflows an int* @since 1.8*/@HotSpotIntrinsicCandidatepublic static int addExact(int x, int y) {int r = x + y;// HD 2-12 Overflow iff both arguments have the opposite sign of the resultif (((x ^ r) & (y ^ r)) < 0) {throw new ArithmeticException("integer overflow");}return r;}

在Java层面判断两个值的和是否超过最大值对应的就是两个异或、一个和与一个比较操作,而在X86_64体系中,大部分计算指令都会更新状态寄存器(Flags register), 其中就有表示是否溢出的标志位(overflow flag),因此我吗只需要在操作完成后,比较标志位即可。

3.3 Integer.bigCount()

这个方法是统计所输入int值的二进制形式有多少个1

    /*** Returns the number of one-bits in the two's complement binary* representation of the specified {@code int} value.  This function is* sometimes referred to as the <i>population count</i>.** @param i the value whose bits are to be counted* @return the number of one-bits in the two's complement binary*     representation of the specified {@code int} value.* @since 1.5*/@HotSpotIntrinsicCandidatepublic static int bitCount(int i) {// HD, Figure 5-2i = i - ((i >>> 1) & 0x55555555);i = (i & 0x33333333) + ((i >>> 2) & 0x33333333);i = (i + (i >>> 4)) & 0x0f0f0f0f;i = i + (i >>> 8);i = i + (i >>> 16);return i & 0x3f;}

可以看的,源码中的实现方式通过二进制的计算应该也是比较高明的,但是相对于X86_64体系架构中的一条指令popcnt就可以计算出1的个数,还是更加高效的。

3.4 Unsafe 类中经常会被用到的便是 compareAndSwap 方法

compareAndSwap()其实就是我们经常说的乐观锁思想的CAS的实现, 在JDK9+,被更名为 compareAndSet 或 compareAndExchange 方法。

在 X86_64 体系架构中,对这些方法的调用将被替换为lock cmpxchg 指令,也就是原子性更新指令。

3.5 String类、StringLatin1类、StringUTF16类和Arrays类的方法。

HotSpot 虚拟机将使用 SIMD 指令(single intruction multiple data,即用一条指令处理多个数据)小标题的类进行优化

For example,Arrays.equals(byte[], byte[])方法原本是逐个字节比较,在使用了 SIMD 指令之后,可以放入 16 字节的 XMM 寄存器中(甚至是 64 字节的 ZMM 寄存器中)批量比较。

四、intrinsic与方法内联

Hotspot虚拟机中,intrinsic实现方式分为两种

  1. 独立程序桩,就是在解释或者即时编译时调用,这类实现形式较少,主要为Math类中的一些方法
  2. 特殊的编译器IR —— 中间表达形式(Intermediate Representation)节点

在编译原理中,通常把编译器分为前端和后端,前端编译经过词法分析、语法分析、语义分析生成中间表达形式(Intermediate Representation,以下称为IR),后端会对IR进行优化,生成目标代码。
Java字节码就是一种IR,但是字节码的结构复杂,字节码这样代码形式的IR也不适合做全局的分析优化。现代编译器一般采用图结构的IR,静态单赋值(Static Single Assignment,SSA)IR是目前比较常用的一种。这种IR的特点是每个变量只能被赋值一次,而且只有当变量被赋值之后才能使用。

在编译过程中,即时编译器使用特殊的IR节点替换原有IR节点,被后端使用,生产CPU指令,这是大部分intrinsic实现的方式。

这个替换过程是在方法内联时进行的,当即时编译器碰到方法调用,会先查询方法是否为intrinsic。如果是,插入特殊IR,不是,进行原本内联的工作。

内联不仅将被调用方法的IR图节点复制到调用者方法的IR图中,还要完成其他操作。被调用方法的参数替换为调用者方法进行方法调用时所传入参数。

也就是说,如果方法调用的目标方法是 intrinsic,那么即时编译器会直接忽路原目标方法的宇节码,忽略原目标方法是否有字节码。即便是native 方法,只要它被标记为intrinsic,即时编译器便能够将之〝内联“进来,并插入特殊的IR节点。
事实上,不少被标记为 intrinsic 的方法都是native 方法。原本对这些native 方法的调用需要经过 JNI (Java Native Interface),其性能开销十分巨大。但是,经过即时编译器的intrinsic 优化之后,这部分 JNI 开销便直接消失,并且最终的结果也十分高效。
举个例子,我们可以通过Thread.currentThread方法来获取当前线程。这是一个 native方法,同时也是一个 Hotspot intrinsic。在x86_64 体系架构中,R13 寄存器存放着当前线程的指针。因此.对该方法的调用将被即时编译器替换为一个特殊 IR 节点,并最终生成读取 R13 寄存器指令。

五、现有的intrinsic

详情直接查看参考资料7
在这里插入图片描述

参考资料

  1. 22 | HotSpot虚拟机的intrinsic
  2. java ir_基本功 | Java即时编译器原理解析及实践
  3. 为什么int型最大值加一后等于这个值?
  4. [译]看JVM如何用最酷的x86指令来比较字符串
  5. java ir_基本功 | Java即时编译器原理解析及实践
  6. Compact Strings
  7. Java 12 OpenJDK / jdk / hs
  8. Java 8 OpenJDK / jdk8u / jdk8u / hotspot

这篇关于【随便聊聊 JVM原理】 虚拟机做的那些优化 - intrinsic的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/380906

相关文章

JavaScript中的isTrusted属性及其应用场景详解

《JavaScript中的isTrusted属性及其应用场景详解》在现代Web开发中,JavaScript是构建交互式应用的核心语言,随着前端技术的不断发展,开发者需要处理越来越多的复杂场景,例如事件... 目录引言一、问题背景二、isTrusted 属性的来源与作用1. isTrusted 的定义2. 为

Java循环创建对象内存溢出的解决方法

《Java循环创建对象内存溢出的解决方法》在Java中,如果在循环中不当地创建大量对象而不及时释放内存,很容易导致内存溢出(OutOfMemoryError),所以本文给大家介绍了Java循环创建对象... 目录问题1. 解决方案2. 示例代码2.1 原始版本(可能导致内存溢出)2.2 修改后的版本问题在

Java CompletableFuture如何实现超时功能

《JavaCompletableFuture如何实现超时功能》:本文主要介绍实现超时功能的基本思路以及CompletableFuture(之后简称CF)是如何通过代码实现超时功能的,需要的... 目录基本思路CompletableFuture 的实现1. 基本实现流程2. 静态条件分析3. 内存泄露 bug

Java中Object类的常用方法小结

《Java中Object类的常用方法小结》JavaObject类是所有类的父类,位于java.lang包中,本文为大家整理了一些Object类的常用方法,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1. public boolean equals(Object obj)2. public int ha

SpringBoot项目中Maven剔除无用Jar引用的最佳实践

《SpringBoot项目中Maven剔除无用Jar引用的最佳实践》在SpringBoot项目开发中,Maven是最常用的构建工具之一,通过Maven,我们可以轻松地管理项目所需的依赖,而,... 目录1、引言2、Maven 依赖管理的基础概念2.1 什么是 Maven 依赖2.2 Maven 的依赖传递机

SpringBoot实现动态插拔的AOP的完整案例

《SpringBoot实现动态插拔的AOP的完整案例》在现代软件开发中,面向切面编程(AOP)是一种非常重要的技术,能够有效实现日志记录、安全控制、性能监控等横切关注点的分离,在传统的AOP实现中,切... 目录引言一、AOP 概述1.1 什么是 AOP1.2 AOP 的典型应用场景1.3 为什么需要动态插

虚拟机与物理机的文件共享方式

《虚拟机与物理机的文件共享方式》文章介绍了如何在KaliLinux虚拟机中实现物理机文件夹的直接挂载,以便在虚拟机中方便地读取和使用物理机上的文件,通过设置和配置,可以实现临时挂载和永久挂载,并提供... 目录虚拟机与物理机的文件共享1 虚拟机设置2 验证Kali下分享文件夹功能是否启用3 创建挂载目录4

Oracle查询优化之高效实现仅查询前10条记录的方法与实践

《Oracle查询优化之高效实现仅查询前10条记录的方法与实践》:本文主要介绍Oracle查询优化之高效实现仅查询前10条记录的相关资料,包括使用ROWNUM、ROW_NUMBER()函数、FET... 目录1. 使用 ROWNUM 查询2. 使用 ROW_NUMBER() 函数3. 使用 FETCH FI

Java实现Excel与HTML互转

《Java实现Excel与HTML互转》Excel是一种电子表格格式,而HTM则是一种用于创建网页的标记语言,虽然两者在用途上存在差异,但有时我们需要将数据从一种格式转换为另一种格式,下面我们就来看看... Excel是一种电子表格格式,广泛用于数据处理和分析,而HTM则是一种用于创建网页的标记语言。虽然两

java图像识别工具类(ImageRecognitionUtils)使用实例详解

《java图像识别工具类(ImageRecognitionUtils)使用实例详解》:本文主要介绍如何在Java中使用OpenCV进行图像识别,包括图像加载、预处理、分类、人脸检测和特征提取等步骤... 目录前言1. 图像识别的背景与作用2. 设计目标3. 项目依赖4. 设计与实现 ImageRecogni