一些杂想:Java老矣,尚能饭否?( 二 )


搜了一下 , 似乎国外网友也有一样的疑惑[7] 。不过 , 我认为让程序员可以定义应用程序的模块是什么 , 它们将如何被其他模块使用 , 以及它们依赖于哪些其他模块 , 这些事情还是有必要做的 。
当然Java9除了模块化之外 , 还有一些其他特性也值得关注:
关于 JVMCI 多介绍一些 。相比用 C 或 C++ 编写的现有编译器(说的就是你 , C2) , 用Java写编译器更容易维护和改进 。JVMCI的API 提供了访问 JVM 结构、安装编译代码和插入 JVM 编译系统的机制 , 后面讲到的Graal正是基于JVMCI 。
编译器与JVM的交互可以分为如下三个方面 。
响应编译请求;
获取编译所需的元数据(如类、方法、字段)和反映程序执行状态的;
将生成的二进制码部署至代码缓存(code cache)里 。
即时编译器通过这三个功能组成了一个响应编译请求、获取编译所需的数据 , 完成编译并部署的完整编译周期 。
传统情况下 , 即时编译器是与Java虚拟机紧耦合的 。也就是说 , 对即时编译器的更改需要重新编译整个Java虚拟机 。这对于开发相对活跃的Graal来说显然是不可接受的 。
为了让Java虚拟机与Graal解耦合 , 引入 JVMCI 将上述三个功能抽象成一个Java层面的接口 。这样一来 , 在Graal所依赖的JVMCI版本不变的情况下 , 我们仅需要替换Graal编译器相关的jar包(Java 9以后的jmod文件) , 便可完成对Graal的升级 。
其实JVMCI接口就长这样:
{
/**
*a. Thisthetocode and
*it in the code cache if theis .
*/
sult ( );
Java 10:小升级
的性能提升点并不多(6个月一次的版本节奏难免要挤挤牙膏):
Java 11:ZGC闪亮登场
Java 11是LTS版本 , 也可能是企业选择从万年Java 8升级到的第一个版本 。最大的改动是引入了新一代的垃圾回收器-ZGC[14] 。ZGC的首要目标是实现低停顿(暂停时间不超过10ms)、高并发的垃圾回收 , ZGC回收器与G1一样基于内存布局 , 使用了读屏障、染色指针和内存多重映射等技术来实现可并发的标记-整理 。
但ZGC并不是完美的 , 逃不过内存占用()、吞吐量()和延迟()的三元悖论 。与G1相比 , 它的强项是低延迟 , 缺点是内存占用更高 , 吞吐量比G1稍低(不过这强依赖于测试用例 , 我也看到一些显示ZGC的吞吐量高于G1) , 另外还有一些其他问题[15]也值得注意 。总的来说 , 如果考虑使用ZGC替代CMS , 建议是使用Java 15之后的版本 。
数据来源: the JDK’s New[16]
另一个容易被人忽略的特性是Java 11中引入了一个号称无操作的垃圾回收器[17] , 即不会做GC的垃圾回收器 。这个很有意思 , 但确实对于一些不需要长时间运行、小规模的程序来说 , 会更关注启动时间、内存占用等指标 , 很典型的就比如函数 。只要JVM能正确分配内存 , 然后在堆耗尽之前退出 , 那显然运行负载极小、没有任何回收行为的便是很恰当的选择 。
Java 12:和内存返还
Java 12中引入了一个新的实验性的垃圾回收器-[18] , 与ZGC一样是以低停顿为目标(注意这里说的是 , 因为非亲生的缘故 , 中并没有包含) 。
另一个是G1上的改动 , 能够自动将未使用的堆内存返还给操作系统[19] 。我们经常看到 , Java程序占用的内存比实际应用本身运行产生的对象占用要多 , 即使在应用本身没有流量时也是如此 , 原因是多方面的(这里不谈JVM、类的元数据、编译后的本地代码等等对内存的额外占用):