2021 年 3 月 31 日,毕昇 JDK update 版本正式发布,下载方式见文末参考文档[1][2],该版本在同步 OpenJDK 社区 8u282/11.0.10 的基础上,还包含如下更新,为用户提供高性能、可用于生产环境的 OpenJDK 发行版。
-
G1 Full GC 优化(毕昇 JDK 11)
-
LazyBox 特性(毕昇 JDK 11)
-
提供鲲鹏硬件加速的 KAEProvider(毕昇 JDK 8)
-
Jmap 并发扫描优化(毕昇 JDK8, 毕昇 JDK11)
-
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 加解密性能明显提升。
测试结果
结论:相比 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
{{m.name}}
原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/72241.html