zz 云原生时代,Java的危与机

https://icyfenix.cn/tricks/2020/java-crisis/qcon.html

另一方面,在微服务的背景下,提倡服务围绕业务能力而非技术来构建应用,不再追求实现上的一致,一个系统由不同语言,不同技术框架所实现的服务来组成是完全合理的;服务化拆分后,很可能单个微服务不再需要再面对数十、数百GB乃至TB的内存;有了高可用的服务集群,也无须追求单个服务要7×24小时不可间断地运行,它们随时可以中断和更新。同时,微服务又对应用的容器化亲和性,譬如镜像体积、内存消耗、启动速度,以及达到最高性能的时间等方面提出了新的要求,在这两年的网红概念Serverless也进一步增加这些因素的考虑权重,而这些却正好都是Java的弱项:哪怕再小的Java程序也要带着完整的虚拟机和标准类库,使得镜像拉取和容器创建效率降低,进而使整个容器生命周期拉长。基于Java虚拟机的执行机制,使得任何Java的程序都会有固定的基础内存开销,以及固定的启动时间,而且Java生态中广泛采用的依赖注入进一步将启动时间拉长,使得容器的冷启动时间很难缩短。

计算机硬件经过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的访问,在机器层面看完全可以达到一致的效率。

原文地址:https://www.cnblogs.com/inshua/p/14113905.html