目录
免费的低耗能分析(Low-Overhead Heap Profiling)
弃用Parallel Scavenge和Serial Old的GC组合
背景
熬夜看完谢菲联3:1战胜热刺,曼城4:0横扫已经提前夺冠的红军,醒来又是无聊的一天........
jdk9新特性
目录结构的改变
jdk8及以前的目录结构如下所示(MemoryAnalyzer是我后来加的,不属于原有的结构)
jdk9的目录结构如下图所示,其中jre目录是后来加的,也就是原有jdk9目录不包含jre
模块化系统
要解决的问题
jre的膨胀和臃肿(每次JVM启动时,由于要加载rt.jar,所以要至少加载30~60MB的内存);不同版本的类库交叉依赖;每个公共类可以被类路径下任何其他的公共类所访问到,会导致使用并不想开放的api;类路径可能重复等
概念
模块实际上就是包外面再包一层,来管理包,使代码更安全
我们可以使用jlink工具,定制运行时环境
实现目标
模块独立、化繁为简
减少内存开销;只加载必要jdk模块,简化类库和大型应用的开发维护;改进java SE,使其适应不同大小的计算设备;改进安全性
示例
1)、创建项目,基于jdk9
2)、构建两个模块,demo0和demo1,demo1中引用demo0中的bean类
3)、点击Project Structure->Modules,把两个模块和项目的语言等级都设置成9,并把src目录标记为源目录
4)、点击设置->构建、执行、部署->编译器->java编译器,把三个部分的字节码版本也都设置成9
5)、在demo0的源目录下创建module-info.java文件,声明要导出的包
内容如下
module demo0 {
exports com.szc.bean; // 导出包
}
6)、在demo1的源目录下也创建module-info.java文件,声明要引用的模块
内容如下
module demo1 {
requires demo0;
}
然后就可以在demo1中使用demo0中导出包的内容了
import com.szc.bean.Person;
public class ModuleTest {
public static void main(String[] args) {
Person p = new Person("szc", 23);
}
}
而没有暴露的包,里面的类就不能被引用,例如下图中entity包下的User类
在demo1中强行使用会报错
7)、我们当然也可以使用第三方的模块,比如java.logging,在demo1中的module-info.java引入此包即可
module demo1 {
requires demo0;
requires java.logging;
}
然后,就能使用了
import com.szc.bean.Person;
import java.util.logging.Logger;
public class ModuleTest {
private static final Logger sLOGGER = Logger.getLogger("szc");
public static void main(String[] args) {
Person p = new Person("szc", 23);
sLOGGER.info(p.toString());
}
}
运行输出如下
jShell命令
提供类似python、scala的repl交互式编程环境
我们可以进行输出语句、定义变量等操作
也可以定义方法,并调用
以及方法的重载
还可以导包,显示导入的包
我们按/+tab可以显示所有可用的命令
其中/list可以显示输入历史
/edit可以调出编辑面板
面板中三个按钮自然表示取消、提交和退出编辑面板
jshell中没有受检异常,也就是自行捕获编译期异常
最后,ctrl-c退出,或者/exit退出
多版本兼容jar包
此机制可以让我们创建仅在某个版本的java环境中的class版本,也可以让class的后期版本直接覆盖前期版本
接口中的私有方法
jdk7中只能声明全局常量和抽象方法
interface IMyInterface {
public void test0();
}
jdk8中的接口可以有具体的静态方法和默认方法
interface IMyInterface {
public static void test1 () {
System.out.println("静态方法");
}
default void test2() { // 可以被实现类覆写
System.out.println("默认方法");
}
public void test0();
}
jdk9中的接口甚至私有方法都能有方法体,而jdk8及以前接口方法都是公有的
interface IMyInterface {
private void test() {
System.out.println("接口中的私有方法");
}
public static void test1 () {
System.out.println("静态方法");
}
default void test2() {
System.out.println("默认方法");
}
public void test0();
}
钻石操作符(泛型)的升级
创建泛型类对象时,可以覆写其中的方法
Set<String> set = new HashSet<>(){
@Override
public boolean add(String s) {
return super.add(s + "..");
}
};
测试代码
public class DiamondOperatorTest {
public static void main(String[] args) {
new DiamondOperatorTest().test();;
}
public void test() {
Set<String> set = new HashSet<>(){
@Override
public boolean add(String s) {
return super.add(s + "..");
}
};
set.add("1");
set.add("2");
set.add("3");
set.add("4");
for (String s : set) {
System.out.println(s);
}
}
}
输出如下
try语句的升级
传统的异常捕获语句如下所示,以流为例
public static void main(String[] args) {
InputStreamReader reader = null;
try {
reader = new InputStreamReader(System.in);
reader.read();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
jdk8中可以在try后面跟一对()内进行变量的实例化,然后就不用写finally了,JVM会自行释放资源
public static void main(String[] args) {
try (InputStreamReader reader = new InputStreamReader(System.in)) {
reader.read();
} catch (IOException e) {
e.printStackTrace();
}
}
jdk9中,()内的变量可以在外面实例化了,相关资源还是由JVM释放,但此时()内的变量自行成为final变量,不可再修改
public static void main(String[] args) {
InputStreamReader reader = new InputStreamReader(System.in);
try (reader) {
reader.read();
} catch (IOException e) {
e.printStackTrace();
}
}
如果有多个要捕获异常的资源,那么在()里用;隔开即可
public static void main(String[] args) {
InputStreamReader reader = new InputStreamReader(System.in);
InputStreamReader reader0 = new InputStreamReader(System.in);
try (reader; reader0) {
reader.read();
} catch (IOException e) {
e.printStackTrace();
}
}
下划线命名标识符的限制
jdk9中不能使用_做为标识符名了
public static void main(String[] args) {
String _ = "szc"; // 编译报错
}
String存储结构的变化
jdk8及以前,String使用char[]存放字符串
jdk9中就换成了字节数组
这么做的原因,是因为char数组使用两个字节存放一个字符,而占用堆空间的绝大多数对象都是String对象,绝大多数String存储的是拉丁字符,这些拉丁字符只使用1个字节就足够存放,所以jdk9中String对象使用了byte数组存放字符串。
jdk9把utf-16字符数组换成了字节数组,外加一个编码域,用来表示此字符串应该用什么编码规则编解码。如果编码域指定的编码是latin-1/ISO-8859-1,那么就是用一个字节表示一个字符;如果指定的编码是utf-16,就用两个字节表示一个字符
StringBuilder和StringBuffer的父类AbstractStringBuilder也应用了这一变化,使用字节数组存储字符串
快速创建只读集合
过去创建只读集合,需要调用Collections中的unmodifiableCollection方法
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");
Collection<String> strings = Collections.unmodifiableCollection(list);
上面的strings对象就是只读的,不能修改或添加数据
现在,可以直接调用具体的unmodifiable+集合名方法来创建具体的只读集合
List<Integer> integers = Collections.unmodifiableList(Arrays.asList(1, 2, 3));
下面是创建只读映射的方法
Map<Object, Object> map = Collections.unmodifiableMap(new HashMap<>() {
{ // 插入代码块,插入键值对
put("a", 1);
put("b", 2);
put("c", 3);
}
});
map.forEach((k, v) -> System.out.println(k + "-" + v));
上面的代码可以简化成of方法的调用
List<Integer> integers = List.of(1, 2, 3);
Map<Object, Object> map = Map.of("a", 1, "b", 2, "c", 3); // 入参顺序:k1, v1, k2, v2...
Map<String, Integer> map1 = Map.ofEntries(Map.entry("a", 1), Map.entry("b", 2), Map.entry("c", 3)); // 传入一个个键值对entry对象
map.forEach((k, v) -> System.out.println(k + "-" + v));
其中的integers和map对象就都是只读的
增强的流api
takeWhile
读取,直到遇见不符合条件的元素
List<Integer> list = Arrays.asList(11, 32, 44, 29, 52, 72, 82, 64, 4, 19);
list.stream().takeWhile(integer -> integer < 50).forEach(System.out::println);
输出如下,不会输出后面的4和19
dropWhile
作用和takeWhile互补,一直不读取,直到遇见不符合条件的元素
List<Integer> list = Arrays.asList(11, 32, 44, 29, 52, 72, 82, 64, 4, 19);
list.stream().dropWhile(x -> x < 50).forEach(System.out::println);
输出如下
of()方法和ofNullable()方法
of()可以创建含有一个null对象的流
Stream.of(1, 2, 4, null).forEach(System.out::println);
输出如下
ofNullable()可以创建只有一个null对象的流
Stream.ofNullable(null).forEach(System.out::println);
什么也不会输出,ofNullable()方法也可以传入非空对象
System.out.println(".................");
Stream.ofNullable(null).forEach(System.out::println);
System.out.println(".................");
Stream.ofNullable(2).forEach(System.out::println);
输出如下
iterator()重载方法
通过iterate()的第二个参数控制迭代进行的条件
Stream.iterate(0, x -> x < 5, x -> x + 1).forEach(System.out::println);
以上代码类似于
for (int i = 0; i < 5; i++) {
System.out.println(i);
}
输出结果
Optional类中的stream()方法
Optional.ofNullable(list).stream().forEach(System.out::println);
输出如下
如果要对Optional类的stream对象进行铺平的话,可以调用flatMap()方法
Optional.ofNullable(list).stream().flatMap(x -> x.stream()).forEach(System.out::println);
输出如下
多分辨率图像api
在mac上,jdk已经支持视网膜显示,但jdk8及以前并不支持windows和linux上的多分辨率显示
jdk9使用了比现在更先进的api:Direct2D for Windows和GTK+,而不是Xlib for Linux,支持图形、窗口和文本的自动缩放;而且提供了处理多分辨率图像的能力,适配不同的DPI。
全新的http客户端api
2015年的http2允许服务器主动push(推送)数据,也就是可以发送比客户端请求更多的数据
使用示例
public class HttpClient2Test {
public static void main(String[] args) throws IOException, InterruptedException {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder(URI.create("http://www.baidu.com"))
.GET().build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandler.asString());
System.out.println(response.statusCode());
System.out.println(response.version().name());
System.out.println(response.body());
}
}
在module-info.java中引用httpclient
requires jdk.incubator.httpclient;
被废弃的api
主要是Applet和appletviewer的api都被标记为废弃,因为主流浏览器已经取消了对java浏览器插件的支持,H5的出现更是加速了java浏览器插件的灭亡,我们可以使用java Web Start来代替applet,以实现从浏览器启动或安装应用程序
智能java编译工具sjavac
sjavac第一个阶段用于提升多核处理器下的编译速度,第二阶段使其成为默认的编译工具,并且能够编译运行低版本的java代码
统一的jvm日志系统
对所有jvm组件引入一个单一的日志系统,从而让这些组件支持细粒度的和易配置的jvm日志,避免碎片化。
javadoc的H5支持
jdk9中javadoc的输出,符合兼容H5标准,主要多了一个search功能
js的Nashorn引擎升级
Nashorn让java中嵌入js,并提供js解释引擎。jdk9中包含了一个用来解析Nashorn的ECMAScript语法树的api,这使得IDE和服务端不需要Nashorn的内部类,就能解析ECMAScrpit代码
动态编译器
引入AOT(Ahead Of Time),这个功能使得java应用在JVM启动前,就被编译成二进制代码。此功能在jdk9中处于实验阶段,稳定版要到jdk10中发布
总结
jdk9中还看不到的功能:
1)、标准化的和轻量级的json api
2)、新的货币api
展望:
1)、传统的大型企业或互联网应用,正在被云端、容器化应用、模块化微服务甚至FaaS所替代
2)、java标榜面向对象,现在却毫不顾忌地加入面向接口编程思想,又加入匿名对象的概念,这都是对传统面向对象的冲击
3)、java需要在新的计算场景下,改进开发效率
jdk11新特性
局部变量自动类型推断
public class Main {
public static void main(String[] args) {
var s = "s";
System.out.println(s.getClass().getCanonicalName()); // java.lang.String
}
}
自动类型推断必须使用var,类型推断后,类型就不能变了
// s = 1; // 不可再赋予新的类型
s = "a"; // ok
var不是一个关键字
int var = 1;
var变量必须赋初值
// var b; // 必须赋初值
类属性不能用var
public class Main {
// private var x = 1; // 类成员变量不能使用var
}
可用于lambda表达式
List.of(1, 2, 4, 5, 3).stream().map((var x) -> x + "_").forEach(System.out::println);
以及函数式接口
Consumer<String> consumer = (var t) -> System.out.println(t.toUpperCase());
consumer.accept("ss"); // SS
也可用于给参数加注解
Consumer<String> consumer = (@Deprecated var t) -> System.out.println(t.toUpperCase());
以下方式就不对了
// Consumer<String> consumer = (@Deprecated t) -> System.out.println(t.toUpperCase()); // 注解后面不能直接跟变量名
String类新增方法
判断是否是空白字符串
String s1 = "\t \n";
System.out.println(s1.isBlank()); // 判断是否为空白 true
去除首尾空白
s1 = "\t sss\n";
System.out.println(s1.trim().length()); // 去除首尾空白,可以去除全角空格
System.out.println(s1.strip().length()); // 去除首尾空白,但不能去除全角空格
System.out.println(s1.stripLeading()); // 去除头部空白
System.out.println(s1.stripTrailing()); // 去除尾部空白
以上代码输出如下
4
3
sss
sss
lines()方法可以对字符串每一行进行流式处理
"asc\nccc\nwww".lines().map(str -> str.toUpperCase()).forEach(System.out::println);
输出如下
ASC
CCC
WWW
repeat()方法重复此字符串
System.out.println("a".repeat(5)); // aaaaa
Optional方法增强
增加了对空值的处理
System.out.println(Optional.ofNullable(null).orElse("b")); // 如果为空,返回"b"
System.out.println(Optional.ofNullable(null).orElseGet(() -> "b")); // 也可以使用函数式接口实现orElse()
System.out.println(Optional.ofNullable(null).orElseThrow()); // 如果为空,排除异常
以上代码输出如下
b
b
Exception in thread "main" java.util.NoSuchElementException: No value present
at java.base/java.util.Optional.orElseThrow(Optional.java:382)
at com.szc.Main.main(Main.java:42)
transferTo()方法
文件输出可以直接调用inputStream的transferTo()方法,将输入流中数据直接复制刷写到输出流中
try (var inputStream = new FileInputStream("D:/test.jar");
var outputStream = new FileOutputStream("test2.jar")) {
inputStream.transferTo(outputStream);
} catch (Exception e) {
e.printStackTrace();
}
移除和废弃的一些内容
移除的:com.sun.awt.AWTUtilities、sum.misc.Unsafe.defineClass(被java.lang.invoke.MethodHandles.Lookup.defineClass替代)、Thread.destroy()、Thread.stop(Throwable)、sun.nio.disableSystemWideOverlappingFileLockCheck属性、sun.locale.formatasdefault属性、jdk.snmp模块、javafx模块、javaMissionControl等
JavaEE和CORBA模块,比如java.xml部分、java.corba、java.transaction、java.activation等模块,但是新增了java.transaction.xa模块
废弃的:-XX:+AggressiveOpts、-XX:UnlockCommercialFeatures、-XX:+LogCommercialFeatures
Nashorn js引擎,可以考虑使用GraalVM
pack200和unpack200,这是以前压缩jar包的工具,现在被废弃了
更简化的编译运行程序
如果java文件里没有使用别的文件里的自定义类,那么就可以直接使用java就可以编译运行java文件,也不会输出class文件
如果有两个main方法,就只会执行第一个
class A
{
public static void main(String[] args) {
System.out.println("aaa");
}
}
public class Test
{
public static void main(String[] args) {
System.out.println("bbb");
}
}
输出如下
Unicode10
Unicode10加入了8518个字符,4个脚本和56个新的emoji表情符号
新增的Epsilon收集器
这是一个处理内存分配但不负责内存回收的GC,堆内存用完,JVM即刻退出。关于垃圾回收和垃圾回收器,以及GC参数设置,请参见文章JVM学习之垃圾回收和垃圾回收器,文章最后也提到了EpsilonGC、ZGC、雪兰多GC等新的垃圾回收器。
如果启用了Epsilon收集器,System.gc()的调用就没有了意义。
启用Epsilon方法:-XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC
测试代码如下,配置参数:-Xms10m -Xmx10m -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC
package com.szc;
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<Test> tests = new ArrayList<>();
int count = 0;
while (true) {
tests.add(new Test());
count++;
if (count == 500) {
tests.clear();
}
}
}
}
class Test {
@Override
protected void finalize() throws Throwable {
System.out.println("finalize..");
}
}
输出如下
D:\develop\jdk\jdk-11.0.7\bin\java.exe -Xms10m -Xmx10m -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC "-javaagent:D:\develop\IntelliJ IDEA Community Edition 2019.1.3\lib\idea_rt.jar=52381:D:\develop\IntelliJ IDEA Community Edition 2019.1.3\bin" -Dfile.encoding=UTF-8 -classpath D:\develop\ideaWorkspace\JDK11Test\target\classes com.szc.Main
Terminating due to java.lang.OutOfMemoryError: Java heap space
Process finished with exit code 3
而去掉EpsilonGC之后的输出如下所示
可见尽管tests.clear()调用之后原有的500个对象成为了垃圾对象,但堆爆满时依旧没有发生GC去回收任何对象
EpsilonGC主要用途如下:
性能测试(过滤GC引起的性能消耗,相当于控制变量)、内存压力测试(看看不回收的情况下,到底能不能消耗指定大小的内存)、执行非常短的任务(GC反而是浪费时间)、VM接口测试、延迟吞吐量的改进等实验性质的调优
ZGC
低延迟的ZGC,不管堆内存多大,都能使得STW时间不会超过10ms;和现有的G1相比,应用吞吐能力的下降不会超过15%
ZGC是一个并发、基于区域(region)、标记压缩算法的GC,只有根结点扫描阶段会发生STW,因此停顿时间不会随着堆内存的增长和存活对象的增长而增长
启用ZGC的方法:-XX:+UnlockExperimentalVMOptions -XX:+UseZGC
目前ZGC只能用于64位的linux操作系统下
完全支持linux容器(包括docker)
jdk11以前的java应用程序在docker中运行的性能会下降,但现在此问题在容器控制组(cgroups)的帮助下得以解决,使JVM和docker配合得更加默契
G1的完全并行GC
jdk11升级了G1,使我们可以免费享受彻底并行的Full GC,快速的卡表扫描
免费的低耗能分析(Low-Overhead Heap Profiling)
通过JVMTI的SampledObjectAlloc回调提供一个低开销的堆分析方式
新的加密算法
用ChaCha20-Poly1305这种更加高效安全的加密算法,代替RC4;采用新的默认根权限证书集,跟随最新的HTTPS安全协议TLS1.3
Flight Recorder
用来记录java程序的运行,现在从商业版中开源出来了。
有两种使用方式:程序开始前,添加-XX:StartFilghtRecording参数;也可以在程序启动后,通过jcmd命令启动jfr
D:\develop\jdk\jdk-11.0.7\bin>jcmd 20412 JFR.start
20412:
Started recording 1. No limit specified, using maxsize=250MB as default.
Use jcmd 20412 JFR.dump name=1 filename=FILEPATH to copy recording data to file.
D:\develop\jdk\jdk-11.0.7\bin>jcmd 20412 JFR.dump name=1 filename=out.jfr
20412:
Dumped recording "1", 359.5 kB written to:
C:\Users\songzeceng\AppData\Local\JetBrains\IdeaIC2020.1\compile-server\out.jfr
D:\develop\jdk\jdk-11.0.7\bin>jcmd 20412 JFR.stop name=1
20412:
Stopped recording "1".
其中的20412是待测的java程序进程号,输出的文件路径可知是C:\Users\songzeceng\AppData\Local\JetBrains\IdeaIC2020.1\compile-server\out.jfr,但这是二进制文件,基本看不了
在jdk12及以后,才有了jfr命令可以查看jfr文件,例如查看CPU加载信息
D:\develop\jdk\jdk-12\bin>jfr print --events CPULoad C:\Users\songzeceng\AppData\Local\JetBrains\IdeaIC2020.1\compile-server\out.jfr
jdk.CPULoad {
startTime = 00:55:26.793
jvmUser = 6.37%
jvmSystem = 0.70%
machineTotal = 10.31%
}
jdk.CPULoad {
startTime = 00:55:27.798
jvmUser = 0.00%
jvmSystem = 0.13%
machineTotal = 4.57%
}
jdk.CPULoad {
startTime = 00:55:28.796
jvmUser = 0.00%
jvmSystem = 0.13%
machineTotal = 4.91%
}
jdk.CPULoad {
startTime = 00:55:29.798
jvmUser = 0.00%
jvmSystem = 0.13%
machineTotal = 4.05%
}
jdk.CPULoad {
startTime = 00:55:30.803
jvmUser = 0.13%
jvmSystem = 0.13%
machineTotal = 2.99%
}
jdk.CPULoad {
startTime = 00:55:31.804
jvmUser = 0.00%
jvmSystem = 0.26%
machineTotal = 4.36%
}
jdk.CPULoad {
startTime = 00:55:32.804
jvmUser = 0.13%
jvmSystem = 0.13%
machineTotal = 6.71%
}
jdk.CPULoad {
startTime = 00:55:33.807
jvmUser = 0.00%
jvmSystem = 0.26%
machineTotal = 4.66%
}
jdk.CPULoad {
startTime = 00:55:34.814
jvmUser = 0.00%
jvmSystem = 0.26%
machineTotal = 4.82%
}
jdk.CPULoad {
startTime = 00:55:35.829
jvmUser = 0.00%
jvmSystem = 0.39%
machineTotal = 5.01%
}
jdk.CPULoad {
startTime = 00:55:36.821
jvmUser = 0.00%
jvmSystem = 0.26%
machineTotal = 4.54%
}
jdk.CPULoad {
startTime = 00:55:37.822
jvmUser = 0.00%
jvmSystem = 0.26%
machineTotal = 4.29%
}
jdk.CPULoad {
startTime = 00:55:38.841
jvmUser = 0.00%
jvmSystem = 0.38%
machineTotal = 4.26%
}
jdk.CPULoad {
startTime = 00:55:39.824
jvmUser = 0.00%
jvmSystem = 0.26%
machineTotal = 3.85%
}
jdk.CPULoad {
startTime = 00:55:40.832
jvmUser = 0.00%
jvmSystem = 0.26%
machineTotal = 8.03%
}
jdk.CPULoad {
startTime = 00:55:41.835
jvmUser = 0.00%
jvmSystem = 0.26%
machineTotal = 4.34%
}
jdk.CPULoad {
startTime = 00:55:42.854
jvmUser = 0.00%
jvmSystem = 0.38%
machineTotal = 5.35%
}
jdk.CPULoad {
startTime = 00:55:43.839
jvmUser = 0.00%
jvmSystem = 0.26%
machineTotal = 3.89%
}
jdk.CPULoad {
startTime = 00:55:44.843
jvmUser = 0.00%
jvmSystem = 0.26%
machineTotal = 3.99%
}
jdk.CPULoad {
startTime = 00:55:45.845
jvmUser = 0.00%
jvmSystem = 0.39%
machineTotal = 5.01%
}
jdk.CPULoad {
startTime = 00:55:46.846
jvmUser = 0.13%
jvmSystem = 0.00%
machineTotal = 6.59%
}
jdk.CPULoad {
startTime = 00:55:47.844
jvmUser = 0.00%
jvmSystem = 0.13%
machineTotal = 2.83%
}
jdk.CPULoad {
startTime = 00:55:48.843
jvmUser = 0.00%
jvmSystem = 0.13%
machineTotal = 3.45%
}
jdk.CPULoad {
startTime = 00:55:49.845
jvmUser = 0.00%
jvmSystem = 0.13%
machineTotal = 3.61%
}
jdk.CPULoad {
startTime = 00:55:50.848
jvmUser = 0.00%
jvmSystem = 0.26%
machineTotal = 3.40%
}
jdk.CPULoad {
startTime = 00:55:51.852
jvmUser = 0.00%
jvmSystem = 0.26%
machineTotal = 3.73%
}
jdk.CPULoad {
startTime = 00:55:52.869
jvmUser = 0.00%
jvmSystem = 0.38%
machineTotal = 4.37%
}
jdk.CPULoad {
startTime = 00:55:53.855
jvmUser = 0.00%
jvmSystem = 0.26%
machineTotal = 5.43%
}
jdk.CPULoad {
startTime = 00:55:54.859
jvmUser = 0.00%
jvmSystem = 0.13%
machineTotal = 7.28%
}
jdk.CPULoad {
startTime = 00:55:55.858
jvmUser = 0.00%
jvmSystem = 0.13%
machineTotal = 7.23%
}
jdk.CPULoad {
startTime = 00:55:56.885
jvmUser = 0.00%
jvmSystem = 0.38%
machineTotal = 9.12%
}
jdk.CPULoad {
startTime = 00:55:57.865
jvmUser = 0.00%
jvmSystem = 0.27%
machineTotal = 3.01%
}
jdk.CPULoad {
startTime = 00:55:58.865
jvmUser = 0.00%
jvmSystem = 0.26%
machineTotal = 4.01%
}
jdk.CPULoad {
startTime = 00:55:59.885
jvmUser = 0.00%
jvmSystem = 0.38%
machineTotal = 4.91%
}
jdk.CPULoad {
startTime = 00:56:00.875
jvmUser = 0.00%
jvmSystem = 0.26%
machineTotal = 10.92%
}
jdk.CPULoad {
startTime = 00:56:01.872
jvmUser = 0.00%
jvmSystem = 0.26%
machineTotal = 15.21%
}
jdk.CPULoad {
startTime = 00:56:02.874
jvmUser = 0.13%
jvmSystem = 0.00%
machineTotal = 5.43%
}
jdk.CPULoad {
startTime = 00:56:03.875
jvmUser = 0.00%
jvmSystem = 0.13%
machineTotal = 5.02%
}
jdk.CPULoad {
startTime = 00:56:04.875
jvmUser = 0.00%
jvmSystem = 0.13%
machineTotal = 8.98%
}
jdk.CPULoad {
startTime = 00:56:05.880
jvmUser = 0.00%
jvmSystem = 0.26%
machineTotal = 3.93%
}
jdk.CPULoad {
startTime = 00:56:06.881
jvmUser = 0.00%
jvmSystem = 0.26%
machineTotal = 4.36%
}
jdk.CPULoad {
startTime = 00:56:07.882
jvmUser = 0.00%
jvmSystem = 0.26%
machineTotal = 2.74%
}
jdk.CPULoad {
startTime = 00:56:08.883
jvmUser = 0.13%
jvmSystem = 0.13%
machineTotal = 4.98%
}
jdk.CPULoad {
startTime = 00:56:09.887
jvmUser = 0.00%
jvmSystem = 0.26%
machineTotal = 4.79%
}
jdk.CPULoad {
startTime = 00:56:10.893
jvmUser = 0.00%
jvmSystem = 0.13%
machineTotal = 4.32%
}
jdk.CPULoad {
startTime = 00:56:11.897
jvmUser = 0.00%
jvmSystem = 0.26%
machineTotal = 4.94%
}
jdk.CPULoad {
startTime = 00:56:12.898
jvmUser = 0.00%
jvmSystem = 0.26%
machineTotal = 4.00%
}
jdk.CPULoad {
startTime = 00:56:13.900
jvmUser = 0.00%
jvmSystem = 0.26%
machineTotal = 4.47%
}
jdk.CPULoad {
startTime = 00:56:14.910
jvmUser = 0.00%
jvmSystem = 0.26%
machineTotal = 3.98%
}
jdk.CPULoad {
startTime = 00:56:15.903
jvmUser = 0.00%
jvmSystem = 0.26%
machineTotal = 4.30%
}
D:\develop\jdk\jdk-12\bin>
jdk12新特性
新的Shenandoah(雪兰多)收集器
雪兰多收集器使用的内存结构和G1类似,都是将内存划分为区域,整体流程也和G1相似。关于G1,请参见文章JVM学习之垃圾回收和垃圾回收器相关章节。
最大的区别在于雪兰多收集器实现了并发疏散环节,引入的Brooks Forwarding Pointer技术使得GC在移动对象时,对象的引用仍然可以访问,这样就降低了延迟,相关团队宣城是使99.9%的暂停小于10ms。
其工作周期如下:
1)、初始标记,并启动并发标记阶段
2)、并发标记遍历堆阶段
3)、并发标记完成阶段
4)、并发整理回收无活动区域阶段
5)、并发疏散,整理内存区域
6)、初始化更新引用阶段
7)、并发更新引用
8)、完成引用更新阶段
9)、并发回收无引用区域阶段
启用方法:
XX:+UnlockExperimentalVMOptions -XX:+UseShenandoahGC
微基准表达式
JMH(Java Microbenchmark Harness)是专门用于代码微基准测试的工具套件,用于方法层面的测试,精度可以达到微秒级。
其主要应用场景有:
1)、想准确知道某个方法需要执行多长时间,以及执行时间和输入之间的相关性
2)、对比接口的不同实现在给定条件下的吞吐量
3)、查看多少百分比的请求在多长时间内完成
jdk12添加了一套新的微基准测试,简化了现有微基准测试的运行和新微基准测试的创建过程
switch表达式
这个版本是jdk13的预览功能,所以要将jdk、jre和目标字节码的版本设置为13
首先是箭头操作符,可以同时处理多个case
public class Main {
public static void main(String[] args) {
Fruit f = Fruit.APPLE;
switch (f) {
case APPLE -> System.out.println(1);
case ORANGE, GRAPE -> System.out.println(2);
case PEAR, MANGO, WATERMALLON -> System.out.println(3);
default ->
System.out.println("No such fruit");
}
}
}
enum Fruit {
APPLE, ORANGE, GRAPE, PEAR, MANGO, WATERMALLON
}
输出自然是1
然后也可以把switch块当成一个表达式,用变量接收它的值
public class Main {
public static void main(String[] args) {
Fruit f = Fruit.APPLE;
int ret = switch (f) {
case APPLE -> 1;
case ORANGE, GRAPE -> 2;
case PEAR, MANGO, WATERMALLON -> 3;
default ->
throw new IllegalStateException("error");
};
System.out.println(ret);
}
}
enum Fruit {
APPLE, ORANGE, GRAPE, PEAR, MANGO, WATERMALLON
}
这个改进类似于kotlin中的when表达式
JVM常量api
java.base模块新增了java.lang.constant包,里面定义了一系列的基于值的符号引用类型,用于描述每种可加载常量,主要用于节省内存
目前基本数据类型的包装类和String都实现了java.lang.constant.Constable和java.lang.constant.ConstantDesc两个接口,两个接口定义如下
package java.lang.constant;
import java.lang.Enum.EnumDesc;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.invoke.VarHandle.VarHandleDesc;
public interface ConstantDesc {
Object resolveConstantDesc(MethodHandles.Lookup lookup) throws ReflectiveOperationException;
}
///
package java.lang.constant;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodType;
import java.lang.invoke.VarHandle;
import java.util.Optional;
public interface Constable {
Optional<? extends ConstantDesc> describeConstable();
}
主要作用就是让加载这些类的时候更加快捷
只保留一个AArch64实现
之前的jdk存在两个64位的ARM端口,两个端口都产生了aarch64实现,但把src/hotspot/cpu/arm中的称为arm64,把open/src/hotspot/cpu/aarch64称为aarch64
jdk12中删除了arm64,只保留其中关于32位的arm端口实现,64位的全交给open/src/hotspot/cpu/aarch64中的AArch64来实现
这样就让开发贡献者避免实现两个64的arm
默认类数据共享归档文件
在同一台机器上启动多个JVM时,如果每个JVM都单独装在自己的类,那么启动成本和内存占用就很高。所以java团队就引入了类数据共享机制(Class Data Sharing,简称CDS),即一些核心类在JVM进程间共享,每个JVM只需要装载自己的应用类即可
CDS目前可以支持系统类、引导类和应用类,jdk12对此的改进是将其默认使能:-Xshare:auto,并对64位平台的jdk架构进行优化,自动调用java -Xshare:dump,生成的文件保存在JavaHome/lib/server目录下,名为classes.jar
可中止的G1混合收集器
当G1回收耗时超过目标暂停时间,就会终止垃圾回收过程
主要思路是:把回收集分为必须部分和可选部分,优先处理必须部分。
必须部分主要包括G1不能递增处理的部分(如年轻代),也可以包含老年代以提高效率。
在优先处理必须部分时,会维护可选部分的一些数据,但产生的CPU开销不会超过1%,而且会增加本机内存使用率;处理完必须部分后,如果还有时间,就处理可选部分,如果剩下时间不够,就可能只处理可选部分的一个子集。处理完一个子集后,G1会根据剩余时间来决定是否继续收集。
G1及时返回未使用的已分配内存
jdk12中的G1将在应用程序不活动期间定期生成或持续循环检测整体的java堆使用情况,以便更及时地将java堆中不使用的内存返回给OS。这一改进带来的优势在云平台的容器环境中更加明显,此时内存利用率的提高会直接降低经济成本
jdk12为此新增了两个参数:G1PeriodicGCInterval和G1PeriodicGCSystemLoadThreshold,两者均为0的话表示禁用此功能。前者表示定期检测的周期,后者表示触发内存返回的系统负载阈值
另有一个参数G1PeriodicInvokesConcurrent来控制定期GC的类型,默认是Full GC,如果设置值了,就会继续上一个或启动一个新的并发周期
如果定期GC严重影响程序执行,就要考虑程序的CPU负载,或者让用户禁用定期GC。
String中新增的方法
transform()方法
对字符串进行链式转换
String transform = "szc".transform(x -> x + "szc").transform(String::toUpperCase);
System.out.println(transform); // SZCSZC
indent()方法
在字符串的每一行前面加空格
String indent = "szcc\ncc\nanyang".indent(3);
System.out.println(indent);
输出如下,每一行前面加了3个空格
szcc
cc
anyang
Files中新增的方法——mismatch()
返回两个Path对应的文件内容中首次字节不匹配发生的行索引,从0开始。如果返回-1,就是指两个文件内容完全一样
try (FileWriter fileWriter1 = new FileWriter("test1.txt");
FileWriter fileWriter2 = new FileWriter("test2.txt")) {
fileWriter1.write("a");
fileWriter1.write("b");
fileWriter1.write("c");
fileWriter1.close();
fileWriter2.write("a");
fileWriter2.write("B");
fileWriter2.write("C");
fileWriter2.close();
System.out.println(Files.mismatch(Path.of("test1.txt"), Path.of("test2.txt")));
} catch (IOException e) {
e.printStackTrace();
}
输出为1
其他新增项
新增支持unicode11,包括684个字符、11个块和7个脚本
支持压缩数字格式化,比如1000可以格式化为1K,1000000可以格式化为1M,也可以根据指定的语言环境格式化
var cnf = NumberFormat.getCompactNumberInstance(Locale.CHINA, NumberFormat.Style.SHORT);
System.out.println(cnf.format(1L << 30));
System.out.println(cnf.format(1000));
System.out.println(cnf.format(1L << 40));
System.out.println(cnf.format(1_92000));
System.out.println(cnf.format(192000));
输出如下
11亿
1,000
1兆
19万
19万
jdk13新特性
动态CDS档案
CDS是jdk12的特性,jdk13中这个特性支持java应用执行之后进行动态归档,以后执行java程序后一些类就可以直接从这些归档文件中加载了
ZGC:提交未使用的堆内存
ZGC在jdk11中引入的收集器,jdk13中使能了向OS提交未使用的堆内存
ZGC中的区域称之为ZPage,当ZGC压缩堆时,ZPage被释放,然后变成ZPageCache,最后使用LRU算法对PageCache区域进行定时清除。时间间隔默认为5分钟,用户自定义时间间隔尚未实现,而如果-Xms和-Xmx相等的话,这个功能就相当于没有
重新实现旧版套接字api
重新实现了Socket接口,新的实现类是NioSocketImpl来替换jdk1.0的PlainSocketImpl,其有以下特点:
1)、使用和NIO相同的内部结构,无需再使用本地代码
2)、和现有的缓冲区机制集成在一起,因此不需要为IO使用线程栈
3)、使用java.util.concurrent锁,而不是synchronized同步方法,增强了并发能力
4)、新的实现是jdk13中的默认实现,但旧的实现还没有删除,可以通过系统属性jdk.net.usePlainSocketImpl(不为空,且不为false)来切换到旧版本
switch表达式中引入yield
default语句里可以单独用yield返回值
String x = "3";
int i = switch (x) {
case "1" -> 10;
case "3" -> 30;
case "4", "5" -> 20;
default -> {
yield x.length();
}
};
case语句里也可以使用yield,但yield和->只能存在一种
i = switch (x) {
case "1":
System.out.println("aa");
yield 10;
case "3":
System.out.println("bb");
yield 30;
case "4", "5":
yield 20;
default:
yield x.length();
};
yield用于从switch语句里返回值,作用域要比return小,和->相比它又允许case里有更多的逻辑
文本块
文本块用来表示多行字符串,用一对"""包住即可
String s = """
<html>
<head>
<meta charset="utf-8"/>
</head>
<body>
<p>aaa</p>
</body>
</html>
""";
比如表示SQL时
s = """
select * from students
where id in (12, 13, 14, 15)
order by grade desc
""";
文本块避免了换行转义,提高了可读性
文本块第一个字符为行终止符,所以第一个可见字符必须在第二行
s = """
a
""";
每一行末尾实际上都有一个行终止符(换行符)。而且,运行时,文本块将被实例化为String实例,所以使用和传统的字符串对象是一样的;编译时,文本块中每一行后面多余的空格会被去掉,前面多余空格数将取决于结束"""的情况:如果结束"""是单独一行的话,保留的空格数将与结束"""对齐;否则全部删除
s = """
a
""";
System.out.println(s.length()); // 2
var s1 = """
a""";
System.out.println(s1.length()); // 1
System.out.println(s1.equals(s)); // false
System.out.println(s1 == "a"); // true
s = """
a
""";
System.out.println(s.length()); // 5
文本块可以表示空字符串
s1 = """
""";
System.out.println(s1.equals("")); // true
文本块中显式\n依旧有效
System.out.println("""
a\n
""".length()); // 3
文本块中的"就不用转义了,但"""需要转义
System.out.println("""
"I`ve got to go, \""" now \""". ", said Jason.
"Ok, as you wish.", said Mike
""");
/*
"I`ve got to go, """ now """. ", said Jason.
"Ok, as you wish.", said Mike
*/
文本块也可以拼接,注意结束"""就不要单独一行了
System.out.println("""
a""" + """
bcd"""); // abcd
可以使用replace()方法简化拼接过程
String type = "String";
System.out.println("""
The type is $type""".replace("$type", type)); // The type is String
或者formatted()方法,此方法在jdk13刚被引进,就被标记为移除
String type = "String";
System.out.println("The type is %s".formatted(type)); // The type is String
似乎更推荐String.format()方法
System.out.println(String.format("The type is %s", type)); // The type is String
新增项
1)、添加new FileSystem(Path)、new FileSystem(Path, Map<String, ?>)方法
2)、新的ByteBuffer Bulk get/put方法
3)、支持Unicode12.1
4)、添加-XX:SoftMaxHeapSize,仅对ZGC有用
5)、ZGC最大的堆大小升到16TB
总结
语法层面:改进版的switch表达式、文本块
API层面:NioSocketImpl来替换原有的PlainSocketImpl
GC层面:改进了ZGC,以支持回收未使用的内存
jdk14新特性
instanceof省去了强制类型转换的过程
以前是这么写的
Object obj = "szc";
if (obj instanceof String) {
String s = (String) obj;
}
现在可以这么写
if (obj instanceof String s) {
System.out.println(s);
}
类的equals()方法因此也可以被简化
class Monitor {
private String mName;
private int mCount;
@Override
public boolean equals(Object o) {
return o instanceof Monitor other && other.mName.equals(mName) && other.mCount == mCount;
}
}
更详细的空指针异常
在运行时加上参数-XX:+ShowCodeDetailsInExceptionMessages,可以更详细地显示空指针异常。例如以下代码
public class Main {
public static void main(String[] args) {
MonitorGroup group = new MonitorGroup();
group.show();
}
}
class MonitorGroup {
private List<Monitor> mMonitors;
public MonitorGroup() {
}
public MonitorGroup(List<Monitor> mMonitors) {
this.mMonitors = mMonitors;
}
public void show() {
System.out.println(mMonitors.size());
}
}
class Monitor {
}
运行后报错如下
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "java.util.List.size()" because "this.mMonitors" is null
at com.szc.MonitorGroup.show(Main.java:33)
at com.szc.Main.main(Main.java:18)
报出了执行哪个方法时出现了异常,并且给出了哪个变量为空
Record数据结构
Record数据结构可以更方便地创建java bean类
1)、首先创建时选用record
2)、然后给出属性即可,这些属性都是final常量,也就是只能被赋值一次
public record User(String mName, int mAge) {
}
以上代码就相当于原始的属性+有参构造方法+get方法+toString()+equals()+hashcode()方法
User u = new User("szc", 23);
System.out.println(u); // User[mName=szc, mAge=23]
System.out.println(u.mName()); // szc
System.out.println(u.mAge()); // 23
System.out.println(u.equals(new User("szc", 23))); // true
System.out.println(u.hashCode() + ", " + new User("szc", 23).hashCode()); // 3546299, 3546299
record也可以覆写或定义方法,以及静态属性和方法
public record User(String mName, int mAge) {
public static String sNation;
public static String getNation() {
return sNation;
}
public void show() {
System.out.println("Name = " + mName + ", age = " + mAge);
}
}
但是不能声明非静态的属性,而且record类也不能是抽象类
public record User(String mName, int mAge) {
public static String sNation;
// public int mCount;
}
// abstract record Person() { }
而且record类是final的,不能有继承语句
// record Count() extends Thread { }
jdk12/13中的switch表达式正式转正
switch表达式终于不是测试功能了..
增强版的文本块
如果不想换行,可以加上\
System.out.println("""
aaa\
bbb
"""); // aaabbb\n
弃用Parallel Scavenge和Serial Old的GC组合
下图中绿色虚线和CMS也被抛弃了,所以只剩下Serial GC -- Serial Old GC、Parallel Scavenge -- Parallel Old和G1三种GC(组合)了。关于这些经典垃圾回收器,请参见文章JVM学习之垃圾回收和垃圾回收器相关章节。
windows和mac上可用ZGC
使用参数为-XX:+UnlockExperimentalVMOptions -XX:+UseZGC
结语
虽然jdk迭代更新很快,但由于成规模的项目用的都是jdk7或jdk8,而且很多第三方库也都是基于jdk7或8的,所以选择对项目的jdk版本升级还是要三思而行,因为这些新的jdk似乎只是让开发更容易,而客户感受不到这些改进,那么带来的经济效益就值得商榷了。
不过,虽然升级jdk或者其他代码重构可能是费力不讨好的事儿,但尝个鲜也无可厚非,就当长见识,开开眼