历史背景
2018 年 9 月 26 日,Oracle 官网发表 Java 11 正式公布。这个版本中一共蕴含 17 个 JEP(JDK Enhancement Proposals,JDK 加强提案)。
JDK 11 是一个长期反对版本(LTS, Long-Term-Support),在编写本文的工夫节点下和 JDK17 一样被用于编写我的项目代码的支流版本。
本文联合了各方材料整顿出 JDK11 的新个性,工作上应用高版本 JDK 的机会的确不多,然而本人体验的过程中的确切身体会到 JDK 降级的益处。
降级 JDK11 的意义
- 大量的新个性、Bug 修复,例如,容器环境反对,GC 等根底畛域的加强。
- 平安协定更新,比方废除 TLS1.2 中不平安的加密算法,局部反对 TLS1.3 协定。
- JVM 的零老本优化。
- 划时代的 垃圾收集器 ZGC,要害目标是实现 GC 工夫不超过 10ms。
-
G1 垃圾收集器的收费降级
- 并行的 Full GC(JDK11 之前 Oracle 没有思考过 G1 须要并行 Full GC)
- 疾速的 CardTable 扫描
- 自适应的堆占用比例调整(IHOP)
- 在并发标记阶段的类型卸载
-
JVM 自带工具加强
- JEP 328: Flight Recorder(JFR),商用版工具 JFR 转为开源。
- JEP 331: Low-Overhead Heap Profiling 实现底层老本堆剖析 JVM 扩大
-
TLS 协定精简
- 欠缺 Java 规范 HTTP 类库上的扩大能力。
- JEP 332: Transport Layer Security (TLS) 1.3,齐全反对TLS 1.3(协定局部局部反对,加密算法局部根本满足要求)。
-
HTTP/2 Client API
- 欠缺了 Java 语言本身进行 Http Client 的能力
- 等同于把 HttpClient 官网化(好听点就是搬来了)
- <s> 只有不写作者,谁也不晓得本人写的还是抄的谁的 </s>
-
废除 Nashorn JavaScript 引擎
- 实际上是全力推动 Graal VM 虚拟机的开发。
降级内容
上面就是依据 ”JEP(JDK Enhancement Proposals,JDK 加强提案)” 进行介绍。对于原始的提案名称,这里放到了“附录”局部。
新个性
JEP 309: Dynamic Class-File Constants 类文件新构造
属于 JVM 层面的优化,简略理解即可。上面是收集到的的一些国外网站的解释:
Dynamic class-file constants
JEP 309 extends the existing Java class-file format, creating CONSTANT_Dynamic. This is a JVM feature and doesn't depend on the higher software layers. The loading of CONSTANT_Dynamic delegates the creation of a bootstrap method. When comparing it to the invokedynamic call, you will see how it delegates linking to a bootstrap method.
One of the main goals of dynamic class-file constants is to make it simple to create new forms of materializable class-file constants, which provides language designers and compiler implementers with broader options for expressivity and performance. This is achieved by creating a new constant pool form that can be parameterized using a bootstrap method with static arguments.
上面是简略概括和理解:
动静类文档常量:JEP 309 扩大了现有的 Java 类文档格局,创立了 CONSTANT_Dynamic。这是 JVM 性能,不依赖于更高的软件层。加载 CONSTANT_Dynamic 委派疏导办法的创立。将其与动静调用进行比拟时,将看到它如何委托链接到疏导办法。动静类文档常量的次要指标之一,是使创立可具体化的类文档常量的新模式 变得简略,这为语言设计者和编译器实现者提供了更宽泛的表白性和性能选项。
这是通过创立一个新的常量池模式来实现的,该表单能够应用带有动态参数的疏导办法进行参数化。
其指标是升高开发新模式的可实现类文件束缚带来的老本和烦扰
这里的介绍还是不是很懂,所以我又找了一篇英文博客,它论述了从 JDK7 呈现的 invokeDynamic 指令到 JDK11 的新指令呈现的含意。
入手 Java 11 的常量动静 – Java 代码极客 – 2023 (javacodegeeks.com)
Java 的指令集新增根本都是大动作,作为比照集体联想到 JDK7 呈现的新指令invokedynamic,周志明的《深刻了解 JVM 虚拟机》中介绍了这个指令,并且 JDK8 以此为根底实现了 Lambada 语法。
上面是原书中无关 invokedynamic 的介绍:
invokedynamic 指令是在 JDK 7 时退出到字节码中的,过后的确只为了做动静语言(如 JRuby、Scala)反对,Java 语言自身并不会用到它。而到了 JDK 8 时代,Java 有了 Lambda 表达式和接口的默认方 法,它们在底层调用时就会用到 invokedynamic 指令,这时再提动静语言反对其实已不齐全切合,咱们 就只把它当个代称吧。
所以从 JDK11 的这一次变动来看,又是一次相似 invokedynamic 的大更新?
JEP 323: Local-Variable Syntax for Lambda Parameters
留神是“语法糖”,理论 Class 字节码中会被辨认为正确的数据类型,并不是变为弱类型语言了,Java 仍旧是 强类型语言。
这个改良次要目标实际上是侧重于 Lambada 的缺点改善,上面通过理论代码演示进行介绍:
var javastack = "javastack";
System.out.println(javastack);
局部变量类型推断指的是右边的类型间接应用 var 定义,而不必写具体的类型,编译器能依据左边的表达式主动推断类型,如下面的 String。
var javastack = "javastack";
就等于:
String javastack = "javastack";
var 的变量还对于 Lambada 语法进行了更多改良,申明隐式类型的 lambda 表达式的形参时容许应用 var。应用 var 的益处是在应用 lambda 表达式时 给参数加上注解。
比方咱们常见的隐式类型的写法如下,JDK11 之前都只能是这样的“隐式类型”写法:
(x, y) -> x.process(y)
有申明 var 之后,从外表上看能够让变量“可读性”变强了一点:
(var x, var y) -> x.process(y)
实际上有了“变量类型”之后,之前 Java 版本无奈给 Lambada 表达式的隐式变量进行标记的问题就失去解决:
(@Deprecated var x, @Nullable var y) -> x.process(y)
另外这里扩大一下 JDK 的 非 Lambada 的申明隐式类型 ,这是属于 J ava10 呈现的货色,具体含意上面的代码一看便知:
var foo = new Foo();
for (var foo : foos) {...}
try (var foo = ...) {...} catch ...
这样的写法能够使得咱们扭转对象的时候更小概率改变遍历的代码,比方之前须要应用 DtoA 做遍历,当初外面的数据包装为 Dtob 了,这时候只须要动 for 循环外面的内容即可。
JEP 329: ChaCha20 and Poly1305 Cryptographic Algorithms 实现 RFC7539 中指定的 ChaCha20 和 Poly1305 两种加密算法, 代替 RC4
浏览这一节内容倡议读者先有个印象:Java 11(2018 年 9 月 26 日)和 TLS1.3 规范公布(2018 年 12 月 20 日 · TLS1.3 正式版公布)
- ChaCha20:是 Google 设计的另一种加密算法,密钥长度固定为 256 位,纯软件运行性能要超过 AES,在 ARMv8 引擎降级之后,AES 也飞速晋升导致 ChaCha20 被反超。所以当初来看尽管劣势不大,然而仍然不错的一个算法。
- Poly1305:是由 Daniel Bernstein 创立的音讯身份验证代码(MAC)。Poly1305 采纳 32 字节的一次性密钥和一条音讯来生成 16 字节的标记。而后应用该标记对音讯进行身份验证。每当 Poly1305 用作 MAC 算法时,都须要为完整性密钥调配 256 位。(引自:Poly1305)
题外话:为什么 ChaCha20 和 Poly1305 在 TLS1.3 中合并为一个算法 ChaCha20-Poly1305?
其实很好了解,就好比两个运动员,一个专一短跑,另一个善于长跑,一合体不就“无敌”了么,ChaCha20-Poly1305 就是两个算法舍短取长的成果,并且独自离开也是目前十分平安的算法。
RFC7539 也就是 IETF 制订的 TLS1.2 的协定规范名称。RC4 在很早之前就曾经被证实存在破绽,前面曾经被废除了。
看到这部分的时候,集体联想到之前学习的 [[HTTP 面试题 – TLS1.3 首次解读]] 的内容。TLS1.3 不是把这两个合并成 ChaCha20-Poly1305 了么,并且它是 TLS1.3 IETF 指定的 AEAD 算法,JDK 又是如何对待这个问题的呢?
集体抱着这个疑难查阅了 JDK 官网 issue 的,最终在上面的连贯中找到了答案:
[[JDK-8140466] ChaCha20 and Poly1305 TLS Cipher Suites – Java Bug System (openjdk.org)](https://bugs.openjdk.org/browse/JDK-8140466)
Fix request (11u)
I would like to backport this to 11u because of Chacha20 and Poly1305 cipher suite SHOULD be implemented for TLSv1.3 according to rfc8446
Original patch applies almost clean except for the CipherSuite.java test - the list of cipher suites was reordered by JDK-8210632
Also, CheckCipherSuites.java and CipherSuitesInOrder.java tests are updated to support CHACHA20 cipher suites.
RFR: https://mail.openjdk.java.net/pipermail/jdk-updates-dev/2021-June/006644.html
翻译过去:
我想将其向后移植到 11u,因为 Chacha20 和 Poly1305 明码套件应该依据 rfc8446 为 TLSv1.3 实现 原始补丁(差不多)能够失常利用,除了 CipherSuite.java 测试 – 明码套件列表由 JDK-8210632 从新排序 此外,CheckCipherSuites.java 和 CipherSuitesInOrder.java 测试也进行了更新,以反对 CHACHA20 明码套件。
当然顺带也查到了 JDK 代码改变提交地址,感兴趣代码改变细节能够看这一段代码:https://hg.openjdk.org/jdk/jdk/rev/720fd6544b03
ChaCha20-Poly1305 算法,它是 TLS 1.3 反对的 AEAD 算法。
最初是 JDK 的一段案例程序:
/**
* XECPublicKey 和 XECPrivateKey
* RFC7748 定义的秘钥协商计划更高效, 更平安. JDK 减少两个新的接口
*/
public class XECPublicKeyAndXECPrivateKeyTest {public static void main(String[] args) throws NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeySpecException, InvalidKeyException {generateKeyPair();
}
private static void generateKeyPair() throws NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeySpecException, InvalidKeyException {KeyPairGenerator kpg = KeyPairGenerator.getInstance("XDH");
NamedParameterSpec paramSpec = new NamedParameterSpec("X25519");
kpg.initialize(paramSpec);
KeyPair kp = kpg.generateKeyPair();
PublicKey publicKey = generatePublic(paramSpec);
generateSecret(kp, publicKey);
}
private static void generateSecret(KeyPair kp, PublicKey pubKey) throws InvalidKeyException, NoSuchAlgorithmException {KeyAgreement ka = KeyAgreement.getInstance("XDH");
ka.init(kp.getPrivate());
ka.doPhase(pubKey, true);
byte[] secret = ka.generateSecret();
// [B@10a035a0
System.out.println(secret);
}
private static PublicKey generatePublic(NamedParameterSpec paramSpec) throws NoSuchAlgorithmException, InvalidKeySpecException {KeyFactory kf = KeyFactory.getInstance("XDH");
BigInteger u = new BigInteger("111");
XECPublicKeySpec pubSpec = new XECPublicKeySpec(paramSpec, u);
PublicKey pubKey = kf.generatePublic(pubSpec);
return pubKey;
}
}
JEP 332: Transport Layer Security (TLS) 1.3
理论状况能够看看已经负责过 Java 的平安开发工作的大佬文章:
An example of TLS 1.3 client and server on Java | The blog of a gypsy engineer。
集体已经在 [[HTTP 面试题 – TLS1.3 首次解读]] 对这位大佬做了一些简略论述,并且倡议看这部分内容前对于 TLS1.3 肯定的理解,可能会有更深的印象。
依据作者所说就是 Java11 实现是实现了 TLS1.3,然而是个“残血版”,比方不反对上面的个性:
- 0-RTT data 0-RTT 数据
- Post-handshake authentication 握手后认证
- Signed certificate timestamps (SCT) 签名证书工夫戳 (SCT)
- ChaCha20/Poly1305 cipher suites (targeted to Java 12) ChaCha20/Poly1305 明码套件(针对 Java 12)
- x25519/x448 elliptic curve algorithms x25519/x448 椭圆曲线算法(针对 Java 12)
- edDSA signature algorithms edDSA 签名算法(针对 Java 12)
然而从好的方面来看,Java11 曾经齐全具备兼容 TLS1.3 的条件,因为这些不反对的仅仅是小局部内容,实际上大部分 TLS1.3 指定的最新加密套件都是反对的,使用者只须要改改“动态参数”,即可实现 TLS1.2 到 TLS1.3 的降级兼容。
提醒:为了避免误会,这里集体有必要补充一下。ChaCha20/Poly1305 并不是说 JDK12 才真正实现 ChaCha20 和 Poly1305,
ChaCha20/Poly1305
不是 ChaCha20+Poly1305,尽管实质上是交融到一块,然而实际上一个全新的算法。
另外这一波也怪不到 Oracle,谁让 TLS1.3 比 JDK11 晚公布 3 个月呢。
JEP 321: HTTP Client (Standard) 规范 Java 异步 HTTP 客户端
这是自 JDK9 被引进孵化,直到 JDK11 才正式可用的解决 HTTP 申请的的 HTTP Client API,如大节题目所说,它反对同步和异步发送。
因为是逾越多个大版本的改建,Java11 规范 Java 异步 HTTP 客户端最终被叫做 HTTP/2 Client。它定义了一个全新的实现了 HTTP/ 2 和 WebSocket 的 HTTP 客户端 API,并且能够取代 HttpURLConnection。
过来 HttpURLConnection 被人诟病的点如下:
- API 早于 HTTP1.1 过于形象。(HTTP1.0 是草稿协定,没有被标准化)
- API 难懂,编写的代码难以保护。
- 适度设计,很多反对的协定到当初根本只剩下 HTTP。
java.net 包中能够找到这些 API,然而我并没有看到作者是谁 =-=。
public class JdkHttpClient {public static void main(String[] args) throws IOException, InterruptedException {var request = HttpRequest.newBuilder()
.uri(URI.create("https://cn.bing.com/?mkt=zh-CN"))
.GET()
.build();
var client = HttpClient.newHttpClient();
// 同步
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
// 异步
client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
.thenApply(HttpResponse::body)
.thenAccept(System.out::println);
}/**
截取局部内容://]]></script><script type="text/rms">//<![CDATA[var sj_appHTML=function(n,t){var f,e,o,r,i,s,h;if(t&&n){var c="innerHTML",l="script",a="appendChild",v="length",y="src",p=sj_ce,u=p("div");if(u="<br>"+t,f=u.childNodes,u.removeChild(f[0]),e=u.getElementsByTagName(l),e)for(o=0;o<e[v];o++)r=p(l),i=e[o],i&&(r.type=i.type=="module"||i.type=="importmap"?
Process finished with exit code 0
*/}
JDK 官网给 JDK 引入 HTTP Client API 了,当前还有必要用 Apache 的 HttpClient 工具包么?
JEP 331: Low-Overhead Heap Profiling 低成本的堆剖析
官网阐明:JEP 331: Low-Overhead Heap Profiling (openjdk.org)
Provide a low-overhead way of sampling Java heap allocations, accessible via JVMTI.
官网的介绍是说通过 JVMTI 的sampling提供低堆内存开销的剖析工具。
为什么有了如 Java Flight Recorder、YourKit 和 VisualVM 等十分实用的 JVM 剖析工具,JDK 官网还须要提供接口扩大堆剖析的伎俩呢?
One piece of information that is lacking from most of the existing tooling is the call site for particular allocations. Heap dumps and heap histograms do not contain this information.
要害起因在于大多数的剖析工具短少particular allocations
,这里的特定调配在集体的了解是“堆外”内存的调配,因为这部分是超出 JVM 管辖范畴内间接通过操作系统底层往物理内存调配。
古代的消息中间件 Kafka、RocketMq 等底层大量应用了 页缓存 这个货色,并且进行了大量的间接内存调配,不巧的是这部分内容刚好是 JVM 堆内存剖析工具的痛点。所以 JDK 开发扩大这些接口也是时代倒退的趋势之一。
This proposal mitigates these problems by providing an extensible JVMTI interface that allows the user to define the sampling interval and returns a set of live stack traces.
通过提供一个可扩大的 JVMTI 接口来缓解这些问题,该接口容许用户定义采样距离并返回一组实时堆栈跟踪。
因为用的是 JVM 对外接口,所以这个接口是 C ++ 写的,在 Use-case example
外面有很多的接入案例代码,如果不是要开发 JVM 堆剖析工具,咱们理解到这就能够收住了。
低成本的堆剖析次要指标:
- 开销低,能够继续默认启用。
- 能够通过一个定义明确的程序化接口拜访。
- 能够对所有调配进行采样(即,不限于在某一特定堆区域的调配或以某一特定形式的调配)。
- 能够以 独立 于实现的形式进行定义(即,不依赖于任何特定的 GC 算法或虚拟机实现),并且能够提供对于存活 Java 对象和垃圾 Java 对象的信息。
最终的实现如下:
- 提供用于生产和生产数据作为事件的 API
- 提供缓存机制和二进制数据格式
- 容许事件配置和事件过滤
- 提供 OS,JVM 和 JDK 库的事件
新增内容
JEP 330: Launch Single-File Source-Code Programs 启动繁多文件的源代码程序
提案违心是:加强 java 启动器反对运行单个 java 源代码文件的程序。这是什么意思?在咱们的认知外面,要运行一个 Java 源代码必须先编译,再运行两步执行动作。JDK11 简化了容许 java 程序编译运行的过程。
这个性能是免去了 Java 初学者须要 javac 和 Java 运行 class 文件的步骤,间接 java 类名即可编译运行,然而须要留神这种加强须要满足肯定条件:
- 第一点:执行源文件中的第一个类,第一个类必须蕴含 main 办法,比方上面的代码 main 办法在 Test2 当中就无奈应用此个性:
class Test1 {public void otherMethod(){}}
class Test2 {public static void main(String[] args) throws IOException {}}
- 第二点:不能够应用别源文件中的自定义类, 然而以后文件中的自定义类是能够应用的。
到 JDK10 为止,Java 启动器能以三种形式运行,JDK11 补齐了另一块短板:
- 启动一个 class 文件;
- 启动一个 JAR 中的 main 办法类;
- 启动一个模块中的 main 办法类;
- 启动 java 命令间接运行源码级别的 Java 文件(JDK11)
补充:只有是 Java 程序,无论多简单,最终入口肯定是 main()。
JEP 327: Unicode 10
Unicode 10 减少了 8518 个字符,总计达到了 136690 个字符,并且减少了 4 个脚本,同时还有 56 个新的 emoji 表情符号。
参考:http://unicode.org/versions/Unicode10.0.0/
JEP 328: Flight Recorder
英文名称直译过去是“航行记录仪”,没错就是咱们日常生活中的黑匣子。
在 JDK11 之前是一个商业版的个性,在 java11 当中开源进去,它能够导出事件到文件中,之后配合 Java Mission Control 来剖析。
应用 Flight Recorder 有两种形式:
-
利用启动时配置 java -XX:StartFlightRecording
-XX: +StartFlightRecording=filename=<path>, duration=<time>
-
应用 jcmd 来录制
$ jcmd <pid> JFR.start
$ jcmd <pid> JFR.dump filename=recording.jfr
$ jcmd <pid> JFR.stop
Jcmd <pid> JFR.start filename=<path> duration=<time>
扩大:Java Flight Recorder – Javatpoint
感兴趣能够看看上面的文章介绍的简略的 JFR 应用教程,然而留神是 JDK8 的:
工具篇 - 性能剖析工具 JFR 和 JMC – 麦奇 (mikeygithub.github.io)
实战 JFR
首先是编写一段 JFR 的测试案例代码:
/**
* JFR 的测试程序
* 须要应用 JDK11 以上的版本
* <p>
* src/main/java/com/zxd/interview/jfr/JfrTestApplication.java
*/
public class JfrTestApplication {public static void main(String[] args) {List<Object> items = new ArrayList<>(1);
try {while (true){items.add(new Object());
}
} catch (OutOfMemoryError e){System.out.println(e.getMessage());
}
assert items.size() > 0;
try {Thread.sleep(1000);
} catch (InterruptedException e) {System.out.println(e.getMessage());
}
}
}
为了编译胜利须要删除中文内容:
/**
* JFR 的测试程序
* 须要应用 JDK11 以上的版本
*
* src/main/java/com/zxd/interview/jfr/JfrTestApplication.java */
运行之后发现报错,这是因为中文导致的问题,这里把中文正文掉之后就 OK 了。
D:\adongstack\project\interview>javac -d out -sourcepath src/main src/main/java/com/zxd/interview/jfr/JfrTestApplication.java
src\main\java\com\zxd\interview\jfr\JfrTestApplication.java:7: error: unmappable character (0x8F) for encoding GBK
* JFR 鐨勬祴璇曠▼搴?
^
src\main\java\com\zxd\interview\jfr\JfrTestApplication.java:8: error: unmappable character (0x80) for encoding GBK
* 闇? 瑕佷娇鐢↗DK11 浠ヤ笂鐨勭増鏈?
^
src\main\java\com\zxd\interview\jfr\JfrTestApplication.java:8: error: unmappable character (0xAC) for encoding GBK
* 闇? 瑕佷娇鐢↗DK11 浠ヤ笂鐨勭増鏈?
通过上面的命令执行,编译 JfrTestApplication:
java -XX:+UnlockCommercialFeatures -XX:+FlightRecorder -XX:StartFlightRecording=duration=200s,filename=flight.jfr -cp ./out/ com/zxd/interview/jfr/JfrTestApplication
运行后果:
D:\adongstack\project\interview>java -XX:+UnlockCommercialFeatures -XX:+FlightRecorder -XX:StartFlightRecording=duration=200s,filename=flight.jfr -cp ./out/ com/zxd/interview/jfr/JfrTestApplication
Java HotSpot(TM) 64-Bit Server VM warning: Ignoring option UnlockCommercialFeatures; support was removed in 11.0
Started recording 1. The result will be written to:
D:\adongstack\project\interview\flight.jfr
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
[70.761s][warning][jfr,system,event] Exception occured during execution of period hook for jdk.ThreadContextSwitchRate(345)
因为程序执行的是一个“死循环”,宁静期待执行死循环直到 OOM 即可。执行实现之后,在 Idea 的我的项目根门路当中会呈现上面的文件内容:
这个文件要如何浏览?这里要用 JDK 的另一个工具JMC。令人遗憾的是,JFR 为 JDK 自带的工具,然而 JMC 须要咱们本人去 Oracle 官网下载(JMC 被官网从 JDK 中移除)。
JMC 装置
下载链接:https://www.oracle.com/java/technologies/javase/products-jmc8-downloads.html
这里间接下载以后的最新版本即可。
Windows 平台间接关上程序:
看到上面的界面阐明能够失常应用:
Java Mission Control 剖析代码
进入主界面,咱们间接关上刚刚生成的 jfr 文件:
首先是一份报告,简略介绍了 jfr 的状况:
Application efficiency was affected by halts
The highest ratio of application halts to execution time was 84.7 % during 2023/3/13 下午 9:11:53.000 – 下午 9:12:53. 2.59 % of the halts were for reasons other than GC. The halts ratio for the entire recording was 86.6 %. 2.21 % of the total halts were for reasons other than GC. Application halts are often caused by garbage collections, but can also be caused by excessive thread dumps or heap dumps. Investigate the VM Operation information and possibly the safepoint specific information.
报告次要阐明了整个应用程序大部分的工夫都是在进行进展,也就是对象的调配速度远远高于垃圾回收速度,垃圾收集器长时间工作而呈现进展和期待。
因为咱们是 while 的死循环程序,能够显著 CPU 使用率逐步飙升。
咱们能够浏览“办法调用的统计信息”来查看整个死循环的过程中哪些办法被大量调用:
通过“办法”板块咱们能够发现示例程序的另一个毛病:办法 java.util.ArrayList.grow(int)
已被调用 147 次,在每次没有足够的空间增加对象时扩充数组容量。
咱们可能会看到许多其余有用的信息:
- 无关创建对象的统计信息,当垃圾回收器创立和销毁这些对象时
- 无关线程年表的具体报告,它们何时被锁定或处于活动状态
- 应用程序正在执行哪些 I/O 操作
JEP 318: Epsilon: A No-Op Garbage Collector Epsilon 垃圾收集器
首先吸引我的是 Epsilon 这个单词,和 CMS、G1 这种带有含意的垃圾收集器名称一样,Epsilon 也有非凡的含意。
这个希腊字母同时也是英语音标 ɛ
的起源,上面的介绍引自:Ε – 维基百科,自在的百科全书 (wikipedia.org)
Epsilon(大写 Ε、小写 ε 或 ϵ;希腊语:έψιλον;中文音译:伊普西龙、厄普西隆、艾普西龙、艾普塞朗),是第五个希腊字母。Epsilon(ἒ ψιλόν)即“e 简略的、e 繁多的”的意思,这是为了与中世纪产生单元音化而变为同音的二合字母 ai(αι,古希腊语:[ai̯])做区别,古典期间原本这个字母读作 ei(εἶ,古希腊语:[êː]);源自腓尼基字母 HeHe,又从 epsilon 倒退出了拉丁字母 E 和西里尔字母 Е。在希腊数字零碎中,E 示意 5。
Epsilon 垃圾收集器用法非常简单:
-XX:+UseEpsilonGC
通常倡议搭配参数-XX:+UnlockExperimentalVMOptions
应用。
-XX:+UnlockExperimentalVMOptions:
个别应用在一些低版本 jdk 想应用高级参数或者可能高版本有的参数状况;
解锁试验参数,容许应用实验性参数,JVM 中有些参数不能通过 -XX 间接复制须要先解锁,比方要应用某些参数的时候,可能不会失效,须要设置这个参数来解锁;
JDK 的形容是开发只负责内存调配,其余事件一律不做解决的垃圾收集器,“不回收垃圾的垃圾收集器”看起来怪怪的,因而用处也比拟非凡:
- 性能测试(它能够帮忙过滤掉 GC 引起的性能假象);
- 内存压力临界点测试(比方晓得测试用例应该调配不超过 1 GB 的内存,咱们能够应用 -Xmx1g 配置
-XX:+UseEpsilonGC
,如果违反了该束缚,则会 heap dump 并解体); - 十分短的 JOB 工作(对于这种工作,承受 GC 清理堆那都是节约空间);
- VM 接口测试;
- Last-drop 提早 & 吞吐改良;
这里间接通过一个程序理解 Epsilon 垃圾收集器的成果:
/**
* JDK11 新的垃圾收集器 Epsilon
* 须要配置启动参数:* -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC
*/public class EpsilonGcTest {public static void main(String[] args) {
boolean flag = true;
List<Garbage> list = new ArrayList<>();
long count = 0;
while (flag){list.add(new Garbage());
if(list.size() == 100000 && count == 0){list.clear();
count++;
}
}
System.out.println("程序完结");
}
}
class Garbage{int n = (int)(Math.random() * 100);
@Override
protected void finalize() throws Throwable {System.out.println(this + ":"+ n + "is dying");
}
}
启动之前,不要忘了设置 VM Option,如果看不到 VM Option
这一行,能够点击 ”Modify options” 中增加 VM option。
PS:上面的环境变量和 program arguments 设置启动参数是有效的。
启动之后一会儿控制台会打印上面的内容并且完结。
Terminating due to java.lang.OutOfMemoryError: Java heap space
如果是传统的垃圾收集器,那么输入的后果将会是 System.out.println(this + ":"+ n + "is dying");
的内容,尽管整个程序仍然会持续运行直到 OOM,然而要花更多的工夫期待。
com.zxd.interview.epsilongctest.Garbage@21abe : 59is dying
com.zxd.interview.epsilongctest.Garbage@6f6b18fc : 43is dying
com.zxd.interview.epsilongctest.Garbage@701264d8 : 56is dying
com.zxd.interview.epsilongctest.Garbage@5380a941 : 52is dying
com.zxd.interview.epsilongctest.Garbage@43c78e65 : 72is dying
com.zxd.interview.epsilongctest.Garbage@745cd219 : 11is dying
com.zxd.interview.epsilongctest.Garbage@170b4cee : 68is dying
com.zxd.interview.epsilongctest.Garbage@411725ef : 22is dying
com.zxd.interview.epsilongctest.Garbage@3edc2f0e : 4is dying
com.zxd.interview.epsilongctest.Garbage@2c82eed6 : 58is dying
com.zxd.interview.epsilongctest.Garbage@61a3bb94 : 71is dying
com.zxd.interview.epsilongctest.Garbage@19e3d212 : 70is dying
com.zxd.interview.epsilongctest.Garbage@46182a7f : 19is dying
com.zxd.interview.epsilongctest.Garbage@7dd78834 : 56is dying
com.zxd.interview.epsilongctest.Garbage@1d4e301 : 68is dying
从下面的案例能够看到,有时候不回收垃圾也是有帮忙的。
JEP 333: ZGC: A Scalable Low-Latency Garbage Collector (可伸缩低提早垃圾收集器)
这应该是 JDK11 最为注目的个性, 没有之一,然而前面带了 Experimental, 阐明 JDK11 这一个版本 不倡议用到生产环境。(JDK13 才算是真正欠缺),ZGC 问世对外宣传如下:
- GC 暂停工夫不会超过 10ms;
- 即能解决几百兆小堆,也能解决几个 T 的大堆(OMG);
- 和 G1 相比,利用吞吐能力不会降落超过 15%;
- 为将来的 GC 性能和利用 colord 指针以及 Load barriers 优化奠定根底;
- 初始只反对 64 位零碎;
古代零碎可用内存一直增大,GC 垃圾收集器同样须要一直进化,古代的利用谋求低提早高吞吐量,ZGC 的特点正好切中了 GC 的痛点。
ZGC 的设计指标是:反对 TB 级内存容量,暂停工夫低(<10ms),对整个程序吞吐量的影响小于 15%。
美团在 2020 年有一篇对于 ZGC 的剖析文章品质很高:# 新一代垃圾回收器 ZGC 的摸索与实际,如果比拟难了解,还能够看看这一篇文章:# 了解并利用 JVM 垃圾收集器 -ZGC
ZGC 的理解水平停留在简略了解实践概念即可,上面是从网络整顿的材料归档:
GC 术语
留神上面的并行、串行、并发更偏差 GC 的概念:
- 并行:能够存在多个 GC 线程同时进行工作,然而无奈确定是否须要暂停用户线程
- 串行:指的是只有单个 GC 线程进行工作。留神串行也不肯定须要暂停用户线程
- STW:用户线程暂停,此时只有 GC 线程进行工作。
- 并发:GC 的并发指的是具备并发性,如果说垃圾收集器某些阶段是并发的,那么能够简略在某些阶段 GC 线程能够和应用程序线程同时进行,然而这须要十分复杂的设计避免用户线程操作导致 GC 工作有效。
- 增量阶段指的是某个阶段能够再运行一段时间之后提前停止。
衡量
衡量次要是针对并行和并发阶段带来的问题,并行阶段如果 GC 占用过多的线程可能导致用户线程性能抖动,而并发阶段如果须要保障能同时解决 GC 的有效性和用户线程失常工作的问题。
多层堆和压缩
多层堆指的是让 JVM 治理内存能够像高速缓存一样分为多级缓存存储对象,通过对象分类将频繁拜访对象和很少应用对象离开治理的技术。该性能能够通过扩大指针元数据来实现,指针能够实现计数器位并应用该信息来决定是否须要挪动对象到较慢的存储上。
压缩是指对象能够以压缩模式保留在内存中,而不是将对象重定位到较慢的存储层。获取压缩对象的时候通过读屏障将其解压并且重新分配。
多重映射
这里不过多介绍计算机的虚拟内存和物理内存概念,简略晓得操作系统通过 映射表 实现了物理内存和虚拟内存的映射,最终通过应用页表和处理器的内存治理单元(MMU)和转换查找缓冲器(TLB)来实现这一点。
而多重映射则指的是多个虚拟内存映射到同一块物理内存的技术。ZGC 中应用三个映射来实现多重映射操作。
读屏障
读屏障是每当应用程序线程从堆加载援用时运行的代码片段(即拜访对象上的非原生字段(non-primitive field)):
读屏障是 JVM 向利用代码插入一小段代码的技术。当利用线程从堆中读取对象援用时,就会执行这段代码。须要留神的是,仅“从堆中读取对象援用”才会触发这段代码。
void printName(Person person) {
// 这里触发读屏障
String name = person.name;
// 因为须要从 heap 读取援用
//
System.out.println(name); // 这里没有间接触发读屏障
}
如正文所说 String name = person.name;
这一行因为拜访了对象非原生字段,将援用加载到本地的 name 变量,此时操作触发了读屏障。
如后面所说,读屏障是在返回对象援用之前“做手脚”,比拟能想到的思路是援用中设置某些“标记位”的形式,然而 ZGC 理论用的是“测试加载的援用”查看是否满足条件,最初依据后果判断是否执行本人须要的非凡操作。
ZGC 最终抉择在读屏障上动手脚,一部分起因是源自 Shenadoah 收集器的挑战,这个非 JDK 官网设计的垃圾收集器给 Oracle 不小的冲击。
ZGC 标记
上面简略理解 ZGC 垃圾回收截断中的标记截断解决,ZGC 的标记分为三个阶段:
-
第一阶段是 STW,其中 GC roots 被标记为活对象。
- 从 roots 拜访的对象汇合称为 Live 集。GC roots 标记步骤十分短,因为 roots 的总数通常比拟小。
-
第二阶段遍历对象图并标记所有可拜访的对象。
- 读屏障针应用掩码测试所有已加载的援用,该掩码确定它们是否已标记或尚未标记,如果尚未标记援用,则将其增加到队列以进行标记。
- 第三阶段是边缘状况的解决,这里会有一个十分短暂的 STW 操作。也是针对通过前两次标记之后仍然没有标记实现的最终查看。
GC roots 相似于局部变量,通过它能够拜访堆上其余对象。通过 Gc root 进行可达性剖析查找出无奈“达到”的对象则被标记为垃圾对象
ZGC 重定位
ZGC 在对象清理上采纳和 G1 相似的思路,也就是 标记 - 复制 把活对象挪动到另一处,而后将只存在垃圾对象的内存开释掉,然而实现办法上更为取巧。
ZGC 首先将堆分成许多页面,重定位开始的时候,会筛选一组须要重定位对象的页面,为了保障筛选精确此时须要 短暂 STW将该汇合中 root 对象的援用映射到新地位。
挪动 root 后下一阶段是 并发重定位,此时 GC 线程遍历重定位集并从新定位其蕴含的页中所有对象,如果利用线程拜访到这些须要重定位对象,则通过转发援用的形式从新定位读取新地位。
这里须要留神读屏障并不能齐全保障所有指向旧对象的援用转到从新定位的地址上,在并发的状况下 GC 须要重复屡次查看是否有援用指向旧的地位。
重定位的操作代价非常昂扬,为了提高效率会和 GC 周期的标记阶段一起并行执行,GC 周期标记阶段遍历对象对象图的时候,如果发现未重映射的援用则将其从新映射,而后标记为活动状态。
这两步能够形象认为是一个人负责干活,另一个人负责把后面干活遗留的工作进行检查和标记。
用户过程读取对象从新定位就是通过 读屏障 实现的。
ZGC 体现
ZGC 的 SPECjbb 2015 吞吐量与 Parallel GC(优化吞吐量)大抵相当,但均匀暂停工夫为 1ms,最长为 4ms。与之相比 G1 和 Parallel 有很屡次超过 200ms 的 GC 进展。
ZGC 的状态
ZGC 仍然有很多亟待解决的问题,以 G1 为例从公布到反对之间超过 3 年,ZGC 最终到 JDK13 被正式对外应用。
小结
从整体上看,ZGC 的垃圾收集将所有的暂停管制只依赖于 GC roots 汇合上,将进展管制在一处以达到更好的 GC 成果。
标记垃圾对象的过程中,标记阶段解决标记终止的最 后一次暂停 是惟一的例外,然而它是增量的(不肯定执行),如果超过 GC 工夫估算,那么 GC 将复原到并发标记,直到再次尝试。
删除内容
JEP 320: Remove the Java EE and CORBA Modules
Java EE 和 CORBA 两个模块在 JDK9 中曾经标记 ”deprecated”,在 JDK11 中正式移除。这部分内容基本上没有 Java 开发者接触到(连网络搜寻都不会碰到)所以 遗记 即可。
JavaEE 由 4 局部组成:
- JAX-WS (Java API for XML-Based Web Services),
- JAXB (Java Architecture for XML Binding)
- JAF (the JavaBeans Activation Framework)
- Common Annotations.
看起来比拟多,然而这个个性和 JavaSE 关系不大。并且 Maven 也提供了 JavaEE(http://mvnrepository.com/artifact/javax/javaee-api/8.0 第三方援用,所以移除的影响并不会很大。
至于 CORBA,应用 CORBA 开发程序没有太大的趣味。
CORBA 的 XML 相干模块被移除:
[java.xml.ws](http://java.xml.ws),
`java.xml.bind`,[java.xml.ws](http://java.xml.ws),`java.xml.ws.annotation`,`jdk.xml.bind`,`jdk.xml.ws`
最终保留:`java.xml`,`java.xml.crypto`,`jdk.xml.dom` 模块。
除此之外,还有:
java.se.ee,
java.activation
,java.transaction
被移除,然而 java11 新增一个
java.transaction.xa
模块
JEP 335: Deprecate the Nashorn JavaScript Engine(弃用 Nashorn JavaScript 引擎)
破除 Nashorn javascript 引擎,在后续版本筹备移除掉,有须要的能够思考应用 GraalVM
最终:Nashorn JavaScript Engine 在 Java 15 曾经不可用了。简直不会用到的货色,为了让读者稍微理解一下,这里用 JDK8 的 Nashorn 简略做个演示。
public class NashornTest {public static void main(String[] args) throws ScriptException, FileNotFoundException {
// 间接应用
ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
engine.eval("print('Hello World!');");
// 文件中运行
ScriptEngine engine2 = new ScriptEngineManager().getEngineByName("nashorn");
engine2.eval(new FileReader("E:\\adongstack\\project\\selfUp\\interview\\src\\main\\resources\\static\\js\\hello.js"));
}
}
古代我的项目大多数都是前后端拆散的,哪怕没有前后端拆散也根本没有人会在后端写很多隐患的 JS 脚本,集体认为是这些起因导致的 Nashorn javascript 石破天惊的诞生和石破天惊的覆灭。
Nashorn javascript 引擎更多理解能够看这篇:https://mouse0w0.github.io/2018/12/02/Introduction-to-Nashorn/
JEP : 336 : Deprecate the Pack200 Tools and API 废除 Pack200 和相干 API
Pack200 是自 Java5 呈现的 压缩工具,这个工具能对一般的 jar 文件进行高效压缩。
Pack200 合并原理是依据类的结构设计以及合并常量池的形式,最初去掉无用信息来实现高效压缩,留神这种压缩只能针对 Java 文件进行压缩,这种压缩形式是很有意义的,能达到 jar 包的 10% – 40% 的压缩率。
Java5 中还提供了这一技术的 API 接口,能够将其嵌入到应用程序中应用。应用的办法很简略,上面的短短几行代码即能够实现 jar 的压缩和解压:
压缩
Packer packer=Pack200.newPacker();
OutputStream output=new BufferedOutputStream(new FileOutputStream(outfile));
packer.pack(new JarFile(jarFile), output);
output.close();
解压
Unpacker unpacker=Pack200.newUnpacker();
output=new JarOutputStream(new FileOutputStream(jarFile));
unpacker.unpack(pack200File, output);
output.close();
看了下面的介绍,读者可能会跟我有一样的疑难,感觉这 不是挺好的一货色么为啥要摈弃? 还能联合 SpringBoot 的 jar 包进行压缩,为了进一步理解 JDK 为啥要在 Java11 移除,这里找到的 JDK 官网的阐明:
JEP 367: Remove the Pack200 Tools and API (openjdk.org)
我把 JDK 官网的阐明局部拿过去了:
There are three reasons to remove Pack200:
1. Historically, slow downloads of the JDK over 56k modems were an impediment to Java adoption. The relentless growth in JDK functionality caused the download size to swell, further impeding adoption. Compressing the JDK with Pack200 was a way to mitigate the problem. However, time has moved on: download speeds have improved, and JDK 9 introduced new compression schemes for both the Java runtime ([JEP 220](http://openjdk.java.net/jeps/220)) and the modules used to build the runtime ([JMOD](http://openjdk.java.net/jeps/261#Packaging:-JMOD-files)). Consequently, JDK 9 and later do not rely on Pack200; JDK 8 was the last release compressed with `pack200` at build time and uncompressed with `unpack200` at install time. In summary, a major consumer of Pack200 -- the JDK itself -- no longer needs it.
2. Beyond the JDK, it was attractive to compress client applications, and especially applets, with Pack200. Some deployment technologies, such as Oracle's browser plug-in, would uncompress applet JARs automatically. However, the landscape for client applications has changed, and most browsers have dropped support for plug-ins. Consequently, a major class of consumers of Pack200 -- applets running in browsers -- are no longer a driver for including Pack200 in the JDK.
3. Pack200 is a complex and elaborate technology. Its [file format](https://docs.oracle.com/en/java/javase/13/docs/specs/pack-spec.html) is tightly coupled to the [class file format](https://docs.oracle.com/javase/specs/jvms/se13/html/jvms-4.html#jvms-4.1) and the [JAR file format](https://docs.oracle.com/en/java/javase/13/docs/specs/jar/jar.html), both of which have evolved in ways unforeseen by JSR 200. (For example, [JEP 309](http://openjdk.java.net/jeps/309) added a new kind of constant pool entry to the class file format, and [JEP 238](http://openjdk.java.net/jeps/238) added versioning metadata to the JAR file format.) The implementation in the JDK is split between Java and native code, which makes it hard to maintain. The API in `java.util.jar.Pack200` was detrimental to the modularization of the Java SE Platform, leading to [the removal of four of its methods in Java SE 9](http://cr.openjdk.java.net/~iris/se/9/java-se-9-fr-spec/#APIs-removed). Overall, the cost of maintaining Pack200 is significant, and outweighs the benefit of including it in Java SE and the JDK.
一大坨论文一样的英文,这里抽取局部语句的关键点简略概括一下:
第一点:
- 说白了就是有新人干活还有力量,老东西连忙退休。过来网络带宽资源吃紧加上网速很慢,超过 56k 的 jar 包要下半天,压缩解决能在肯定水平上 治本。然而古代的网络环境下个几十 K 的货色简直都是霎时实现了,而后JDK9 还引入了新的压缩形式(要害)。
第二点:
- Pack200 最初一点夹缝:作为浏览器开发插件压缩的生存空间也没了,古代浏览器插件有独有的开发方式,Java 在这一块掀不起什么浪花。
第三点:
Pack200 is a complex and elaborate technology
。“Pack200 是一项简单而精密的技术”,我愿称之为高情商发言,低情商就是 臃肿又难用。前面旁征博引“批评了一番如许“精密”和“简单”导致的问题。
总结:网络下载速度的晋升 以及 java9 引入模块化零碎 之后 不再依赖Pack200,因而这个版本将其移除掉。
JDK11 其余内容
Optional 加强
新增了 empty()
办法来判断指定的 Optional
对象是否为空。
var op = Optional.empty();
System.out.println(op.isEmpty());// 判断指定的 Optional 对象是否为空
String 加强
// 判断字符串是否为空
" ".isBlank();//true
// 去除字符串首尾空格
"Java".strip();// "Java"
// 去除字符串首部空格
"Java".stripLeading(); // "Java"
// 去除字符串尾部空格
"Java".stripTrailing(); // "Java"
// 反复字符串多少次
"Java".repeat(3); // "JavaJavaJava"
// 返回由行终止符分隔的字符串汇合。"A\nB\nC".lines().count(); // 3
"A\nB\nC".lines().collect(Collectors.toList());
移除项
- 移除了
com.sun.awt.AWTUtilities
- 移除了
sun.misc.Unsafe.defineClass
- 应用
java.lang.invoke.MethodHandles.Lookup.defineClass
来代替 - 移除了
Thread.destroy()
以及Thread.stop(Throwable)
办法 - 移除了
sun.nio.ch.disableSystemWideOverlappingFileLockCheck
、sun.locale.formatasdefault
属性 - 移除了
jdk.snmp
模块 - 移除了
javafx
,openjdk 预计是从 java10 版本就移除了,然而 oracle jdk10 还尚未移除 javafx,而 java11 版本则 oracle 的 jdk 版本也移除了 javafx。 - 移除了Java Mission Control,从 JDK 中移除之后,须要本人独自下载
移除了这些Root Certificates:
- Baltimore Cybertrust Code Signing CA
- SECOM
- AOL and Swisscom
废除项
-XX+AggressiveOpts
选项-XX:+UnlockCommercialFeatures
-XX:+LogCommercialFeatures
选项也不再须要
G1 垃圾收集器降级
JDK11 的 G1 垃圾收集齐相比于 JDK 8 能够享受到:
- 并行的 Full GC
- 疾速的 CardTable 扫描
- 自适应的堆占用比例调整(IHOP)
- 在并发标记阶段的类型卸载
…. 等等
这些都是针对 G1 的一直加强,串行 Full GC 被经常诟病最终 Oracle 忍气吞声复用了 CMS 的并行 GC 代码给实现了(配置参数能够看出端倪,很多和 CMS 配置仅仅是换了个名字),最终是缩小程序员的调优老本和调优工夫。
附录
17 个 JEP(JDK Enhancement Proposals,JDK 加强提案)
上面蕴含了这 17 个 JEP 的提案:
JEP 181: Nest-Based Access Control
JEP 309: Dynamic Class-File Constants
JEP 315: Improve Aarch64 Intrinsics
JEP 318: Epsilon: A No-Op Garbage Collector
JEP 320: Remove the Java EE and CORBA Modules
JEP 321: HTTP Client (Standard)
JEP 323: Local-Variable Syntax for Lambda Parameters
JEP 324: Key Agreement with Curve25519 and Curve448
JEP 327: Unicode 10
JEP 328: Flight Recorder
JEP 329: ChaCha20 and Poly1305 Cryptographic Algorithms
JEP 330: Launch Single-File Source-Code Programs
JEP 331: Low-Overhead Heap Profiling
JEP 332: Transport Layer Security (TLS) 1.3
JEP 333: ZGC: A Scalable Low-Latency Garbage Collector (Experimental)
JEP 335: Deprecate the Nashorn JavaScript Engine
JEP 336: Deprecate the Pack200 Tools and API
Jshell(JDK9)
和 Go 和 Python 这种自带 Shell 的语言进行比照,Java 的 Shell 直到 Java9 才呈现,Java9 引入了 jshell 这个交互性工具,让 Java 也能够像脚本语言一样来运行,能够从控制台启动 jshell。
一个编程语言本该有的货色,这里就不多介绍了。Jshell 比拟适宜刚刚入门 Java 语言的学习者应用。
写在最初
这篇文章把大部分 JDK11 新个性简略剖析了一遍,如果有谬误的阐述欢送斧正。JDK 的新版本个性还是十分有意思的,尽管不肯定能在工作中用上,然而理解这些个性跟进时代是作为 Java 开发者的根本操守。