一、即时编译(JIT)

JIT:Just In Time Compiler,即时编译器

这是针对解释型语言而言的,而且并非虚拟机必须,是一种优化手段。Hotspot就有这种技术,Java虚拟机标准对JIT的存在没有作出任何规范,这是虚拟机实现的自定义优化技术。

HotSpot虚拟机的执行引擎在执行Java代码是可以采用 解释执行 和 编译执行 两种方式的

如果采用的是编译执行方式,那么就会使用到JIT,而解释执行就不会使用到JIT。

HotSpot中的编译器是javac,它的工作就是将java代码编译为可执行的class文件,这部分工作是完全独立的,完全不需要运行时参与,所以java程序的编译是半独立实现的。有了字节码,就由解释器来进行解释执行,这是早期java虚拟机的工作流程;后来,java虚拟机会将执行频率高的方法或者语句块通过JIT编译成本地机器码,提高了代码执行的效率

(一)JIT工作原理

当JIT编译启用时(默认是启用的),JVM读入.class文件解释后,将其发给JIT编译器。JIT编译器将字节码编译成本机机器代码。

通常javac将程序源代码编译,转换成java字节码,JVM通过解释字节码将其翻译成对应的机器指令,逐条读入,逐条解释翻译。很显然,经过解释执行,其执行速度必然会比可执行的二进制字节码程序慢。为了提高执行速度,引入了JIT技术。

在运行时JIT会把翻译过的机器码保存起来,以备下次使用,因此从理论上来说,采用该JIT技术可以,可以接近以前纯编译技术

(二)分层编译(Tiered Compilation)

Tiered Compilation是Java7中出现的,目的是整合C1的快速编译和C2的快速执行。因为C2使用了“激进”的优化手段,编译较慢。Java7以前,一般要求快速启动的GUI程序会选择C1,偏好性能的服务器程序使用C2

热点探测

HotSpot虚拟机中有两个编译器,一个是给客户端用的叫client Compiler,另一个是服务器用的叫Server Compiler。

一般的,把Client Compiler也叫C1编译器,Server Compiler叫C2编译器或Opto编译器

虚拟机会根据自身版本与宿主机的硬件性能自动选择运行模式,也可以使用 “-client”或“-server”参数去强制指定虚拟机运行在Client模式或Server模式

热点代码有两类

  • 被多次执行的方法
  • 多次执行的循环体

虚拟机为每个代码块和方法设置了计数器,执行一次就加1。超过限定次数就认为是热点代码,开始JIT处理。给JIT去处理只是一个请求,并不会立即同步等待结果。因为JIT编译比较耗时,在编译完成前会继续解释执行。编译器处理都是以方法为单位,所以第一类热点代码是标准的JIT编译方式;对于第二种热点代码,JIT编译器会处理包含该循环的方法

HotSpot虚拟机有两种计数器(方法会同时记录这两个计数),它们的阈值并不同

1、调用次数计数器,可以通过-XX:CompileThreadhold参数指定阈值,不指定默认C1是1500次,C2是1万次

2、字节码中向之前跳转的指令叫“回边”,回边次数是回边计数器。明显这个针对的是第二类热点代码。它的阈值是算出来的,公式如下

OSR 阈值 = CompileThreshold *
((OnStackReplacePercentage - InterpreterProfilePercentage)/100)

第一个参数CompileThreshold就是调用计数器,后面两个也都可以通过-XX指定。默认InterpreterProfilePercentage是33,而OnStackReplacePercentage的默认值在客户端和服务器模式不一样,分别是933和140,所以阈值分别是13500和10700

// -XX:+PrintCompilation -XX:-DoEscapeAnalysispublic static void main(String[] args) {for (int i = 0; i < 200; i++) {long start = System.nanoTime();for (int j = 0; j < 1000; j++) {new Object();}long end = System.nanoTime();System.out.printf("%d\t%d\n",i,(end - start));}}

上述代码在后面循环次数中时间花费比较少

图片来自百度

因为JVM 将执行状态分成了 5 个层次

0 层,解释执行(Interpreter)

1 层,使用 C1 即时编译器编译执行(不带 profiling)

2 层,使用 C1 即时编译器编译执行(带基本的 profiling)

3 层,使用 C1 即时编译器编译执行(带完全的 profiling)

4 层,使用 C2 即时编译器编译执行

注:

profiling 是指在运行过程中收集一些程序执行状态的数据,例如【方法的调用次数】,【循环的 回边次数】等

(三)即时编译器(JIT)与解释器的区别

1、解释器是将字节码解释为机器码,下次即使遇到相同的字节码,仍会执行重复的解释

2、JIT 是将一些字节码编译为机器码,并存入 Code Cache,下次遇到相同的代码,直接执行,无需 再编译

3、解释器是将字节码解释为针对所有平台都通用的机器码

4、JIT 会根据平台类型,生成平台特定的机器码

对于占据大部分的不常用的代码,我们无需耗费时间将其编译成机器码,而是采取解释执行的方式运 行;另一方面,对于仅占据小部分的热点代码,我们则可以将其编译成机器码,以达到理想的运行速 度。 执行效率上简单比较一下 Interpreter < C1 < C2

https://docs.oracle.com/en/java/javase/12/vm/java-hotspot-virtual-machine-performance-enhancements.html#GUID-F33D8BD0-5C4A-4CE8-8259-FD9D73C7C7C6

(四)内联

方法内联就是把被调用方函数代码"复制"到调用方函数中,来减少因函数调用开销的技术

一个简单的两数相加程序,被内联前的代码

private int add4(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 add4(int x1, int x2, int x3, int x4) {  return x1 + x2 + x3 + x4;
}

JVM会自动的识别热点方法,并对它们使用方法内联优化 ;一段代码需要执行多少次才会触发JIT优化呢?通常这个值由-XX:CompileThreshold参数进行设置

  • 使用client编译器时,默认为1500;
  • 使用server编译器时,默认为10000

一个方法就算被JVM标注成为热点方法,JVM仍然不一定会对它做方法内联优化。其中有个比较常见的原因就是这个方法体太大了,分为两种情况。

  • 如果方法是经常执行的,默认情况下,方法大小小于325字节的都会进行内联(可以通过** -XX:MaxFreqInlineSize=N**来设置这个大小)
  • 如果方法不是经常执行的,默认情况下,方法大小小于35字节才会进行内联(可以通过** -XX:MaxInlineSize=N **来设置这个大小)

可以通过增加这个大小,以便更多的方法可以进行内联;但是除非能够显著提升性能,否则不推荐修改这个参数。因为更大的方法体会导致代码内存占用更多,更少的热点方法会被缓存,最终的效果不一定好。

如果想要知道方法被内联的情况,可以使用下面的JVM参数来配置

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

想要对热点的方法使用内联的优化方法,最好尽量使用final、private、static这些修饰符修饰方法,避免方法因为继承,导致需要额外的类型检查,而出现效果不好情况

(五)字段优化

JMH 基准 OpenJDK: jmh

创建 maven 工程,添加依赖如下

<properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><maven.compiler.source>1.8</maven.compiler.source><maven.compiler.target>1.8</maven.compiler.target><jmh.version>1.0</jmh.version><uberjar.name>benchmarks</uberjar.name></properties><dependencies><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.11</version><scope>test</scope></dependency><dependency><groupId>org.openjdk.jmh</groupId><artifactId>jmh-core</artifactId><version>${jmh.version}</version></dependency><dependency><groupId>org.openjdk.jmh</groupId><artifactId>jmh-generator-annprocess</artifactId><version>${jmh.version}</version><scope>provided</scope></dependency></dependencies>
@Warmup(iterations = 2, time = 1)
@Measurement(iterations = 5, time = 1)
@State(Scope.Benchmark)
public class Benchmark1 {int[] elements = randomInts(1_000);private static int[] randomInts(int size) {Random random = ThreadLocalRandom.current();int[] values = new int[size];for (int i = 0; i < size; i++) {values[i] = random.nextInt();}return values;}@Benchmarkpublic void test1() {for (int i = 0; i < elements.length; i++) {doSum(elements[i]);}}@Benchmarkpublic void test2() {int[] local = this.elements;for (int i = 0; i < local.length; i++) {doSum(local[i]);}}@Benchmarkpublic void test3() {for (int element : elements) {doSum(element);}}static int sum = 0;@CompilerControl(CompilerControl.Mode.INLINE)static void doSum(int x) {sum += x;}public static void main(String[] args) throws RunnerException {Options opt = new OptionsBuilder().include(Benchmark1.class.getSimpleName()).forks(1).build();new Runner(opt).run();}
}

启用 doSum 的方法内联,测试结果如下(每秒吞吐量,分数越高的更好):

# Run complete. Total time: 00:00:28Benchmark                Mode  Samples        Score  Score error  Units
c.m.Benchmark1.test1    thrpt        5  3543660.510   220838.607  ops/s
c.m.Benchmark1.test2    thrpt        5  3349451.189   913399.544  ops/s
c.m.Benchmark1.test3    thrpt        5  3472374.929   602064.401  ops/s

禁用 doSum 方法内联

@CompilerControl(CompilerControl.Mode.DONT_INLINE)
static void doSum(int x) {sum += x;
}
# Run complete. Total time: 00:00:28Benchmark                Mode  Samples       Score  Score error  Units
c.m.Benchmark1.test1    thrpt        5  421998.239   185020.935  ops/s
c.m.Benchmark1.test2    thrpt        5  505350.741    14294.063  ops/s
c.m.Benchmark1.test3    thrpt        5  463303.567   180720.536  ops/s

如果 doSum 方法内联了,刚才的 test1 方法会被优化成下面的样子

@Benchmark
public void test1() {// elements.length 首次读取会缓存起来 -> int[] localfor (int i = 0; i < elements.length; i++) { // 后续 999 次 求长度 <- localsum += elements[i]; // 1000 次取下标 i 的元素 <- local}
}

二、反射优化

Java的反射技术,使静态语言的java具备了动态语言的某些特质。反射,让java动态,编程的时候更加灵活,能够动态获取信息以及动态调用对象方法。其实,Java基础技术中的代理,注解也都是依托反射才 能得以实现并应用广泛,另外我们常用的Spring、myBatis等技术框架也都是依托反射才能得以实现

public class Reflect1 {public static void test() {System.out.println("test...");}public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, IOException {Method test = Reflect1.class.getMethod("test");for (int i = 0; i <= 16; i++) {System.out.printf("%d\t", i);test.invoke(null);}System.in.read();}
}

test.invoke 前面 0 ~ 15 次调用使用的是 MethodAccessor 的 NativeMethodAccessorImpl 实现

package sun.reflect;
import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;
import sun.reflect.misc.ReflectUtil;class NativeMethodAccessorImpl extends MethodAccessorImpl {private final Method method;private DelegatingMethodAccessorImpl parent;private int numInvocations;NativeMethodAccessorImpl(Method method) {this.method = method;}public Object invoke(Object target, Object[] args)throws IllegalArgumentException, InvocationTargetException {// inflationThreshold 膨胀阈值,默认 15if (++this.numInvocations > ReflectionFactory.inflationThreshold()&& !ReflectUtil.isVMAnonymousClass(this.method.getDeclaringClass())){// 使用 ASM 动态生成的新实现代替本地实现,速度较本地实现快 20 倍左右MethodAccessorImpl generatedMethodAccessor =(MethodAccessorImpl)(new MethodAccessorGenerator()).generateMethod(this.method.getDeclaringClass(),this.method.getName(),this.method.getParameterTypes(),this.method.getReturnType(),this.method.getExceptionTypes(),this.method.getModifiers());this.parent.setDelegate(generatedMethodAccessor);}// 调用本地实现return invoke0(this.method, target, args);}void setParent(DelegatingMethodAccessorImpl parent) {this.parent = parent;}private static native Object invoke0(Method method, Object target, Object[]args);
}

当调用到第 16 次(从0开始算)时,会采用运行时生成的类代替掉最初的实现,可以通过 debug 得到 类名为 sun.reflect.GeneratedMethodAccessor1

使用阿里的 arthas 工具

java -jar arthas-boot.jar

再输入【jad + 类名】来进行反编译

$ jad sun.reflect.GeneratedMethodAccessor1

JVM -- 运行期优化;JIT(九)相关推荐

  1. Java编译器优化与运行期优化技术浅析

    2019独角兽企业重金招聘Python工程师标准>>> 一.java编译器优化 1. JVM的编译器可以分为三个编译器:      1)  前端编译器:把.java转变为.class ...

  2. 深入理解JVM虚拟机(九):运行期优化与JIT编译器

    1. JIT编译器的引入 首先我们这篇文章中所说的编译器都是指JVM的组成部分之一-即时编译器(JIT),与生成Java字节码的javac编译器要区分开来.首先我们这篇文章中所说的编译器都是指JVM的 ...

  3. 深入理解JVM:晚期(运行期)优化

    Java程序最初是通过解释器进行解释执行的,当虚拟机发现某个方法或代码块的运行特别频繁时,就会把这些代码认定为"热点代码"(Hot Spot Code). 1.即时编译器(JIT编 ...

  4. [深入理解Java虚拟机]第十一章 程序编译与代码优化-晚期(运行期)优化

    概述 在部分的商用虚拟机(Sun HotSpot.IBM J9)中,Java程序最初是通过解释器(Interpreter)进行解释执行的,当虚拟机发现某个方法或代码块的运行特别频繁时,就会把这些代码认 ...

  5. 理解java虚拟机工作后了解吗,【深入理解JAVA虚拟机】第4部分.程序编译与代码优化.2.运行期优化。这章提到的具体的优化技术,应该对以后做性能工作会有帮助。...

    1.概述 Java程序最初是通过解释器(Interpreter)进行解释执行的,当虚拟机发现某个方法或代码块的运行特别频繁时,就会把这些代码认定为"热点代码"(Hot Spot C ...

  6. jvm(11)-晚期(运行期)优化

    [0]README 0.1)本文部分文字描述转自 "深入理解 jvm",旨在学习  晚期(运行期)优化  的基础知识: [1]概述 1)即时编译器(JIT=just in time ...

  7. 在运行期通过反射了解JVM内部机制

    在日常工作中,我们都习惯直接使用或者通过框架使用反射.在没有反射相关硬编码知识的情况下,这是Java和Scala编程中使用的类库与我们的代码之间进行交互的一种主要手段.但是,使用反射仅限于JVM内部运 ...

  8. 【笔记】深入理解 Java 虚拟机:晚期(运行期)优化

    文章目录 概述 Hotspot 虚拟机内的即时编译器 解释器与编译器 编译对象与触发条件 方法调用计数器 回边计数器 编译过程 Client Compiler Server Compiler 编译优化 ...

  9. 一文了解程序员必须要知道的JVM和性能优化知识点

    目录 JVM和性能优化 1.Java内存区域 虚拟机的历史 未来的Java技术一览 运行时数据区域 站在线程角度来看堆和栈 深入辨析堆和栈 方法的出入栈 虚拟机中的对象 堆参数设置和内存溢出实战 2. ...

最新文章

  1. 碰到Maven依赖冲突,想砸电脑?这个IDEA插件必须了解一下...
  2. 关于 There is no getter for property named ‘id‘ in ‘class java.lang.Integer‘
  3. Android之在一个类里面注册Handler发送消息在另外一个类里面接收消息
  4. 移动端 IP 优选方案
  5. ZZULIOJ 1117: 查找数组元素
  6. 在Project中引用zedgraph控件
  7. 找不到文件或程序集名称“DreamweaverCtrls”的解决方法
  8. Checkstyle的配置集
  9. C#编写串口监控软件的详细教程
  10. java 坦克大战暂停_java实现坦克大战游戏
  11. 数学建模:现代优化算法之粒子群算法
  12. 免费后台挂尔雅浏览器下载及使用教程
  13. H5如何调用手机摄像头?
  14. 每月缴的个税,你知道怎么算的吗?
  15. iOS 13获取keyWindow
  16. 主修计算机专业的青年们的一封信(转载)
  17. Linux和Windows设备驱动架构比较
  18. 2022年湖南省自考考试学前教育行政与管理练习题及答案
  19. 人机协作,小i机器人搭档杨澜主持上海科技节闭幕式
  20. pcb成孔与孔金属化技术

热门文章

  1. 机器翻译自动评估-BLEU算法详解
  2. 重庆工商职业学院计算机类宿舍,重庆工商职业学院宿舍_宿舍条件图片环境分享...
  3. 服务器做bond的方法
  4. Spring优点、Spring IOC 底层实现原理、Spring IOC 快速入门案例、何为 DI 依赖注入
  5. 修改文字对齐方式,居中改为底部对齐
  6. java给视频添加字幕_JavaCV本地视频流通过帧图片添加文本进行字幕合成
  7. win7安装pycharm并永久激活
  8. 微信小程序如何引入外部字体包
  9. asp 检查黑名单_c# asp.net ip黑名单
  10. 邻接表的实现(有向邻接表)、(无向邻接表),基于C++