2021年毕昇 JDK 的第一个重要更新来了

来源: 投稿
作者: openEuler
2021-04-16

2021 年 3 月 31 日,毕昇 JDK update 版本正式发布,下载方式见文末参考文档[1][2],该版本在同步 OpenJDK 社区 8u282/11.0.10 的基础上,还包含如下更新,为用户提供高性能、可用于生产环境的 OpenJDK 发行版。

  1. G1 Full GC 优化(毕昇 JDK 11)

  2. LazyBox 特性(毕昇 JDK 11)

  3. 提供鲲鹏硬件加速的 KAEProvider(毕昇 JDK 8)

  4. Jmap 并发扫描优化(毕昇 JDK8, 毕昇 JDK11)

  5. Bug fixes

G1 Full GC 优化原理及使用

G1 GC(Garbage First Garbage Collector)是一款面向服务端、低延迟的垃圾收集器.当 G1 进行 Full GC 时,会对整个堆进行清理,耗时较长,所以如何调优 Full gc 一直是开发人员奋斗的目标之一。G1 Full GC 在当前的实现中,主要包括如下四个阶段:标记存活对象、计算目标对象的位置、更新引用的位置、移动对象完成压缩,由于 G1 对存活对象较多的 region 进行回收时(G1 是以 region 为单位来管理 java 堆的),需要移动较多的对象,却只能回收较少的内存,效率低下,因此可以在 G1 的第四阶段不对此 region 进行压缩,减少处理的时间。

优化原理

针对上面这种情况,可以通过如下步骤,对 Full GC 过程进行优化:

  • 在标记阶段,对每个 region 存活的字节数进行统计

  • 在计算目标对象位置时,把存活比例较高的 region 加入到不进行压缩的 region 集合

  • 更新引用的位置与现有情况保持一致

  • 在压缩时跳过不进行压缩的 region 集合 为此,我们为用户提供如下参数来使用该特性:

参数 说明
G1FullGCNoMoving 打开选项后,当 G1 进行 Full GC 时,将会启用此优化功能。此选项默认关闭
G1NoMovingRegionLiveBytesLowerThreshold 指定不进行压缩的 region 中存活字节占比的最小值。即当 region 中的存活字节数占比超过设定的值时,将该 region 加入到不进行压缩的 region 集合。默认值为 98

在存活对象占比较高的 Full GC 中,使用此特性将会减少 Full GC 的停顿时间。

性能测试

Dacapo 是一种可以对编程语言、内存管理和计算机体系结构进行测试的 Java 基准测试工具,由以下套件组成:avrora、batik、eclipse、fop、h2、jython、pmd、tomcat、daytrader、xalan、lucene 等,其中 h2 是一种类似于 JDBCbench 的内存基准测试,针对银行应用程序的模型执行许多事务,详见[3]。这里对 h2 进行测试。

测试环境:

  • CPU: Kunpeng 920

  • OS: CentOS 7.6

  • JDK: 毕昇 JDK 11.0.10

测试脚本 script.sh 如下,执行./script.sh h2,然后即可统计 Full GC 停顿时间。

#!/bin/bash
export java=$JAVA_HOME/bin/java
export java_options="-Xmx1g -Xms1g -XX:ParallelGCThreads=4"
echo $java $java_options

task=$1
for i in `seq 30`
do
  echo ">>>>>>>>>>>>>>>>>>>> base $i <<<<<<<<<<<<<<<<<<<<<<"
  $java $java_options -Xlog:gc*=info:file=h2-base/gc-$task-base-$i.log -jar dacapo-9.12-bach.jar -t 4 --iterations 5 --size huge --no-pre-iteration-gc $task
done
for i in `seq 30`
do
  echo ">>>>>>>>>>>>>>>>>>>> opt $i <<<<<<<<<<<<<<<<<<<<<<"
  $java $java_options -XX:+G1FullGCNoMoving -Xlog:gc+phases=trace,gc=info,gc+heap=info,gc+task=info:file=h2-opt/gc-$task-opt-$i.log -jar dacapo-9.12-bach.jar -t 4 --iterations 5 --size huge --no-pre-iteration-gc $task
done

测试结果:其中 Percentile 为箱线图中的概念,10% 即表示将数据从小到大排序,第 10%个数据提升 11.25%.

测试结果

结论:从图中可以看到,优化之后可以将 G1 Full GC 的停顿时间降低 3%~11%。

该优化的一部分已合入 OpenJDK 社区[4],剩余部分正在推进中。

LazyBox 特性介绍

Java 为每种基本类型提供了对应的包装类型,将基本类型转换为包装类型在 Java 中称为装箱。由于泛型的存在,在 Java 中会频繁的进行装箱拆箱操作,带来许多额外的开销,典型例子如下:

<T extends Number>
int add(T a, T b) {
  return a.intValue() + b.intValue();
}

LazyBox 特性通过在 Hotspot C2 中推迟装箱的时机,使 C2 只进行必要的装箱,减少装箱操作,提高 C2 生成代码的执行效率。

场景分析

对于某些装箱后的值,在某些路径下并不会被用到,但还是会忍受装箱带来的开销。比如下面代码中的 integer 对象:

  int sum = 0;
  for (int i = 0; i < 100; i++) {
    Integer integer = Integer.valueOf(299);
        if (i < 1) {  //冷路径
             blackhole.consume(integer); //逃逸
             sum+=2;
        } else {     //热路径
             sum+=integer.intValue(); //拆箱
        }
  }
  return sum;

当 i>=1 时,代码只是想获取 integer 对象的 int 值,此时没有必要对 integer 进行装箱,在此中场景下,使用 LazyBox 可以极大的提高性能。只有等到确实需要装箱时,再在 C2 中插入装箱操作。

不过由于在每次需要装箱时,都会插入装箱操作,所以在某些场景下可能会导致对象不一致。例如下面的场景:

Data data = new Data();

int value = 299;
Integer a = Integer.valueOf(value);
data.a = a;
data.b = a;

System.out.println(data.a == data.b); //开启LazyBox后为false

由于将 a 赋值给 data 中的 a 和 b 之前,都在 C2 中插入了装箱操作,所以导致 data 中的 a 和 b 为不同的对象,所以在使用 LazyBox 时,用户需要留心这一点。

相关参数如下:

参数 说明
LazyBox 打开 LazyBox 特性,该选项为实验选项,需要开启-XX:+UnlockExperimentalVMOptions, 同时由于 LazyBox 依赖 AggressiveUnBoxing 优化,所以还需开启-XX:+AggressiveUnboxing 选项
PrintLazyBox 打印 LazyBox 状态,用于开发者调试

用户可通过如下方式使用 LazyBox 特性:

-XX:+UnlockExperimentalVMOptions -XX:+AggressiveUnboxing -XX:+LazyBox

性能测试

测试环境:

  • CPU: Kunpeng 920

  • OS: openEuler 20.03

  • JDK: 毕昇 JDK 11.0.10

本实验采用工业级测试套件 SPECPower 进行测试,在测试过程中进行了绑核,测试结果表明:结合 毕昇 JDK 以前的优化,相比 OpenJDK 可以提升 SPECPower 10%.

鲲鹏硬件加速的 KAEProvider

KAE(Kunpeng Accelerate Engine)加解密是鲲鹏 920 处理器提供的硬件加速方案,可以显著降低处理器消耗,提高处理器效率[5].毕昇 JDK 为 Java 用户提供 KAEProvider,使 Java 开发人员可以直接使用硬件带来的加速效果,提升加解密效率。

实现

Java 通过 JCA(Java Cryptography Architecture)为开发者提供了良好的接口,开发者只需要实现 SPI(Service Provider Interface)接口,并在 CSP(Cryptographic Service Provider, 下文简称 Provider)中进行注册,即可让用户使用自己的加解密实现。举个例子:当用户通过MessageDigest.getInstance("SHA-256")获取 message digest 对象时,JCA 会依次搜索注册的 Provider,直到找到一种实现为止,大体过程如下[6]:

用户可通过 java.security 文件指定各个 Provider 的优先级,或者在代码中通过Security.insertProviderAt(Provider, int)接口指定.也可以在获取摘要对象时手动指定从哪个 Provider 中获取,如MessageDigest.getInstance("SHA-256","KAEProvider"),这种情况下,JCA 将优先使用用户指定的 Provider.

毕昇 JDK 当前实现了 MessageDigest(MD5,SHA256,SHA384)Cipher(AES-ECB,AES-CBC,AES-CTR,RSA)KeyPairGenerator(RSA)HMac 等 SPI,并在 KAEProvider 中进行了注册,其它算法会在后续版本合入,用户可通过如下方式来使用 KAEProvider.

  • 方式 1: 使用 Security API 添加 KAE Provider ,并设置其优先级。

Security.insertProviderAt(new KAEProvider(), 1);
  • 方式 2:修改 jre/lib/security/java.security 文件,添加 KAE Provider,并设置其优先级。

security.provider.1=org.openEuler.security.openssl.KAEProvider
security.provider.2=sun.security.provider.Sun
security.provider.3=sun.security.rsa.SunRsaSign
security.provider.4=sun.security.ec.SunEC
security.provider.5=com.sun.net.ssl.internal.ssl.Provider
security.provider.6=com.sun.crypto.provider.SunJCE
security.provider.7=sun.security.jgss.SunProvider
security.provider.8=com.sun.security.sasl.Provider
security.provider.9=org.jcp.xml.dsig.internal.dom.XMLDSigRI
security.provider.10=sun.security.smartcardio.SunPCSC
security.provider.11=sun.security.mscapi.SunMSCAPI

性能测试

JMH(Java Microbenchmark Harness)是 OpenJDK 社区提供的一种对 Java 进行 benchmark 测试的工具,使用方式见[7].毕昇 JDK 已将对应的 JMH 测试用例合入了 openEuler 社区[8],这里采用对应的用例进行测试。

测试环境:

  • CPU: Kunpeng 920

  • OS: openenuler 20.03

  • KAE: v1.3.10 ,下载链接见[9]

  • JDK: 毕昇 JDK 1.8.0_282

测试结果如下,可以看到,在使用 KAEProvider 后,RSA 加解密性能明显提升。

up-50ad72b1030f97dd3f0f9403e26150ceea2.png

测试结果

结论:相比 JDK 默认 Provider 提供的 RSA 加解密,当密钥长度为 2048 位时,KAEProvider 可以提升 53%~80%,当密钥长度为 4096 位时,KAEProvider 可以提升 70%~83%。

Jmap 并发扫描介绍

当前 jmap 采用单线程对 java 堆进行扫描,扫描速度较慢,并且当对超大堆进行扫描时(大于 200G),容易引起系统卡死。因此可以通过多线程来进行扫描,减少卡顿时间。

实现

毕昇 JDK 将社区高版本的 jmap 优化回合到此次发布中,为 jmap -histo 选项增加指定并发线程数的 parallel 参数,使 jmap 可以使用多线程对堆进行扫描,有效提高 jmap 的扫描效率,减少扫描时间。具体实现原理可参考[10]。用户可通过在 jmap -histo 后增加 parallel 参数来使用此特性,如下所示:

  • jmap -histo:live,parallel=3 pid : 指定并发线程数为 3

  • jmap -histo:live,parallel=0 pid : 使用当前系统可支持的并发线程数(-XX:ParallelGCThreads)

  • jmap -histo:live,parallel=1 pid : 使用原有的串行扫描

当前的实现只支持 G1 和 ParallelGC,后续版本将支持 CMS.

Bug fixes

除了上面介绍的一些特性外,毕昇 JDK 还合入了社区高版本中的一些 bug fix 和优化的 patch,为用户提供稳定、高性能的毕昇 JDK。具体回合 patch 如下:

  • JDK8

    • 8231841: AArch64: debug.cpp help() is missing an AArch64 line for pns

    • 8254078: DataOutputStream is very slow post-disabling of Biased Locking

    • 8168996: C2 crash at postaloc.cpp

    • 8140597: Forcing an initial mark causes G1 to abort mixed collections

    • 8214418: half-closed SSLEngine status may cause application dead loop

    • 8259886: Improve SSL session cache performance and scalability

  • JDK11

    • 8254078: DataOutputStream is very slow post-disabling of Biased Locking

    • 8217918: C2: -XX:+AggressiveUnboxing is broken

 

参考

  • [1] Bishengjdk8下载:https://mirrors.huaweicloud.com/kunpeng/archive/compiler/bisheng_jdk/bisheng-jdk-8u282-linux-aarch64.tar.gz

  • [2] Bishengjdk11下载:https://mirrors.huaweicloud.com/kunpeng/archive/compiler/bisheng_jdk/bisheng-jdk-11.0.10-linux-aarch64.tar.gz

  • [3] dacapo介绍:http://dacapobench.org

  • [4] 8263495: Gather liveness info in the mark phase of G1 full gc:https://github.com/openjdk/jdk/commit/8c8d1b31

  • [5] 鲲鹏加速引擎介绍:https://support.huaweicloud.com/devg-kunpengaccel/kunpengaccel_16_0002.html

  • [6] Java Cryptography Architecture (JCA) Reference Guide:https://docs.oracle.com/javase/8/docs/technotes/guides/security/crypto/CryptoSpec.html

  • [7] jmh介绍:https://github.com/openjdk/jmh

  • [8] KAEProvider jmh用例:https://gitee.com/openeuler/bishengjdk-8/tree/master/jdk/test/micro/org/openeuler/bench/security/openssl

  • [9] KAE下载链接:https://github.com/kunpengcompute/KAE/releases

  • [10] 8239290:Add parallel heap iteration for jmap -histo:https://bugs.openjdk.java.net/browse/JDK-8239290

展开阅读全文
10 收藏
分享
加载中
27 评论
10 收藏
分享
返回顶部
顶部