2006年之后SUN公司决定将JDK进行开源,从此成立了OpenJDK组织进行JDK代码管理。任何人都可以获取该源码,并通过源码构建一个发行版发布到网络上。但是需要一个组织审核来确保构建的发行版是有效的, 这个组织就是JCP(Java Community Process)。2009年,SUN公司被Oracle公司"白嫖"(参考2018年Google赔款),此时大家使用的JDK通常都是Oracle公司的OpenJDK构建版本-OracleJDK。但是,Oracle公司是一个明显只讲商业而不管情怀的公司,接手Java商标之后,明显加快了JDK的发布版本。2018年9月25日,JDK11成功发布,这是一个LTS版本,包含了17个JEP的更新。与此同时,Oracle把JDK11起以往的商业特性全部开源给OpenJDK(例如:ZGC和Flight Recorder)。
根据Oracle的官方说法(Oracle JDK Releases for Java 11 and Later),从JDK11之后,OracleJDK与OpenJDK的功能基本一致。然后,Oracle宣布以后将会同时发行两款JDK:1. 一个是以GPLv2+CE协议下,由Oracle发行OpenJDK(简称为Oracle OpenJDK);2. 另一个是在OTN协议下的传统OracleJDK。这两个JDK共享绝大多数源码,核心差异在于前者可以免费在开发、测试和生产环境下使用,但是只有半年时间的更新支持。后者各个人可以免费使用,但是生产环境中商用就必须付费,可以有三年时间的更新支持。
一、Java平台模块化系统(Jigsaw项目)
1、什么是Java模块化?
-
强封装性:一个模块可以选择性的对其他模块隐藏部分实现细节。 -
定义良好的接口:一个模块只有封装是不够的,还要通过对外暴露接口与其他模块交互。因此,暴露的接口必须有良好的定义。 -
显示依赖:一个模块通常需要协同其他模块一起工作,该模块必须显示的依赖其他模块 ,这些依赖关系同时也是模块定义的一部分。
2、为什么要做模块化?
2.1 如何使得Java SE应用程序更加轻量级的部署?
2.2 在暴露的JAR包中,如何隐藏部分API和类型?
2.3 一直遭受NoClassDefFoundError的折磨
3、JPMS如何解决现有问题?
-
强封装(Strong encapsulation): 每一个模块都可以声明了哪些包是对外暴露的,java编译和运行时就可以实施这些规则来确保外部模块无法使用内部类型。 -
可靠配置(Reliable configuration):每一模块都声明了哪些是它所需的,那么在运行时就可以检查它所需的所有模块在应用启动运行前是否都有。
3.1 Project Jigsaw
Modular development starts with a modular platform. —Alan Bateman 2016.9
-
可伸缩平台(Scalable platform):逐渐从一个庞大的运行时平台到有有能力缩小到更小的计算机设备。 -
安全性和可维护性(Security and maintainability):更好的组织了平台代码使得更好维护。隐藏内部API和更明确的接口定义提升了平台的安全性。 -
提升应用程序性能(Improved application performance):只有必须的运行时runtimes的更小的平台可以带来更快的性能。 -
更简单的开发体验Easier developer experience:模块系统与模块平台的结合使得开发者更容易构建应用和库。
4 创建第一个Java模块
--moudule1
---src
----main
-----java
------com.company.package1
------moudule-info.java
---pom.xml
5 模块化对现有应用的影响
5.1 你可以不用但是不能不懂
例如,在从JDK8升级到JDK11时,我们经常会收到一下警告:
WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by com.jd.jsf.java.util.GregorianCalendar_$$_Template_1798100948_0 (file:/home/export/App/deliveryorder.jd.com/WEB-INF/lib/jsf-1.7.2.jar) to field java.util.Calendar.fields
WARNING: Please consider reporting this to the maintainers of com.jd.jsf.java.util.GregorianCalendar_$$_Template_1798100948_0
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release
5.2 Java模块、Maven模块和OSGI模块的之间的关系。
5.3 模块化对类加载机制的影响
5.4 总结
二、垃圾回收器的一系列优化措施
2.1、ZGC-新一代垃圾回收器
-
GC停顿时间不会超过10ms。 -
停顿时间不会随着堆的大小,或者活跃对象的大小而增加; -
相对于G1垃圾回收器而言,吞吐量降低不超过15%; -
支持Linux/x64、window和mac平台; -
支持8MB~16TB级别的堆回收。
-
在仅关注吞吐量指标下,ZGC超过了G1; -
在最大延迟不超过某个设定值(10到100ms)下关注吞吐量,ZGC较G1性能更加突出。 -
在仅关注低延迟指标下,ZGC的性能高出G1将近两个数量级。99.9th仅为G1的百分之一。
-
因为整个ZGC周期基本都是并发执行,因此创建新对象的速度与垃圾回收的速度从一开始就在较量。如果创建新对象的速度更胜一筹,垃圾会将堆占满导致部分线程阻塞,直到垃圾回收完毕。 -
G1虽然是第一个基于全局的垃圾回收器,但是仍然存在新生代和老年代的概念。但是从ZGC开始,完全抛弃了新生代和老年代。但是新生代对象朝生夕灭的特性会给ZGC带来很大的压力。完全的并发执行,必然会造成一定的吞吐量降低。 -
在JDK11,G1垃圾回收器目前还只是实验性的功能,只支持Linux/x64平台。后续优化接改进,短时间内无法更新到JDK11中,所以可能会遇到一些不稳定因素。例如: 1. JDK12支持并发类卸载功能。2. JDK13将可回收内存从4TB支持到16TB。3. JDK14提升稳定性的同时,提高性能。4. JDK15从实验特性转变为可生产特性 。所以如果想要使用稳定的ZGC功能,只能升级到JDK17,横跨一个JDK11LTS版本,同时面临近200个JEP带来的功能更新。 -
实际线上生产环境,在订单商品等核心系统尝试使用ZGC。但是压测结果显示,在JDK11还是JDK17都差强人意。当然这并不是代表ZGC本身技术缺陷,而是需要根据不同的线上环境做更深度的调优和实践。因为数据保密等原因,这里没有给大家展示具体的压测数据,读者可以在各自环境进行不同程度的压测验证。
2.2、G1垃圾回收器相关
2.2.1、G1的Full GC从串行改为并行(JEP307)
2.2.2、可中断的Mixed-GC(JEP344)
2.2.3 G1支持NUMA技术(JEP345)
2.3、废弃CMS垃圾回收器
2.4、废弃ParallelScavenge + SerialOld 垃圾回收器组合
2.4、Epsilon:低开销垃圾回收器
三、诊断和监控相关优化
3.1 Java Flight Recorder[JEP328]
3.2 Java Mission Control [JMS]
3.3 统一 JVM 日志(JEP158)
解决这个问题最佳的方法:对所有的JVM组件引入一个统一的日志框架,这些JVM组件支持细粒度的和易配置的JVM日志。JDK8以前常用的打印GC日志方式:
-Xloggc:/export/Logs/gc.log //输出GC日志到指定文件
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
3.3.1 目标:
所有日志记录的通用命令行选项。
通过tag对日志进行分类,例如:compiler, gc, classload, metaspace, svc, jfr等。一条日志可能会含有多个 tag
日志包含多个日志级别:error, warning, info, debug, trace, develop。
可以将日志重定向到控制台或者文件。
error, warning级别的日志重定向到标准错误stderr.
可以根据日志大小或者文件数对日志文件进行滚动。
一次只打印一行日志,日志之间无交叉。
日志包含装饰器,默认的装饰器包括:uptime, level, tags,且装饰可配置。
3.3.2 如何使用
-Xlog[:option]
option := [][:[][:[][:]]]
'help'
'disable'
what := [,...]
selector := [*][=]
tag-set := [+...]
'all'
tag := name of tag
level := trace
debug
info
warning
error
output := 'stderr'
'stdout'
[file=]
decorators := [,...]
'none'
decorator := time
uptime
timemillis
uptimemillis
timenanos
uptimenanos
pid
tid
level
tags
output-options := [,...]
output-option := filecount=
filesize=
parameter=value
-Xlog:all=warning:stderr:uptime,level,tags
- 默认配置
- 'all' 即是包含所有tag
- 默认日志输出级别warning,位置stderr
- 包含uptime,level,tags三个装饰
JDK9之前参数-XX:+PrintGCDetails可参考:
-
-Xlog:safepoint,classhisto*=trace,age*,gc*=info:file=/export/Logs/gc-%t.log:time,tid,level,tags:filecount=5,filesize=50MB
- safepoint表示打印用户线程并发及暂停执行时间
- classhisto表示full gc时打印堆快照信息
- age*,gc* 表示打印包括gc及其细分过程日志,日志级别info,文件:/export/Logs/gc.log。
- 日志格式包含装饰符:time,tids,level,tags
- default output of all messages at level 'warning' to 'stderr'
will still be in effect
- 保存日志个数5个,每个日志50M大小
-Xlog:gc+heap=debug:file=/export/Logs/gc.log:time,tids,level,tags:filecount=5,filesize=1M
- 打印包括gc及其细分过程日志,日志级别info,文件:/export/Logs/gc.log。
- 日志格式包含装饰符:time,tids,level,tags
- default output of all messages at level 'warning' to 'stderr'
will still be in effect
- 保存日志个数5个,每个日志1M大小
J DK9之前的GC日志:
2014-12-10T11:13:09.597+0800: 66955.317: [GC concurrent-root-region-scan-start]
2014-12-10T11:13:09.597+0800: 66955.318: Total time for which application threads were stopped: 0.0655753 seconds
2014-12-10T11:13:09.610+0800: 66955.330: Application time: 0.0127071 seconds
2014-12-10T11:13:09.614+0800: 66955.335: Total time for which application threads were stopped: 0.0043882 seconds
2014-12-10T11:13:09.625+0800: 66955.346: [GC concurrent-root-region-scan-end, 0.0281351 secs]
2014-12-10T11:13:09.625+0800: 66955.346: [GC concurrent-mark-start]
2014-12-10T11:13:09.645+0800: 66955.365: Application time: 0.0306801 seconds
2014-12-10T11:13:09.651+0800: 66955.371: Total time for which application threads were stopped: 0.0061326 seconds
2014-12-10T11:13:10.212+0800: 66955.933: [GC concurrent-mark-end, 0.5871129 secs]
2014-12-10T11:13:10.212+0800: 66955.933: Application time: 0.5613792 seconds
2014-12-10T11:13:10.215+0800: 66955.935: [GC remark 66955.936: [GC ref-proc, 0.0235275 secs], 0.0320865 secs]
JDK9统一日志框架输出的日志格式 :
[2021-02-09T21:12:50.870+0800][258][info][gc] Using G1
[2021-02-09T21:12:51.751+0800][365][info][gc] GC(0) Pause Young (Concurrent Start) (Metadata GC Threshold) 60M->5M(4096M) 7.689ms
[2021-02-09T21:12:51.751+0800][283][info][gc] GC(1) Concurrent Cycle
[2021-02-09T21:12:51.755+0800][365][info][gc] GC(1) Pause Remark 13M->13M(4096M) 0.959ms
[2021-02-09T21:12:51.756+0800][365][info][gc] GC(1) Pause Cleanup 13M->13M(4096M) 0.127ms
[2021-02-09T21:12:51.758+0800][283][info][gc] GC(1) Concurrent Cycle 7.208ms
[2021-02-09T21:12:53.232+0800][365][info][gc] GC(2) Pause Young (Normal) (G1 Evacuation Pause) 197M->15M(4096M) 17.975ms
[2021-02-09T21:12:53.952+0800][365][info][gc] GC(3) Pause Young (Concurrent Start) (GCLocker Initiated GC) 114M->17M(4096M) 15.383ms
[2021-02-09T21:12:53.952+0800][283][info][gc] GC(4) Concurrent Cycle
四、更加优雅的语法或者方法
4.1、集合工厂方法
// 创建只有一个值的可读list,底层不使用数组
static <E> List<E> of(E e1) {
return new ImmutableCollections.List12<>(e1);
}
// 创建有多个值的可读list,底层使用数组
static <E> List<E> of(E e1, E e2, E e3) {
return new ImmutableCollections.List12<>(e1, e2,e3);
}
// 创建单例长度为0的Set结合
static <E> Set<E> of() {
return ImmutableCollections.emptySet();
}
static <E> Set<E> of(E e1) {
return new ImmutableCollections.Set12<>(e1);
}
4.2、接口私有方法
public interface HelloService {
public void sayHello();
// 默认方法
default void saySomething(){
syaEngHello();
sayHello();
};
// 私有方法
private void syaEngHello(){
System.out.println("Hello!");
}
}
4.3、改进的 Stream API
// 循环直到第一个满足条件后停止
default Stream takeWhile(Predicate predicate);
// 循环直到第一个满足条件后开始
default Stream dropWhile(Predicate predicate);
// 根据表达式生成迭代器
static Stream iterate(T seed, Predicate hasNext, UnaryOperator next);
// 使用空值创建空的Stream,避免空指针
static Stream ofNullable(T t);
4.4、JShell
4.5、局部类型推断(JEP286)
public static void main(String[] args) throws Exception {
var lists = List.of("a", "b", "c");
for (var word : lists) {
System.out.println(word);
}
}
var关键字只能用于可推断类型的代码位置,不能使用于方法形式参数,构造函数形式参数,方法返回类型等。标识符var不是关键字,它是一个保留的类型名称。这意味着var用作变量,方法名或则包名称的代码不会受到影响。但var不能作为类或则接口的名字。
4.6、标准Java HTTP Client
response=urllib.request.urlopen('https://www.python.org') #请求站点获得一个HTTPResponse对象
print(response.read().decode('utf-8')) #返回网页内容
JDK:
HttpURLConnection connection = (HttpURLConnection) new URL("http://localhost:8080/demo/list?name=HTTP").openConnection();
connection.setRequestMethod("GET");
connection.connect();
int responseCode = connection.getResponseCode();
log.info("response code : {}", responseCode);
// read response
try (BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
} finally {
connection.disconnect();
}
Apache HttpClient:
CloseableHttpClient httpClient = HttpClientBuilder.create().build();
// 创建Get请求
HttpGet httpGet = new HttpGet("http://localhost:12345/doGetControllerOne");
// 响应模型
CloseableHttpResponse response = null;
// 由客户端执行(发送)Get请求
response = httpClient.execute(httpGet);
// 从响应模型中获取响应实体
HttpEntity responseEntity = response.getEntity();
System.out.println("响应状态为:" + response.getStatusLine());
Java 9 中引入了标准Http Client API 。并在 Java 10 中进行了更新的。到了Java11,在前两个版本中进行孵化的同时,Http Client 几乎被完全重写,并且现在完全支持异步非阻塞。与此同时它是 Java 在 Reactive-Stream 方面的第一个生产实践,其中广泛使用了 Java Flow API。
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("http://openjdk.java.net/"))
.build();
client.sendAsync(request, BodyHandlers.ofString())
.thenApply(HttpResponse::body)
.thenAccept(System.out::println)
.join();
4.7、Helpful NullPointerExceptions( JEP358 )
a.b.c.i = 99;
a[i][j][k] = 99;
在之前,我们只能收到以下异常堆栈信息,然后必须借助DEBUG工具调查问题:
Exception in thread "main" java.lang.NullPointerException
at Prog.main(Prog.java:5)
优化后,我们可以得到更加优雅的空指针异常提示信息:
Exception in thread "main" java.lang.NullPointerException:
Cannot read field "c" because "a.b" is null
at Prog.main(Prog.java:5)
Exception in thread "main" java.lang.NullPointerException:
Cannot load from object array because "a[i][j]" is null
at Prog.main(Prog.java:5)
4.8、更加优雅的instance of 语法(JEP394)
if (obj instanceof String) {
String s = (String) obj; // grr...
...
}
上 面的instanc of语法一共做了三件事:
-
判断是否为String类型; -
如果是,转成String类型; -
创建一个名为s的临时变量;
if (obj instanceof String s) {// obj是否为String类型,如果是创建临时变量s
// Let pattern matching do the work!
...
}
我们可以看到,整体代码风格确实优雅了很多。变量s的作用域为满足条件的判断条件范围之内。因此,以下使用也是合法的:
if (obj instanceof String s && s.length() > 5) {// 因为&&具有短路功能
flag = s.contains("jdk");
}
但是以下用法,则会报错:
if (obj instanceof String s || s.length() > 5) { // Error!
...
}
合理使用,则可以达到以下效果:
// 优化使用前
public final boolean equals(Object o) {
if (!(o instanceof Point))
return false;
Point other = (Point) o;
return x == other.x
&& y == other.y;
}
// 优化使用后:
public final boolean equals(Object o) {
return (o instanceof Point other)
&& x == other.x
&& y == other.y;
}
4.9、更加优雅的Switch用法
// 之前
switch (day) {
case MONDAY:
case FRIDAY:
case SUNDAY:
System.out.println(6);
break;
case TUESDAY:
System.out.println(7);
break;
case THURSDAY:
case SATURDAY:
System.out.println(8);
break;
case WEDNESDAY:
System.out.println(9);
break;
}
// 之后
switch (day) {
case MONDAY, FRIDAY, SUNDAY -> System.out.println(6);
case TUESDAY -> System.out.println(7);
case THURSDAY, SATURDAY -> System.out.println(8);
case WEDNESDAY -> System.out.println(9);
}
还可以把switch语句当成一个表达式来处理:
T result = switch (arg) {
case L1 -> e1;
case L2 -> e2;
default -> e3;
};
static void howMany(int k) {
System.out.println(
switch (k) {
case 1 -> "one";
case 2 -> "two";
default -> "many";
}
);
}
还可以配合关键字yield,在复杂处理场景里,返回指定值:
int j = switch (day) {
case MONDAY -> 0;
case TUESDAY -> 1;
default -> {
int k = day.toString().length();
int result = f(k);
yield result;
}
};
还有吗?其实在JDK17中,还提出了Swtich 模式匹配的预览功能,可以做到更优雅的条件判断:
// 优化前
static String formatter(Object o) {
String formatted = "unknown";
if (o instanceof Integer i) {
formatted = String.format("int %d", i);
} else if (o instanceof Long l) {
formatted = String.format("long %d", l);
} else if (o instanceof Double d) {
formatted = String.format("double %f", d);
} else if (o instanceof String s) {
formatted = String.format("String %s", s);
}
return formatted;
}
// 优化后
static String formatterPatternSwitch(Object o) {
return switch (o) {
case Integer i -> String.format("int %d", i);
case Long l -> String.format("long %d", l);
case Double d -> String.format("double %f", d);
case String s -> String.format("String %s", s);
default -> o.toString();
};
}
五、字符串压缩-Compact Strings( JEP254 )
六、 Java Flow API
七、新一代JIT编译器 Graal
-
通过本文,大家可以对即将使用的JDK11及JDK17新特性有一个笼统的了解,希望可以看到一些Java预发最近几年的发展方向。 -
通过本文也可以看出,从JDK9到JDK17,Java生态还是生机勃勃。大量功能的更新意味着更优秀的性能及更高效的开发效率,积极主动的尝试高版本JDK; 当然,JDK8到JDK17还有需求优秀的新特性,例如:shanondoah垃圾回收器、Sealed Classes、Records; 鉴于本人能力有限,文中会出现一些漏洞,希望大家找出并指正,让本文成长为后续JDK17升级的扫盲手册。 -end-
本文分享自微信公众号 - 京东云开发者(JDT_Developers)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。