乐趣区

小师妹学JVM之JDK14中JVM的性能优化

[toc]

简介

上一篇文章我们讲到了 JVM 为了提升解释的性能,引入了 JIT 编译器,今天我们再来从整体的角度,带小师妹看看 JDK14 中的 JVM 有哪些优化的方面,并且能够从中间得到那些启发。

更多精彩内容且看:

  • 区块链从入门到放弃系列教程 - 涵盖密码学, 超级账本, 以太坊,Libra, 比特币等持续更新
  • Spring Boot 2.X 系列教程: 七天从无到有掌握 Spring Boot- 持续更新
  • Spring 5.X 系列教程: 满足你对 Spring5 的一切想象 - 持续更新
  • java 程序员从小工到专家成神之路(2020 版)- 持续更新中, 附详细文章教程

String 压缩

小师妹:F 师兄,上次你给我讲的 JIT 真的是受益匪浅,原来 JVM 中还有这么多不为人知的小故事。不知道除了 JIT 之外,JVM 还有没有其他的性能提升的姿势呢?

姿势当然有很多种,先讲一下之前提到过的,在 JDK9 中引入的字符串压缩。

在 JDK9 之前,String 的底层存储结构是 char[], 一个 char 需要占用两个字节的存储单位。

因为大部分的 String 都是以 Latin- 1 字符编码来表示的,只需要一个字节存储就够了,两个字节完全是浪费。

于是在 JDK9 之后,字符串的底层存储变成了 byte[]。

目前 String 支持两种编码格式 LATIN1 和 UTF16。

LATIN1 需要用一个字节来存储。而 UTF16 需要使用 2 个字节或者 4 个字节来存储。

在 JDK9 中,字符串压缩是默认开启的。你可以使用

 -XX:-CompactStrings

来控制它。

分层编译(Tiered Compilation)

为了提升 JIT 的编译效率,并且满足不同层次的编译需求,引入了分层编译的概念。

大概来说分层编译可以分为三层:

  1. 第一层就是禁用 C1 和 C2 编译器,这个时候没有 JIT 进行。
  2. 第二层就是只开启 C1 编译器,因为 C1 编译器只会进行一些简单的 JIT 优化,所以这个可以应对常规情况。
  3. 第三层就是同时开启 C1 和 C2 编译器。

在 JDK7 中,你可以使用下面的命令来开启分层编译:

-XX:+TieredCompilation

而在 JDK8 之后,恭喜你,分层编译已经是默认的选项了,不用再手动开启。

Code Cache 分层

Code Cache 就是用来存储编译过的机器码的内存空间。也就说 JIT 编译产生的机器码,都是存放在 Code Cache 中的。

Code Cache 是以单个 heap 形式组织起来的连续的内存空间。

如果只是用一个 code heap,或多或少的就会引起性能问题。为了提升 code cache 的利用效率,JVM 引入了 Code Cache 分层技术。

分层技术是什么意思呢?

就是把不同类型的机器码分门别类的放好,优点嘛就是方便 JVM 扫描查找,减少了缓存的碎片,从而提升了效率。

下面是 Code Cache 的三种分层:

新的 JIT 编译器 Graal

之前的文章我们介绍 JIT 编译器,讲的是 JIT 编译器是用 C /C++ 来编写的。

而新版的 Graal JIT 编译器则是用 java 来编写的。对的,你没看错,使用 java 编写的 JIT 编译器。

有没有一种鸡生蛋,蛋生鸡的感觉?不过,这都不重要,重要的是 Graal 真的可以提升 JIT 的编译性能。

Graal 是和 JDK 一起发行的,作为一个内部的模块:jdk.internal.vm.compiler。

Graal 和 JVM 是通过 JVMCI(JVM Compiler Interface)来进行通信的。其中 JVMCI 也是一个内部的模块:jdk.internal.vm.ci。

注意,Graal 只在 Linux-64 版的 JVM 中支持,你需要使用 -XX:+UnlockExperimentalVMOptions -XX:+UseJVMCICompiler 来开启 Graal 特性。

前置编译

我们知道在 JIT 中,通常为了找到热点代码,JVM 是需要等待代码执行一定的时间之后,才开始进行本地代码的编译。这样做的缺点就是需要比较长的时间。

同样的,如果是重复的代码,没有被编译成为机器码,那么对性能就会有影响。

而 AOT(Ahead-of-time)就厉害了,看名字就知道是提前编译的意思,根本就不需要等待,而是在 JVM 启动之前就开始编译了。

AOT 提供了一个 java tool,名字叫做 jaotc。显示 jaotc 的命令格式:

jaotc <options> <list of classes or jar files>
jaotc <options> <--module name>

比如,我们可以这样提前编译 AOT 库,以供在后面的 JVM 中使用:

jaotc --output libHelloWorld.so HelloWorld.class
jaotc --output libjava.base.so --module java.base

上面代码提前编译了 HelloWorld 和它的依赖 module java.base。

然后我们可以在启动 HelloWorld 的时候,指定对应的 lib:

java -XX:AOTLibrary=./libHelloWorld.so,./libjava.base.so HelloWorld

这样在 JVM 启动的时候,就回去找相应的 AOTLibrary。

注意,AOT 是一个 Linux-x64 上面的体验功能。

压缩对象指针

对象指针用来指向一个对象,表示对该对象的引用。通常来说在 64 位机子上面,一个指针占用 64 位,也就是 8 个字节。而在 32 位机子上面,一个指针占用 32 位,也就是 4 个字节。

实时上,在应用程序中,这种对象的指针是非常非常多的,从而导致如果同样一个程序,在 32 位机子上面运行和在 64 位机子上面运行占用的内存是完全不同的。64 位机子内存使用可能是 32 位机子的 1.5 倍。

而压缩对象指针,就是指把 64 位的指针压缩到 32 位。

怎么压缩呢?64 位机子的对象地址仍然是 64 位的。压缩过的 32 位存的只是相对于 heap base address 的位移。

我们使用 64 位的 heap base 地址 + 32 位的地址位移量,就得到了实际的 64 位 heap 地址。

对象指针压缩在 Java SE 6u23 默认开启。在此之前,可以使用 -XX:+UseCompressedOops 来开启。

Zero-Based 压缩指针

刚刚讲到了压缩过的 32 位地址是基于 64 位的 heap base 地址的。而在 Zero-Based 压缩指针中,64 位的 heap base 地址是重新分配的虚拟地址 0。这样就可以不用存储 64 位的 heap base 地址了。

Escape analysis 逃逸分析

最后,要讲的是逃逸分析。什么叫逃逸分析呢?简单点讲就是分析这个线程中的对象,有没有可能会被其他对象或者线程所访问,如果有的话,那么这个对象应该在 Heap 中分配,这样才能让对其他的对象可见。

如果没有其他的对象访问,那么完全可以在 stack 中分配这个对象,栈上分配肯定比堆上分配要快,因为不用考虑同步的问题。

我们举个例子:

  public static void main(String[] args) {example();
  }
  public static void example() {Foo foo = new Foo(); //alloc
    Bar bar = new Bar(); //alloc
    bar.setFoo(foo);
  }
}

class Foo {}

class Bar {
  private Foo foo;
  public void setFoo(Foo foo) {this.foo = foo;}
}

上面的例子中,setFoo 引用了 foo 对象,如果 bar 对象是在 heap 中分配的话,那么引用的 foo 对象就逃逸了,也需要被分配在 heap 空间中。

但是因为 bar 和 foo 对象都只是在 example 方法中调用的,所以,JVM 可以分析出来没有其他的对象需要引用他们,那么直接在 example 的方法栈中分配这两个对象即可。

逃逸分析还有一个作用就是 lock coarsening。

为了在多线程环境中保证资源的有序访问,JVM 引入了锁的概念,虽然锁可以保证多线程的有序执行,但是如果实在单线程环境中呢?是不是还需要一直使用锁呢?

比如下面的例子:

public String getNames() {Vector<String> v = new Vector<>();
     v.add("Me");
     v.add("You");
     v.add("Her");
     return v.toString();}

Vector 是一个同步对象,如果是在单线程环境中,这个同步锁是没有意义的,因此在 JDK6 之后,锁只在被需要的时候才会使用。

这样就能提升程序的执行效率。

本文作者:flydean 程序那些事

本文链接:http://www.flydean.com/jvm-performance-enhancements/

本文来源:flydean 的博客

欢迎关注我的公众号: 程序那些事,更多精彩等着您!

退出移动版