Inside Java Newscast #1 深度解读

会挽雕弓如满月,西北望,射天狼。这篇文章主要讲述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
Java 16 – Records相关 JEP 地址:
  • ??JEP 359: Records(Preview)??
  • ??JEP 384: Records (Second Preview)??
  • ??JEP 395: Records??
Records 这个特性我仔细研究过实现:参考我写的另一篇文章??Java Record 的一些思考 - 默认方法使用以及基于预编译生成相关字节码的底层实??
简单说来其实就是(编译后查看下字节码就能看出来),在编译后,根据 Record 源码插入相关域与方法的字节码,包括:
  1. 自动生成的 private final field
  2. 自动生成的全属性构造器
  3. 自动生成的 public getter 方法
  4. 自动生成的 hashCode(),equals(),toString() 方法:
  5. 从字节码可以看出,这三个方法的底层实现是 invokeDynamic 另一个方法
  6. 调用的是  ??ObjectMethods.java??  这个类中的  ??bootstrap??  方法
这个还让我闹了个笑话,我以为这个是 Project Valhala 的 Inline Object 已经实现了(参考我的这个系列:  ??JEP 尝鲜系列??),还去 StackOverflow 问,这个 Record 为啥能有 wait() 方法,并且可以进行 synchronized 同步(因为如果是 Project Valhala 的 Inline Object 的话是没有普通类的对象头的,没法用普通类对象的方法实现同步),结果。。。。。最后还是  Goetz 大佬一眼就看出我是误会了:

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??
类型模式匹配一直是一个呼声很高的特性,如果和下一小节的 Sealed Class 特性 以及 Patterns in switch 结合起来使用会有更好的效果,这个我们在下一节会更详细的说明.
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> ??
对于 Stream 增加了 toList 直接转换成 List,由于不涉及 collect 里面的截断操作,所以比 collect 占用的内存更小,需要的操作更少并且更快。之前转换成 List,需要  ??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??
Java 16 – Unix Domain Sockets相关 JEP:
  • ??JEP 380: Unix-Domain Socket Channels??
Unix domain sockets 以本地文件的形式命名,让我们可以像访问本地文件一样访问本地网络连接。这个用于在同一个机器部署的不同进程之间的通信,下面是一个简单的 BIO 的例子:
//创建 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。
Vector API相关 JEP:
  • ??JEP 338: Vector API (Incubator)??
  • ??JEP 414: Vector API (Second Incubator)??:Java 17 中的
  • ??JEP 417: Vector API (Third Incubator)??:Java 18 中的
其中最主要的应用就是使用了 CPU 的 SIMD(单指令多数据)处理,它提供了通过程序的多通道数据流,可能有 4 条通道或 8 条通道或任意数量的单个数据元素流经的通道。并且 CPU 一次在所有通道上并行组织操作,这可以极大增加 CPU 吞吐量。通过 Vector API,Java 团队正在努力让 Java 程序员使用 Java 代码直接访问它;过去,他们必须在汇编代码级别对向量数学进行编程,或者使用 C/C++ 与 Intrinsic 一起使用,然后通过 JNI 提供给 Java。
一个主要的优化点就是循环,过去的循环(标量循环),一次在一个元素上执行,那很慢。现在,您可以使用 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 中
通过这个 API,我们可以使用纯 Java 代码来调用系统的库,例如使用 Java 代码弹出一个 Windows 提示框: 
以上例子来自于  ??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 中
很多流行的高性能 Java 框架和中间件使用了堆外内存,但是目前 Java 中操作堆外内存的 API 不够完善:
  • ??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 优化(例如方法内联)
如果这些 API 开发完成,使用 Java 操作内存将更加容易理解和高效
Java 16 – JDK Flight RecorderJFR 是我最喜欢的 Java 特性功能,我针对 JFR 写了很多篇文章,使用 JFR 定位过很多性能瓶颈以及线上问题,请参考以下系列或者文章:
  • ??JFR 全解系列??
  • ??JFR导致的雪崩问题定位与解决??
  • ??JFR 定位因为 SSL 导致 CPU Load 飚高的问题??
  • ??一次鞭辟入里的 Log4j2 日志输出阻塞问题的定位??
  • ??spring-data-redis 上百万的 QPS 压力太大连接失败,我 TM 人傻了??
Java 16 中,针对 JFR, 在 Java 14 引入的 JFR Stream 的基础上,增加了通过 JMX 暴露的 JFR Stream。原来我们只能内部消费处理 JFR Event,现在可以通过 JMX 远程消费 JFR Event:??JDK-8253898: JFR: Remote Recording Stream??
Java 16 – jpackage相关 JEP:
  • ??已废弃 JEP 311: Java Packager API & CLI??
  • ??JEP 343: Packaging Tool (Incubator)??
  • ??JEP 392: Packaging Tool??
这个是将 Java 程序打包成可安装包的工具,目前支持的操作系统以及格式包括:
  • Linux: deb and rpm
  • macOS: pkg and dmg
  • Windows: msi and exe
可以参考这个文章试用下:??Building Self-Contained, Installable Java Applications with JEP 343: Packaging Tool??
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??
ZGC 本来已经基本将 GC 每个阶段都做成并发的了,GC 根扫描还是需要 STW。这个 JEP 优化了 GC 根扫描中的线程栈扫描,让这个扫描也可以 “半并行化” 了。这块我也会在日后进行详细的分析。
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 – Security关于安全性相关的优化,请参考:??JDK 16 Security Enhancements??
Java 16 – Deprecations/LimitationsPrimitive Wrapper Warnings相关 JEP:
  • ??JEP 390: Warnings for Value-Based Classes??
这是一个令人激动的更新,是为了我期待已久的 Project Valhala 做铺垫的(对,就是我之前把 Record 误会了的那个)。
目前,原始类型的封装类型类(例如 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??  的提示。
为了推进 Java 模块化,针对  ??--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??  还是可以打破封包控制.

    推荐阅读