vlambda博客
学习文章列表

翻-Java HotSpot虚拟机的性能提升

  • 紧凑的字符串
  • 多层编译
  • 普通对象指针压缩
  • Graal:一个机遇java 的JIT编译器
  • 预编译
  • 从零开始压缩普通对象指针
  • 逃逸分析

紧凑的字符串

紧凑的字符串特征引入一个内部的实现 减少字符串的空间使用。

从不同的应用数据显示,字符串是java堆使用主要成员,而且大量的String对象仅仅包含一个拉丁字母,这样的字母只需要一个字节存储。结果导致存放String对象的字符数组有近一半的空间未使用。紧凑的字符串在Java SE9被引入,降低内存使用,减少垃圾收集器的触发。如果你在应用中观察到性能衰退的问题可以关闭它。

紧凑的字符串特征并未引入新得API和接口,他通过修改String内部实现,从UTF-16(两个字节)字符数组变成一个字节数组和一个而外属性来描述字符串的编码方式。其他字符串相关类,如AbstractStringBuild,StringBuilder,和 StringBuffer 都被更新为相似的内部实现。

在Java SE 9中,紧凑字符串特征是默认开启的,因此String类用一个字节存储一个字符,如同一个拉丁字母一样。当这个特征被关闭时,String类一个字符需要两个字节存储,如同UTF-16编码一样,另外HotSpot字符串函数也变成使用UTF-16编码。

多层编译

多层编译在Java SE7 被引入,给服务器虚拟机带来客户端虚拟机的高效启动。没有多层编译,一个服务器虚拟机使用解释器来收集发送给编译器的关于方法的分析信息。使用多层编译,服务器虚拟机也使用客户端编译器来生成方法的编译版本,这些方法收集了他们自身的分析信息。编译的代码比使用解释器大幅度提升了效率,同时在分析阶段程序有更好的执行效率。通常,启动速度比客服端启动更快,因为最终通过服务器编译器的代码生成可能在应用初始化阶段已经生成。多层编译比常规服务虚拟机有更好的性能峰值,因为更快的分析阶段允许更长的分析周期,产生最优化。

服务器虚拟机默认下开启多层编译,64位和压缩普通对象指针也被支持。你可以使用-XX:-TieredCompilation命令关闭多层编译。

为了容纳多层编译器生成的额外分析代码,默认的代码缓存大小着增加5x,为了高效的组织和管理大空间,“代码分块存储”被使用。

代码分块存储

代码缓存是内存的一块区域,Java虚拟机存储原生代码的区域。她被组织成一个堆数据结构,位于在一个连续的内存块之上。

代替一整块的代码堆,代码缓存被分隔成多个块。每一块都包含一个特殊类型的编译代码。分块更好的控制了JVM内存使用,编译方法更短的扫描时间,大幅度降低代码缓存的分块,更好的提高而性能。


翻-Java HotSpot虚拟机的性能提升

Graal:基于java的JIT编译器

结合Java HotSpot虚拟机,Graal是一个由java编写的高性能、最优化、实时的编译器,一个可以定制的动态编译器,你可以用java调用它。

Graal一些特征和优点:
  • 灵活的预测优化
  • 更好的嵌入
  • 部分逃逸分析
  • 更好的支持java工具和IDE
  • Metacircular 方法:允许更严格的代码生成控制

你可以在静态上下文中使用Graal,静态的提前编译就是基于Graal 框架实现的。

Graal是JDK内部模块构建的一部分,他通过JVM编译器接口与JVM进行沟通。JVM编译器接口同样是JDK构建的一部分。

开启Grall作为JIT编译器,使用下面的命令:
  
    
    
  
-XX:+UnlockExperimentalVMOptions _xx:+UseJVMCICompiler

预编译

预编译(AOT)提高了大的和小的应用启动时间,在启动虚拟机之前将代码编译成原生代码。

通过快速的实时编译,可以节约编译大型java程序的时间。同时,AOT解决了java方法被重复解释消耗性能的问题。

AOT编译器中使用了一个新的jaotc工具,jaotc的语法如下所示:
翻-Java HotSpot虚拟机的性能提升

jaotc是Java安装的一部分,类似javac.

在应用执行时指定生成AOT库:
翻-Java HotSpot虚拟机的性能提升

当JVM启动时,AOT初始化代码寻找被AOTLibrary标记的库,如果没有找到AOT将在JVM实例中关闭。

压缩普通对象指针

Java HotSpot中的普通对象指针(oop),是一个指向对象的管理指针。典型的,oop和原生机器指针有相同的大小,在LP64系统上是64字节。在ILP32系统上,最大堆大小少于4千兆字节,这对于很多应用时不够的。在LP64系统上,给定程序使用的堆大概是1.5倍于iLP32系统。这一需求是由于管理指针扩展大小造成的,内存是便宜的,但是现在内存和宽带供应不足。因此显著提高堆的大小和仅仅超过4千兆限制是不可取的。

java堆中的管理指针指向对象,两端对齐8个字节地址。压缩oop代表着管理指针。。。 (Compressed oops represent managed pointers (in many but not all places
in the Java Virtual Machine (JVM) software) as 32-bit object offsets from the 64-bit
Java heap base address)。
因为是对象的偏移而不是字节偏移,oop能使用的地址提升到40亿个,或堆的大小提高到32千兆字节。要使用他们,必须将他们按照8的倍数进行缩放,并将java堆基于地址他娘到压缩的普通对象指针中,以查找它们所引用个的对象。

术语“decode”指一种操作:一个32字节压缩oop转换成一个64字节原生地址并添加到管理堆中。“encode” 表示相反的操作。
翻-Java HotSpot虚拟机的性能提升

从零开始压缩普通对象指针

翻-Java HotSpot虚拟机的性能提升

逃逸分析

使用逃逸分析技术,HotSpot服务器编译器可以分析行对象的使用范围和决定是否在java堆上分配。

在Java SE 6u23及之后支持和默认开启逃逸分析。

HotSpot服务编译器实现了flow-insensitive逃逸分析算法:
翻-Java HotSpot虚拟机的性能提升

一个对象逃逸状态基于逃逸分析,是下面转态中的一种:

  • GlobalEscape:对象逃脱了方法和线程,例如:一个对象存储在一个静态域,存储在一个已逃脱的对象属性上,或者作为当期那方法的返回结果。
  • ArgEscape:对象作为参数传递或参数引用,但不会在调用是全局逃逸,这种转态是通过分析被调用方法的字节码定义的。
  • NoEscape:对象是一个标量可替换的对象,意味着可在生成的代码中被移除。

逃逸分析之后,服务编译器消除哪些NoEscape状态的对象和生成代码的相关锁。服务编译器也或消除 没有全局逃逸的对象的锁。不会对没有全局逃逸的堆想用栈分配替换堆分配。

下面例子展示一些冠以逃逸分析的脚本: