共计 11035 个字符,预计需要花费 28 分钟才能阅读完成。
本文转载自:https://mp.weixin.qq.com/s/fV…
Java 诞生距今已有 25 年,但它依然长期占据着“天下第一”编程语言的宝座。只是其统治位置并非坚不可摧,反倒能够说是危机四伏。云原生时代,Java 技术体系的许多前提假如都受到了挑战,目前曾经有可预感的、足以威逼波动其根基的潜在可能性正在酝酿。同时,像 Golang、Rust 这样的新生语言,以及 C、C++、C#、Python 等老对手也都对 Java 的市场份额虎视眈眈。面对危机,Java 正在尝试哪些改革?将来,Java 是会持续向前、再攀顶峰,还是由盛转衰?在明天由极客邦科技举办的 QCon 寰球软件开发大会 2020(深圳站)上,远光软件研究院院长、《深刻了解 Java 虚拟机》系列书籍作者周志明发表了主题演讲《云原生时代的 Java》,以下内容为演讲整顿。
明天,25 岁的 Java 依然是最具备统治力的编程语言,长期占据编程语言排行榜的首位,领有一千二百万的宏大开发者群体,全世界有四百五十亿部物理设施应用着 Java 技术,同时,在云端数据中心的虚拟化环境里,还运行着超过两百五十亿个 Java 虚拟机的过程实例(数据来自 Oracle 的 WebCast)。
以上这些数据是 Java 过来 25 年巨大成就的勋绩佐证,更是 Java 技术体系维持本人“天下第一”编程语言的松软壁垒。Java 与其余语言竞争,底气从来不在于语法、类库有如许先进好用,而是来自它宏大的用户群和极其成熟的软件生态,这在朝夕之间难以撼动。然而,这个当初看起来依然坚不可摧的 Java 帝国,其统治位置的巩固水平不仅没有居安思危,反而说是危机四伏也不为过。目前曾经有了可预感的、足以威逼波动其根基的潜在可能性正在酝酿,并随云原生时代而来临。
Java 的危机
Java 与云原生的矛盾,来源于 Java 诞生之初,植入到它基因之中的一些根本的前提假如曾经逐步开始被波动,甚至曾经不再成立。
我举个例子,每一位 Java 的使用者都据说过“一次编写,到处运行”(Write Once, Run Anywhere)这句口号。20 多年前,Java 成熟之前,开发者如果心愿程序在 Linux、Solaris、Windows 等不同平台,在 x86、AMD64、SPARC、MIPS、ARM 等不同指令集架构上都能失常运行,就必须针对每种组合,编译出对应的二进制发行包,或者索性间接散发源代码,由使用者在本人的平台上编译。
面对这个问题,Java 通过语言层虚拟化的形式,令每一个 Java 利用都主动获得平台无关(Platform Independent)、架构中立(Architecture Neutral)的先天劣势,让同一套程序格局得以在不同指令集架构、不同操作系统环境下都能运行且失去统一的后果,不仅不便了程序的散发,还防止了各种平台下内存模型、线程模型、字节序等底层细节差别对程序编写的烦扰。在当年,Java 的这种设计带有令人趋之若鹜的弱小吸引力,间接开启了托管语言(Managed Language,如 Java、.NET)的一段昌盛期。
面对雷同的问题,明天的云原生抉择以操作系统层虚拟化的形式,通过容器实现的不可变基础设施去解决。不可变基础设施这个概念呈现得比云原生要早,本来是指该如何防止因为运维人员对服务器运行环境所做的继续的变更而导致的意想不到的副作用。但在云原生时代,它的外延已不再局限于不便运维、程序降级和部署的伎俩,而是升华一种为向利用代码暗藏环境复杂性的伎俩,是分布式服务得以成为一种可普遍推广的普适架构格调的必要前提。
将程序连同它的运行环境一起封装到稳固的镜像里,现已是一种支流的应用程序散发形式。Docker 同样提出过“一次构建,到处运行”(Build Once, Run Anywhere)的口号,只管它只能提供环境兼容性和有局限的平台无关性(指零碎内核性能以上的 ABI 兼容),且齐全不可能撑持架构中立性,所以将“一次构建,到处运行”与“一次编写,到处运行”对抗起来并不谨严失当,然而无可否认,明天 Java 技术“一次编译,到处运行”的劣势,曾经被容器大幅度地减弱,不再是大多数服务端开发者技术选型的次要思考因素了。
如果仅仅是劣势的减弱,并不足以成为 Java 的间接威逼,充其量只是一个潜在的不利因素,但更加火烧眉毛的危险来自于那些与技术潮流间接抵触的假如。譬如,Java 总体上是面向大规模、长时间的服务端利用而设计的,严 (luō) 谨(suō)的语法利于束缚所有人写出较统一的代码;动态类型动静链接的语言构造,利于多人合作开发,让软件涉及更大规模;即时编译器、性能制导优化、垃圾收集子系统等 Java 最具代表性的技术特色,都是为了便于长时间运行的程序能享受到硬件规模倒退的红利。
另一方面,在微服务的背景下,提倡服务围绕业务能力而非技术来构建利用,不再谋求实现上的统一,一个零碎由不同语言,不同技术框架所实现的服务来组成是齐全正当的;服务化拆分后,很可能单个微服务不再须要再面对数十、数百 GB 乃至 TB 的内存;有了高可用的服务集群,也毋庸谋求单个服务要 7×24 小时不可间断地运行,它们随时能够中断和更新。
同时,微服务又对利用的容器化亲和性,譬如镜像体积、内存耗费、启动速度,以及达到最高性能的工夫等方面提出了新的要求。这两年的网红概念 Serverless 也进一步减少这些因素的思考权重,而这些却正好都是 Java 的弱项:哪怕再小的 Java 程序也要带着残缺的虚拟机和规范类库,使得镜像拉取和容器创立效率升高,进而使整个容器生命周期拉长。基于 Java 虚拟机的执行机制,使得任何 Java 的程序都会有固定的根底内存开销,以及固定的启动工夫,而且 Java 生态中宽泛采纳的依赖注入进一步将启动工夫拉长,使得容器的冷启动工夫很难缩短。
软件工业中曾经呈现过不止一起因 Java 这些弱点而导致失败的案例,如 JRuby 编写的 Logstash,本来是同时承当部署在节点上的收集端(Shipper)和专门转换解决的服务端(Master)的职责,起初因为资源占用的起因,被 Elstaic.co 用 Golang 的 Filebeat 代替了 Shipper 局部的职能;又如 Scala 语言编写的边车代理 Linkerd,作为服务网格概念的提出者,却最终被 Envoy 所取代,其次要弱点之一也是因为 Java 虚拟机的资源耗费所带来的劣势。
尽管在云原生时代仍然有很多适宜 Java 施展的畛域,然而具备弹性与韧性、随时能够中断重启的微型服务确实曾经造成了一股潮流,在逐渐鲸吞大型零碎的领地。正是因为潮流趋势的扭转,新一代的语言与技术尤其器重轻量化和疾速响应能力,大多又从新回归到了原生语言(Native Language,如 Golang、Rust)之上。
Java 的改革
面对挑战,Java 的开发者和社区都没有退缩,它们在各自的畛域给出了很多优良的解决方案,涌现了如 Quarkus、Micronaut、Helidon 等一大批以晋升 Java 在云原生环境下的适应性为卖点的框架。
不过,明天咱们的主题将聚焦在由 Java 官网自身所推动的我的项目上。在围绕 Java 25 周年的研究和布道流动中,官网的设定是以“面向未来的改革”(Innovating for the Future)为基调,你有可能在此之前曾经据说过其中某个(某些)我的项目的名字和改良点,但 这里咱们不仅关怀这些我的项目改良的是什么,还更关怀它们背地的动机与艰难、带来的收益,以及要付出的代价。
Innovating for the Future
Project Leyden
对于原生语言的挑战,最无力最彻底的出击伎俩无疑是将字节码间接编译成能够脱离 Java 虚拟机的原生代码。如果真的可能生成脱离 Java 虚拟机运行的原生程序,将意味着启动工夫长的问题可能彻底解决,因为此时曾经不存在初始化虚拟机和类加载的过程;也意味着程序马上就能达到最佳的性能,因为此时曾经不存在即时编译器运行时编译,所有代码都是在编译期编译和优化好的(如下图所示);没有了 Java 虚拟机、即时编译器这些额定的部件,也就意味着可能省去它们本来耗费的那局部内存资源与镜像体积。
Java Performance Matrices(图片起源)
但同时,这也是风险系数最高、实现难度最大的计划。
Java 并非没有尝试走过这条路,从 Java 2 之前的 GCJ(GNU Compiler for Java),到起初的 Excelsior JET,再到 2018 年 Oracle Labs 启动的 GraalVM 中的 SubstrateVM 模块,最初到 2020 年中期刚建设的 Leyden 我的项目,都在朝着提前编译(Ahead-of-Time Compilation,AOT)生成原生程序这个指标迈进。
Java 反对提前编译最大的艰难在于它是一门动静链接的语言,它假如程序的代码空间是凋谢的(Open World),容许在程序的任何时候通过类加载器去加载新的类,作为程序的一部分运行。要进行提前编译,就必须放弃这部分动态性,假如程序的代码空间是关闭的(Closed World),所有要运行的代码都必须在编译期全副可知。这一点不仅仅影响到了类加载器的失常运作,除了无奈再动静加载外,反射(通过反射能够调用在编译期不可知的办法)、动静代理、字节码生成库(如 CGLib)等所有会运行时产生新代码的性能都不再可用,如果将这些根底能力间接抽离掉,Helloworld 还是能跑起来,但 Spring 必定跑不起来,Hibernate 也跑不起来,大部分的生产力工具都跑不起来,整个 Java 生态中绝大多数上层建筑都会轰然崩塌。
要取得有实用价值的提前编译能力,只有依附提前编译器、组件类库和开发者三方一起协同才可能办到。因为 Leyden 刚刚开始,简直没有公开的材料,所以上面我是以 SubstrateVM 为指标对象进行的介绍:
- 有一些性能,像反射这样的根底个性是不可能斗争的,折衷的解决办法是由用户在编译期,以配置文件或者编译器参数的模式,明确告知编译器程序代码中有哪些办法是只通过反射来拜访的,编译器将办法的增加到动态编译的领域之中。同理,所有应用到动静代理的中央,也必须在当时列明,在编译期就将动静代理的字节码全副生成进去。其余所有无奈通过程序指针剖析(Points-To Analysis)失去的信息,譬如程序中用到的资源、配置文件等等,也必须照此解决。
- 另一些性能,如动静生成字节码也非常罕用,但用户本人往往无奈得悉那些动静字节码的具体信息,就只能由用到 CGLib、javassist 等库的程序去斗争放弃。在 Java 世界中兴许最典型的场景就是 Spring 用 CGLib 来进行类加强,默认状况下,每一个 Spring 治理的 Bean 都要用到 CGLib。从 Spring Framework 5.2 开始减少了 @proxyBeanMethods 注解来排除对 CGLib 的依赖,仅应用规范的动静代理去加强类。
2019 年起,Pivotal 的 Spring 团队与 Oracle Labs 的 GraalVM 团队独特孵化了 Spring GraalVM Native 我的项目,这个目前仍处于 Experimental / Alpha 状态的我的项目,可能让程序先以传统形式运行(启动)一次,自动化地找出程序中的反射、动静代理的代码,代替用户向编译器提供绝大部分所需的信息,并能将容许启动时初始化的 Bean 在编译期就实现初始化,间接绕过 Spring 程序启动最慢的阶段。这样从启动到程序能够提供服务,耗时竟可能低于 0.1 秒。
Spring Boot Startup Time(数据起源)
以原生形式运行后,缩短启动工夫的成果空谷传声,个别会有数十倍甚至更高的改善,程序容量和内存耗费也有肯定水平的降落。不过至多目前而言,程序的运行效率还是要弱于传统基于 Java 虚拟机的形式,尽管即时编译器有编译工夫的压力,但因为能够进行基于假如的激进优化和运行时性能度量的制导优化,使得即时编译器的成果仍要优于提前编译器,这方面须要 GraalVM 编译器团队的进一步致力,也须要从语言改良上动手,让 Java 变得更适宜被编译器优化。
Project Valhalla
Java 语言上可感知的语法变动,少数来自于 Amber 我的项目,它的我的项目指标是继续优化语言生产力,近期(JDK 15、16)会有很多来自这个我的项目的个性,如 Records、Sealed Class、Pattern Matching、Raw String Literals 等实装到生产环境。
然而语法不仅与编码效率相干,与运行效率也有很大关系。“程序 = 代码 + 数据”这个提法至多在掂量运行效率上是适合的,无论是托管语言还是原生语言,最终产物都是处理器执行的指令流和内存存储的数据结构。Java、.NET、C、C++、Golang、Rust 等各种语言谁更快,取决于特定场景下,编译器生成指令流的优化成果,以及数据在内存中的构造布局。
Java 即时编译器的优化成果拔群,然而因为 Java“所有皆为对象”的前提假如,导致在解决一系列不同类型的小对象时,内存拜访性能十分拉垮,这点是 Java 在游戏、图形处理等畛域始终难有建树的重要制约因素,也是 Java 建设 Valhalla 我的项目的指标初衷。
这里举个例子来阐明此问题,如果我想形容空间外面若干条线段的汇合,在 Java 中定义的代码会是这样的:
public record Point(float x, float y, float z) {}
public record Line(Point start, Point end) {}
Line[] lines;
面向对象的内存布局中,对象标识符(Object Identity)存在的目标是为了容许在不裸露对象构造的前提下,仍然能够援用其属性与行为,这是面向对象编程中多态性的根底。在 Java 中堆内存调配和回收、空值判断、援用比拟、同步锁等一系列性能都会波及到对象标识符,内存拜访也是依附对象标识符来进行链式解决的,譬如下面代码中的“若干条线段的汇合”,在堆内存中将形成如下图的援用关系:
Object Identity / Memory Layout
计算机硬件通过 25 年的倒退,内存与处理器尽管都在提高,然而内存提早与处理器执行性能之间的冯诺依曼瓶颈(Von Neumann Bottleneck)不仅没有缩减,反而还在继续加大,“RAM Is the New Disk”曾经从讥嘲梗逐步成为了事实。
一次内存拜访(将主内存数据调入处理器 Cache)大概须要消耗数百个时钟周期,而大部分简略指令的执行只须要一个时钟周期而已。因而,在程序执行性能这个问题上,如果编译器能缩小一次内存拜访,可能比优化掉几十、几百条其余指令都来得更有成果。
额定常识:冯诺依曼瓶颈
不同处理器(古代处理器都集成了内存管理器,以前是在北桥芯片中)的内存提早大略是 40-80 纳秒(ns,十亿分之一秒),而依据不同的时钟频率,一个时钟周期大略在 0.2-0.4 纳秒之间,如此短暂的工夫内,即便真空中流传的光,也仅仅可能前进 10 厘米左右。
数据存储与处理器执行的速度矛盾是冯诺依曼架构的次要局限性之一,1977 年的图灵奖得主 John Backus 提出了“冯诺依曼瓶颈”这个概念,专门用来形容这种局限性。
编译器确实在致力缩小内存拜访,从 JDK 6 起,HotSpot 的即时编译器就尝试通过逃逸剖析来做标量替换(Scalar Replacement)和栈上调配(Stack Allocations)优化,基本原理是如果能通过剖析,得悉一个对象不会传递到办法之外,那就不须要实在地在对中创立残缺的对象布局,齐全能够绕过对象标识符,将它拆散为根本的原生数据类型来创立,甚至是间接在栈内存中调配空间(HotSpot 并没有这样做),办法执行结束后随着栈帧一起销毁掉。
不过,逃逸剖析是一种过程间优化(Interprocedural Optimization),十分耗时,也很难解决那些实践有可能但理论不存在的状况。雷同的问题在 C、C++ 中却并不存在,下面场景中,程序员只有将 Point 和 Line 都定义为 struct 即可,C# 中也有 struct,是依附.NET 的值类型(Value Type)来实现的。Valhalla 我的项目的外围改良就是提供相似的值类型反对,提供一个新的关键字(inline),让用户能够在不须要向办法内部裸露对象、不须要多态性反对、不须要将对象用作同步锁的场合中,将类标识为值类型,此时编译器就可能绕过对象标识符,以平坦的、紧凑的形式去为对象分配内存。
有了值类型的反对后,当初 Java 泛型中令人诟病的不反对原数据类型(Primitive Type)、频繁装箱问题也就随之迎刃而解,当初 Java 的包装类,天经地义地会以代表原生类型的值类型来从新定义,这样 Java 泛型的性能会失去显著的晋升,因为此时 Integer 与 int 的拜访,在机器层面看齐全能够达到统一的效率。
Project Loom
Java 语言形象进去暗藏了各种操作系统线程差异性的对立线程接口,这已经是它区别于其余编程语言(C/C++ 示意有被触犯到)的一大劣势,不过,对立的线程模型不见得永远都是正确的。
Java 目前支流的线程模型是间接映射到操作系统内核上的 1:1 模型,这对于计算密集型工作这很适合,既不必本人去做调度,也利于一条线程跑满整个处理器外围。但对于 I / O 密集型工作,譬如拜访磁盘、拜访数据库占次要工夫的工作,这种模型就显得老本昂扬,次要在于内存耗费和上下文切换上:64 位 Linux 上 HotSpot 的线程栈容量默认是 1MB,线程的内核元数据(Kernel Metadata)还要额定耗费 2 -16KB 内存,所以单个虚拟机的最大线程数量个别只会设置到 200 至 400 条,当程序员把数以百万计的申请往线程池外面灌时,零碎即使能解决得过去,其中的切换损耗也相当可观。
Loom 我的项目的指标是让 Java 反对额定的 N:M 线程模型,请留神是“额定反对”,而不是像当年从绿色线程过渡到内核线程那样的间接替换,也不是像 Solaris 平台的 HotSpot 虚拟机那样通过参数让用户二选其一。
Loom 我的项目新减少一种“虚构线程”(Virtual Thread,以前以 Fiber 为名进行宣传过,但因为要频繁解释啥是 Fiber 所以当初放弃了),实质上它是一种有栈协程(Stackful Coroutine),多条虚构线程能够映射到同一条物理线程之中,在用户空间中自行调度,每条虚构线程的栈容量也可由用户自行决定。
Virtual Thread
同时,Loom 我的项目的另一个指标是要尽最大可能放弃原有对立线程模型的交互方式,艰深地说就是原有的 Thread、J.U.C、NIO、Executor、Future、ForkJoinPool 等这些多线程工具都应该能以同样的形式反对新的虚构线程,原来多线程中你了解的概念、编码习惯大多数都可能持续沿用。
为此,虚构线程将会与物理线程一样应用 java.lang.Thread 来进行形象,只是在创立线程时用到的参数或者办法稍有不同(譬如给 Thread 减少一个 Thread.VIRTUAL_THREAD 参数,或者减少一个 startVirtualThread()办法)。这样现有的多线程代码迁徙到虚构线程中的老本就会变得很低,而代价就是 Loom 的团队必须做更多的工作以保障虚构线程在大部分波及到多线程的规范 API 中都可能兼容,甚至在调试器上虚构线程与物理线程看起来都会有统一的外观。但很难全副都反对,譬如调用 JNI 的本地栈帧就很难放到虚构线程上,所以一旦遇到本地办法,虚构线程就会被绑定(Pinned)到一条物理线程上。
Loom 的另一个重点改良是反对结构化并发(Structured Concurrency),这是 2016 年才提出的新的并发编程概念,但很快就被诸多编程语言所吸纳。它是指程序的并发行为会与代码的构造对齐,譬如以下代码所示,依照传统的编程观点,如果没有额定的解决(譬如无中生有地弄一个 await 关键字),那在 task1 和 task2 提交之后,程序应该持续向下执行:
ThreadFactory factory = Thread.builder().virtual().factory();
try (var executor = Executors.newThreadExecutor(factory)) {executor.submit(task1);
executor.submit(task2);
} // blocks and waits
然而在结构化并发的反对下,只有两个并行启动的工作线程都完结之后,程序才会持续向下执行,很好地以同步的编码格调,来解决异步的执行问题。事实上,“Code like sync,Work like async”正是 Loom 简化并发编程的核心理念。
Project Portola
Portola 我的项目的指标是将 OpenJDK 向 Alpine Linux 移植。Alpine Linux 是许多 Docker 容器首选的根底镜像,因为它只有 5 MB 大小,比起其余 Cent OS、Debain 等动辄一百多 MB 的发行版来说,更适宜用于容器环境。不过 Alpine Linux 为了尽量瘦身,默认是用 musl 作为 C 规范库的,而非传统的 glibc(GNU C library),因而要以 Alpine Linux 为根底制作 OpenJDK 镜像,必须先装置 glibc,此时根底镜像大概有 12 MB。Portola 打算将 OpenJDK 的上游代码移植到 musl,并通过兼容性测试。应用 Portola 制作的规范 Java SE 13 镜像仅有 41 MB,不仅远低于 Cent OS 的 OpenJDK(大概 396 MB),也要比官网的 slim 版(约 200 MB)要小得多。
$ sudo docker build .
Sending build context to Docker daemon 2.56kB
Step 1/8 : FROM alpine:latest as build
latest: Pulling from library/alpine
bdf0201b3a05: Pull complete
Digest: sha256:28ef97b8686a0b5399129e9b763d5b7e5ff03576aa5580d6f4182a49c5fe1913
Status: Downloaded newer image for alpine:latest
---> cdf98d1859c1
Step 2/8 : ADD https://download.java.net/java/early_access/alpine/16/binaries/openjdk-13-ea+16_linux-x64-musl_bin.tar.gz /opt/jdk/
Downloading [==================================================>] 195.2MB/195.2MB
---> Using cache
---> b1a444e9dde9
Step 3/7 : RUN tar -xzvf /opt/jdk/openjdk-13-ea+16_linux-x64-musl_bin.tar.gz -C /opt/jdk/
---> Using cache
---> ce2721c75ea0
Step 4/7 : RUN ["/opt/jdk/jdk-13/bin/jlink", "--compress=2", "--module-path", "/opt/jdk/jdk-13/jmods/", "--add-modules", "java.base", "--output", "/jlinked"]
---> Using cache
---> d7b2793ed509
Step 5/7 : FROM alpine:latest
---> cdf98d1859c1
Step 6/7 : COPY --from=build /jlinked /opt/jdk/
---> Using cache
---> 993fb106f2c2
Step 7/7 : CMD ["/opt/jdk/bin/java", "--version"] - to check JDK version
---> Running in 8e1658f5f84d
Removing intermediate container 8e1658f5f84d
---> 350dd3a72a7d
Successfully built 350dd3a72a7d
$ sudo docker tag 350dd3a72a7d jdk-13-musl/jdk-version:v1
$ sudo docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
jdk-13-musl/jdk-version v1 350dd3a72a7d About a minute ago 41.7MB
alpine latest cdf98d1859c1 2 weeks ago 5.53M
Java 的将来
云原生时代,Java 技术体系的许多前提假如都受到了挑战,“一次编译,到处运行”、“面向长时间大规模程序而设计”、“从凋谢的代码空间中动静加载”、“所有皆为对象”、“对立线程模型”,等等。技术倒退迭代不会停歇,没有必要保持什么“永恒的真谛”,旧的准则被突破,只有正当,便是翻新。
Java 语言意识到了挑战,也意识到了要面向未来而改革。文中提到的这些我的项目,Amber 和 Portola 曾经明确会在 2021 年 3 月的 Java 16 中公布,至多也会达到 Feature Preview 的水平:
- JEP 394:Pattern Matching for instanceof
- JEP 395:Records
- JEP 397:Sealed Classes
- JEP 386:Alpine Linux Port
至于更受关注,同时也是难度更高的 Valhalla 和 Loom 我的项目,目前依然没有明确的版本打算信息,只管它们曾经开发了数年工夫,十分心愿可能赶在 Java 17 这个 LTS 版本中面世,但前路还是困难重重。
至于难度最高、创立工夫最晚的 Leyden 我的项目,目前还齐全处于个性探讨阶段,连个胚胎都算不上。对于 Java 的原生编译,咱们中短期内只可能寄希望于 Oracle 的 GraalVM。
将来一段时间,是 Java 重要的转型 窗口期 ,如果作为下一个 LTS 版的 Java 17,可能胜利集 Amber、Portola、Valhalla、Loom 和 Panama(用于内部函数接口拜访,本文没有提到)的新能力、新个性于一身,GraalVM 也能给予足够强力反对的话,那 Java 17 LTS 大概率会是一个里程碑式的版本,率领着整个 Java 生态从大规模服务端利用,向新的云原生时代软件系统转型。 可能成为比肩当年从面向嵌入式设施与浏览器 Web Applets 的 Java 1,到确立古代 Java 语言方向(Java SE/EE/ME 和 JavaCard)雏形的 Java 2 转型那样的里程碑。
然而,如果 Java 不能减速本人的倒退步调,那由弱小生态所构建的护城河终究会耗费殆尽,被 Golang、Rust 这样的新生语言,以及 C、C++、C#、Python 等老对手鲸吞掉很大一部分市场份额,以至被迫从“天下第一”编程语言的宝座中退位。
Java 的将来是持续向前,再攀顶峰,还是由盛转衰,矛头挫缩,你我刮目相待。