会挽雕弓如满月,西北望,射天狼。这篇文章主要讲述Inside Java Newscast #1 深度解读相关的知识,希望能为你提供帮助。
本文是
??Inside java Newscast #1??
的个人体验与解读。视频地址:?点击这里??
?????? Chapters ??????
- 0:00 - Intro
- 0:57 - Java 16 – Intro
- 1:16 - Java 16 – Records
- 1:43 - Java 16 – Type Pattern Matching
- 1:58 - Java 16 – Sealed Classes - Preview
- 2:25 - Java 16 – Stream API
- 2:51 - Java 16 – HTTP/2 API
- 3:14 - Java 16 – Unix Domain Sockets
- 3:32 - Java 16 – Project Panama (Incubating)
- 4:07 - Java 16 – JDK Flight Recorder
- 4:39 - Java 16 – jpackage
- 5:02 - Java 16 – Performance
- 5:23 - Java 16 – Security
- 5:48 - Java 16 – Ports
- 5:55 - Java 16 – Deprecations/Limitations
- 6:49 - Java 16 – Outro
- 7:08 - Java 17
- 7:22 - Java 17 – Another Port
- 7:34 - Java 17 – Applet for Removal
- 7:55 - Java 17 – Sealed Classes
- 8:12 - Outro
- ??JEP 359: Records(Preview)??
- ??JEP 384: Records (Second Preview)??
- ??JEP 395: Records??
简单说来其实就是(编译后查看下字节码就能看出来),在编译后,根据 Record 源码插入相关域与方法的字节码,包括:
- 自动生成的 private final field
- 自动生成的全属性构造器
- 自动生成的 public getter 方法
- 自动生成的 hashCode(),equals(),toString() 方法:
- 从字节码可以看出,这三个方法的底层实现是 invokeDynamic 另一个方法
- 调用的是
?
?ObjectMethods.java?
? 这个类中的 ??bootstrap?
? 方法
Record 这个特性当初是为了适应什么场景设计的,以及某些设计为何被舍弃,可以参考 Gotez 大佬的这篇文章 ??java-14-feature-spotlight??. 其重中主要的看点总结如下:
1.Java Records 最常用于的地方就是方法多个返回结果,原来我们可能需要用 Apache-commons 里面的 Pair 或者 Tuple 这样的对象封装,或者自己新建一个类型。现在可以使用 Record。
2.第二个常见应用即在 Stream 中传递的过程中保持原有对象,并且减少运算,例如 Stream 排序:
List< Player> topN
= players.stream()
.sorted(Comparator.comparingInt(p -> getScore(p)))
.limit(N)
.collect(toList());
这么写的话,每次作比较都会调用一次 ?
?getScore(p)?
?,这个调用次数是
??O(n^2)?
?。利用 Record 可以用比较少的代码和改动实现减少运算:record PlayerScore(Player player, Score score)
// convenience constructor for use by Stream::map
PlayerScore(Player player)this(player, getScore(player));
List< Player> topN
= players.stream()
.map(PlayerScore::new)
.sorted(Comparator.comparingInt(PlayerScore::score))
.limit(N)
.map(PlayerScore::player)
.collect(toList());
最后再推荐下我写的这篇关于 Record 的序列化的一些解析和思考:??Java Record 的一些思考 - 序列化相关??
Java 16 – Type Pattern Matching相关 JEP 地址:
- ??JEP 305: Pattern Matching for instanceof (Preview)??
- ??JEP 375: Pattern Matching for instanceof (Second Preview)??
- ??JEP 394: Pattern Matching for instanceof??
Nicolai 对 Type Pattern Matching 的说明??Nicolai 的这篇文章?? 对 Type Pattern Matching 的说明非常详细,总结如下:
原来需要这么写的代码:
void feed(Animal animal)
if (animal instanceof Elephant)
((Elephant) animal).eatPlants();
else if (animal instanceof Tiger)
((Tiger) animal).eatMeat();
现在可以直接这么写:
void feed(Animal animal)
if (animal instanceof Elephant elephant)
elephant.eatPlants();
else if (animal instanceof Tiger tiger)
tiger.eatMeat();
不需要空指针判断,因为 instanceof 已经自带 null 判断了,符合条件的 Type Pattern Matching 变量不会为 null。并且, Type Pattern Matching 不支持向上匹配,因为这个没有意义,即下面的代码会编译报错:
public void upcast(String string)
// compile error
if (string instanceof CharSequence sequence)
System.out.println("Duh");
还有一个常用的地方即实现 equals:
// old
@Override
public boolean equals(Object o)
if (this == o)
return true;
if (!(o instanceof Equals))
return false;
Type other = (Type) o;
return someField.equals(other.someField)
& & anotherField.equals(other.anotherField);
// new
@Override
public final boolean equals(Object o)
return o instanceof Type other
& & someField.equals(other.someField)
& & anotherField.equals(other.anotherField);
其实 Type Pattern Matching 是个语法糖其实这个特性是一个语法糖,我们可以简单测试下:
public class TypePatternMatching
public static void main(String[] args)
Object object = new Object();
if (object instanceof String s)
System.out.println("a");
查看编译后的字节码:
public class test.TypePatternMatching
public test.TypePatternMatching();
Code:
0: aload_0
1: invokespecial #1// Method java/lang/Object."< init> ":()V
4: return
public static void main(java.lang.String[]);
Code:
0: new#2// class java/lang/Object
3: dup
4: invokespecial #1// Method java/lang/Object."< init> ":()V
7: astore_1
8: aload_1
9: instanceof#7// class java/lang/String
12: ifeq28
15: aload_1
16: checkcast#7// class java/lang/String
19: astore_2
20: getstatic#9// Field java/lang/System.out:Ljava/io/PrintStream;
23: ldc#15// String a
25: invokevirtual #17// Method java/io/PrintStream.println:(Ljava/lang/String; )V
28: return
可以看出,字节码其实和下面的写法是一样的:
public static void main(String[] args)
Object object = new Object();
if (object instanceof String)
String s = (String)object;
System.out.println("a");
大家可以反编译下这个 class,就能看出来。
Java 16 – Sealed Classes - PreviewSealed Class 在 Java 17 已经发布了,相关的 JEP 如下:
- ??JEP 360: Sealed Classes (Preview)??
- ??JEP 397: Sealed Classes (Second Preview)??
- ??JEP 409: Sealed Classes??
interface Shape
record Circle(double radius) implements Shape
record Rectangle(double width, double height) implements Shape
double area(Shape shape)
if (shape instanceof Circle circle)
return circle.radius() * circle.radius() * Math.PI;
if (shape instanceof Rectangle rect)
return rect.width() * rect.height();
throw new IllegalArgumentException("Unknown shape");
我们如何能确定我们枚举完了所有的 Shape 呢? Sealed Class 这个特性为我们解决这个问题,Sealed Class 可以在声明的时候就决定这个类可以被哪些类继承:
sealed interface Shape permits Rectangle, Circle
record Circle(double radius) implements Shape
record Rectangle(double width, double height) implements Shape
double area(Shape shape)
if (shape instanceof Circle circle)
return circle.radius() * circle.radius() * Math.PI;
if (shape instanceof Rectangle rect)
return rect.width() * rect.height();
throw new IllegalArgumentException("Unknown shape");
Sealed Class (可以是 abstract class 或者 interface )在声明时需要指定所有的实现类的名称。针对继承类,有如下限制:
- Sealed Class 的继承类必须和 Sealed Class 在同一个模块下,如果没有指定模块,就必须在同一个包下
- 每个继承类必须直接继承 Sealed Class,不能间接继承
- 每个继承类必须是下面三种之一:
- final 的 class,Java Record 本身就是 final 的
- sealed 的 class,可以进一步指定会被哪些子类实现
- non-sealed 的 class,也是一种扩展,但是打破 Sealed Class 的限制,Sealed Class 不知道也不关心这种的继承类还会有哪些子类。
sealed interface Shape permits Rectangle, Circle, Triangle, WeirdShape
record Circle(double radius) implements Shape
record Rectangle(double width, double height) implements Shape
sealed interface Triangle extends Shape permits RightTriangle, NormalTriangle
record RightTriangle(double width, double height) implements Triangle
record NormalTriangle(double width, double height) implements Triangle
static non-sealed class WeirdShape implements Shape
class Star extends WeirdShape
double area(Shape shape)
if (shape instanceof Circle circle)
return circle.radius() * circle.radius() * Math.PI;
if (shape instanceof Rectangle rect)
return rect.width() * rect.height();
if (shape instanceof RightTriangle rt)
return rt.width() * rt.height() / 2;
if (shape instanceof NormalTriangle nt)
return nt.width() * nt.height() / 2;
throw new IllegalArgumentException("Unknown shape");
如果结合 Pattern Matching for switch 这个特性,就能实现更加方便的写法,但是目前 Java 17 中,Pattern Matching for switch 还处于 Preview:??JEP 406: Pattern Matching for switch (Preview)??。我们需要在编译参数和启动参数中加上 ?
?--enable-preview?
?,这样就能像下面这样写代码:double area(Shape shape)
return switch (shape)
case Circle circle -> circle.radius() * circle.radius() * Math.PI;
case Rectangle rect -> rect.width() * rect.height();
case RightTriangle rt -> rt.width() * rt.height() / 2;
case NormalTriangle nt -> nt.width( ) * nt.height() / 2;
default -> throw new IllegalArgumentException("Unknown shape");
;
Java 16 – Stream API 更新【Inside Java Newscast #1 深度解读】Java 16 中针对 Stream API 有两个更新,这里先提一个题外话,如果想看 JDK 不同版本之间有何差异,增加或者删除了哪些 API,可以通过下面这个链接查看:
- ??https://javaalmanac.io/jdk/17/apidiff/11/??
同时,我们也可以通过 JDK 内置 jdeps 工具查找过期以及废弃API以及对应的替换
jdeps --jdk-internals -R --class-path libs/* $project
libs是你的所有依赖的目录,$project是你的项目jar包,示例输出:
...
JDK Internal APISuggested Replacement
-------------------------------------
sun.misc.BASE64EncoderUse java.util.Base64 @since 1.8
sun.reflect.ReflectionUse java.lang.StackWalker @since 9
关于这个更新,我写了一篇文章进行解析:??Java 16 中新增的 Stream 接口的一些思考??,核心内容总结如下:
假设有邮件这个 Record 类,包含 id,以及发送到的邮箱和抄送到的邮箱:
record Mail(int id, Set< String> sendTo, Set< String> cc)
我们想找到一批邮件的所有不同的联系人,最后放到一个 List 中,可能会这么写:
Set< String> collect = mails.stream().flatMap(mail ->
Set< String> result = new HashSet< > ();
result.addAll(mail.sendTo());
result.addAll(mail.cc());
return result.stream();
).collect(Collectors.toSet());
但是,这样写显然很不优雅,首先是对于每一个 Mail 都创建了额外的 Set 和对应的 Stream,并且,对于每个 mail 的 sendTo 还有 cc 都遍历了两遍(addAll 一遍,后续 Stream 又一遍)。其实我们的目前只是将 mail 中的 cc 以及 sendTo 取出来,用于参与后续的 Stream。在这种场景下,就非常适合用 mapMulti:
Set< String> collect = mails.stream().< String> mapMulti((mail, consumer) ->
mail.cc().forEach(consumer::accept);
mail.sendTo().forEach(consumer::accept);
).collect(Collectors.toSet());
可以看出:
- mapMulti 的入参是一个
?
?BiConsumer?
?,其实就是使用其参数中的 consumer 接收参与 Stream 后续的对象 - mapMulti 的思路就是将参数中的需要参与后续 Stream 的对象传入 consumer 来继续 Stream
- consumer
没有限制对象类型,想要限制必须加上形参
?
?< String> ?
? 否则最后返回的是 ??Set< Object> ?
? 而不是 ??Set< String> ?
?
?collect(Collectors.toList())?
?,生成的 List 是
??ArrayList?
?,是可变的。但是这次新加的 Api,toList
生成的是
??UnmodifiableList?
?,是不可变的。所以这两个 API
不能直接互相替换,需要做一些检查确认没有更改才能替换。Java 16 – HTTP/2 APIJava 16 中还引入了两个关于 HTTP/2 API 的 JDK 补充,参考:
- ??JDK-8252304: Seed an HttpRequest.Builder from an existing HttpRequest??
- ??JDK-8252382: Add a new factory method to concatenate a sequence of BodyPublisher instances into a single publisher??
- ??JEP 380: Unix-Domain Socket Channels??
//创建 UnixDomainSocketAddress
Path socketFile = Path.of("/home/zhanghaxi/process1");
UnixDomainSocketAddress address = UnixDomainSocketAddress.of(socketFile);
//服务端监听
ServerSocketChannel serverChannel = ServerSocketChannel.open(StandardProtocolFamily.UNIX);
serverChannel.bind(address);
SocketChannel channel = serverChannel.accept();
//客户端连接
SocketChannel channel = SocketChannel.open(StandardProtocolFamily.UNIX);
channel.connect(address);
关于 NIO 的例子,请参考:??https://docs.oracle.com/en/java/javase/16/core/internet-protocol-and-unix-domain-sockets-nio-example.html??
相比于 TCP/IP 本地回环连接访问,由于 Unix Domain Sockets 知道他访问的是本地进程,所以减少了很多检查与校验(例如寻址与路由),同时由于不用做这些检查,包的大小也要小一些。支持 Unix Domain Sockets 的操作系统有 Linux, MacOS 和 Windows 10 以上的版本以及 Windows Server 2019 以上的版本.
Java 16 – Project Panama (Incubating)Project Panama 是一个让 Java 变得更全面的项目,目前还处于孵化中的状态。他目前主要包括以下三个 API:
- Vector API:让 Java 也能使用新的 CPU 指令例如 SIMD(Single Instruction Multiple Data)相关指令来优化计算速度
- Foreign Linker API:让 Java 可以直接调用系统库,不用通过 JNI 再封装一层。
- Foreign-Memory Access API:让 Java 可以直接操作外部内存,突破现有对外内存 API 的限制,同时也是可以整合统一现有堆外内存操作的 API。
- ??JEP 338: Vector API (Incubator)??
- ??JEP 414: Vector API (Second Incubator)??:Java 17 中的
- ??JEP 417: Vector API (Third Incubator)??:Java 18 中的
一个主要的优化点就是循环,过去的循环(标量循环),一次在一个元素上执行,那很慢。现在,您可以使用 Vector API 将标量算法转换为速度更快的数据并行算法。一个使用 Vector 的例子:
//测试指标为吞吐量
@BenchmarkMode(Mode.Throughput)
//需要预热,排除 jit 即时编译以及 JVM 采集各种指标带来的影响,由于我们单次循环很多次,所以预热一次就行
@Warmup(iterations = 1)
//单线程即可
@Fork(1)
//测试次数,我们测试10次
@Measurement(iterations = 10)
//定义了一个类实例的生命周期,所有测试线程共享一个实例
@State(value = https://www.songbingjia.com/android/Scope.Benchmark)
public class VectorTest
private static final VectorSpecies< Float> SPECIES =
FloatVector.SPECIES_256;
final int size = 1000;
final float[] a = new float[size];
final float[] b = new float[size];
final float[] c = new float[size];
public VectorTest()
for (int i = 0; i < size; i++)
a[i] = ThreadLocalRandom.current().nextFloat(0.0001f, 100.0f);
b[i] = ThreadLocalRandom.current().nextFloat(0.0001f, 100.0f);
@Benchmark
public void testScalar(Blackhole blackhole) throws Exception
for (int i = 0; i < a.length; i++)
c[i] = (a[i] * a[i] + b[i] * b[i]) * -1.0f;
@Benchmark
public void testVector(Blackhole blackhole)
int i = 0;
//高于数组长度的 SPECIES 一次处理数据长度的倍数
int upperBound = SPECIES.loopBound(a.length);
//每次循环处理 SPECIES.length() 这么多的数据
for (; i < upperBound; i += SPECIES.length())
// FloatVector va, vb, vc;
var va = FloatVector.fromArray(SPECIES, a, i);
var vb = FloatVector.fromArray(SPECIES, b, i);
var vc = va.mul(va)
.add(vb.mul(vb))
.neg();
vc.intoArray(c, i);
for (; i < a.length; i++)
c[i] = (a[i] * a[i] + b[i] * b[i]) * -1.0f;
public static void main(String[] args) throws RunnerException
Options opt = new OptionsBuilder().include(VectorTest.class.getSimpleName()).build();
new Runner(opt).run();
注意使用处于孵化的 Java 特性需要加上额外的启动参数将模块暴露,这里是?
?--add-modules jdk.incubator.vector?
?,需要在 javac 编译和 java 运行都加上这些参数,使用 IDEA 即:测试结果:
BenchmarkModeCntScoreErrorUnits
VectorTest.testScalarthrpt107380697.998 ± 1018277.914ops/s
VectorTest.testVectorthrpt1037151609.182 ± 1011336.900ops/s
其他使用,请参考:??fizzbuzz-simd-style??,这是一篇比较有意思的文章(虽然这个性能优化感觉不只由于 SIMD,还有算法优化的功劳,哈哈)
Foreign Linker API相关 JEP:
- ??JEP 389: Foreign Linker API (Incubator)??
- ??JEP 412: Foreign Function & Memory API (Incubator)??:在 Java 17 中,和 Foreign Linker API 整合到了一起
- ??JEP 419: Foreign Function & Memory API (Second Incubator)??:位于 Java 18 中
以上例子来自于 ??https://headcrashing.wordpress.com/2021/02/06/spare-keystrokes-with-the-record-keyword-modern-java-jdk-16-head-crashing-informatics-26-2/?? ,感兴趣的可以查看下
Foreign-Memory Access API
- ??JEP 370: Foreign-Memory Access API (Incubator)??
- ??JEP 383: Foreign-Memory Access API (Second Incubator)??
- ??JEP 393: Foreign-Memory Access API (Third Incubator)??
- ??JEP 412: Foreign Function & Memory API (Incubator)??:在 Java 17 中,和 Foreign Linker API 整合到了一起
- ??JEP 419: Foreign Function & Memory API (Second Incubator)??:位于 Java 18 中
- ??ByteBuffer API?? 可以提供直接内存的访问 (DirectBuffer 还有 MMAP Buffer),但是大小受限(2 GB,原因因为 ??Buffer classes limited by 32-bit addressing??),并且有很多问题遗留了很多年未能解决,例如:??MappedByteBuffer.release()/close() to release system resources??,??Please make DirectByteBuffer performance enhancements??, ??Add absolute bulk put and get methods??
- Unsafe API:性能很高,可以被 JIT 优化,但是没有限制内存访问,哪一块内存都能访问,如果访问到一块已经释放的内存,就会导致 JVM 崩溃。
- JNI 调用:性能较差,因为无法被 JIT 优化(例如方法内联)
Java 16 – JDK Flight RecorderJFR 是我最喜欢的 Java 特性功能,我针对 JFR 写了很多篇文章,使用 JFR 定位过很多性能瓶颈以及线上问题,请参考以下系列或者文章:
- ??JFR 全解系列??
- ??JFR导致的雪崩问题定位与解决??
- ??JFR 定位因为 SSL 导致 CPU Load 飚高的问题??
- ??一次鞭辟入里的 Log4j2 日志输出阻塞问题的定位??
- ??spring-data-redis 上百万的 QPS 压力太大连接失败,我 TM 人傻了??
Java 16 – jpackage相关 JEP:
- ??已废弃 JEP 311: Java Packager API & CLI??
- ??JEP 343: Packaging Tool (Incubator)??
- ??JEP 392: Packaging Tool??
- Linux: deb and rpm
- macOS: pkg and dmg
- Windows: msi and exe
Java 16 - Performance性能相关的更新有很多
Hotspot 实现 Elastic Metaspace相关 JEP: ??Elastic Metaspace??
原来的元空间实现中,每个类加载器占用一个单独的元空间抽象,当类加载器被回收后,这块内存被释放但是不会退还给系统而是继续给其他类加载器复用,元空间的系统内存占用只会一直增大不会缩小,也就是不会将内存退还给系统。现在优化了这一点,可以动态伸缩元空间。这块的详细源码分析,我会在之后出一期类似于 ??全网最硬核 TLAB 解析?? 的文章解析这块。
G1 和 Parallel GC 优化如果想详细了解这块的优化,可以参考这篇文章:??JDK 16 G1/Parallel GC changes??
ZGC 优化相关 JEP:
- ??JEP 376: ZGC: Concurrent Thread-Stack Processing??
Shenandoah GC 优化
- ??Shenandoah: should not block pacing reporters??
- ??Shenandoah: reconsider free budget slice for marking??
- ??Shenandoah: pacer should wait on lock instead of exponential backoff??
- ??Shenandoah: support manageable SoftMaxHeapSize option??
- ??Shenandoah: Concurrent weak reference processing??
- ??Shenandoah: "adaptive" heuristic is prone to missing load spikes??
Java 16 – Deprecations/LimitationsPrimitive Wrapper Warnings相关 JEP:
- ??JEP 390: Warnings for Value-Based Classes??
目前,原始类型的封装类型类(例如 Integer )的构造器标记为了过期,并且会在将来的版本被移除,用他们里面的静态方法 ?
?valueOf()?
?
代替。我单独写了一篇文章来分析这个,参考:??JEP解读与尝鲜系列4 - Java 16 中对于 Project Valhalla 的铺垫??
Strong Encapsulation By Default相关 JEP:
- ??JEP 396: Strongly Encapsulate JDK Internals by Default??
- ??JEP 403: Strongly Encapsulate JDK Internals??:Java 17 发布的,已经完全去掉了
?
?--illegal-access?
?,如果使用,会有 ??OpenJDK 64-Bit Server VM warning: Ignoring option --illegal-access=warn; support was removed in 17.0?
? 的提示。
?--illegal-access?
?
的特性进行了修改。Java 16 之前默认是 permit,遇到访问没有开放的包会在第一次有提示,但是还是可以正常运行:WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by j9ms.internal.Nimbus
(file:...) to constructor NimbusLookAndFeel()
WARNING: Please consider reporting this
to the maintainers of j9ms.internal.Nimbus
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
Java 16 则是 deny。即默认禁止非法包访问,用户可以通过启动参数 ?
?--illegal-access=permit?
?
修改。Java 17 则是移除了这个参数,加上这个启动参数也无效了,会有提示并且反射访问内部未暴露的包会报错,例如:var dc = ClassLoader.class.getDeclaredMethod("defineClass",
String.class,
byte[].class,
int.class,
int.class);
dc.setAccessible(true);
使用启动参数?
?--illegal-access=warn?
?
运行:OpenJDK 64-Bit Server VM warning: Ignoring option --illegal-access=warn; support was removed in 17.0
Exception in thread "main" java.lang.reflect.InaccessibleObjectException: Unable to make protected final java.lang.Class java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int) throws java.lang.ClassFormatError accessible: module java.base does not "opens java.lang" to unnamed module @378bf509
at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:354)
at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:297)
at java.base/java.lang.reflect.Method.checkCanSetAccessible(Method.java:199)
at java.base/java.lang.reflect.Method.setAccessible(Method.java:193)
但是,通过启动参数 ?
?--add-opens java.base/java.lang=ALL-UNNAMED?
?
还是可以打破封包控制.推荐阅读
- 实验使用apache构建虚拟主机
- 关于如何构建 Go 代码的思考
- 从画廊短代码中排除the_post_thumbnail
- 动态将图像添加到WordPress中的滑块
- 显示所有注册用户(不仅是作者)的简单公共用户配置文件
- 在WordPress页面上显示特定帖子
- 在表格WordPress中显示自定义帖子类型
- after_setup_theme和init动作钩子之间的区别()
- WordPress上不同页面的不同侧边栏