老者Java,奋战一线

老者Java,奋战一线

1 语言优劣论

世上只有两种编程语言:一种被人骂,一种没人用。

Java已经诞生20多年了,依然是企业级开发中使用最广泛的语言,也是挨骂最多的语言。技术圈经常有“A语言比B语言更好”的争论,争论的核心有两点:语法和性能,比如“Java性能不如C#、Python语法比Java优雅”。

先谈谈语法的优劣。每个开发者都有自己偏爱的语法。你觉得很舒服的写法,就有人觉得别扭,这是个主观问题。比如Java 8 中的Lambda表达式,可以极大的优化集合操作的代码结构,但是不少人反感 () -> {} 这种符号,不愿意在项目中使用。再举个例子,Java语言定义变量的方式是数据类型前置、变量名后置,如: int maxSize = 100;而Go语言中变量名是前置的,如:var maxSize int = 100,写惯了Java的人肯定不适应变量名前置。习惯的力量太大了,许多人将自己的偏爱当成评判的标准。

再谈性能的优劣。编程语言的开发团队固然希望性能更好,但是性能优化要一步一步来。我们要用发展的眼光看待性能问题,早期的Java编译器和虚拟机性能确实堪忧,发展到如今已经改善了不少,以后也会继续提升。如果一款语言性能真的差到无法接受,绝不会用于企业开发中。每种语言的使用场景不同,C语言的性能固然高于Java或者C#,为什么没有人C开发Web项目?在需求快速迭代的项目里,开发效率更重要,性能够用就行,通俗的说就是人比机器贵。通常项目中遇到的大部分的性能瓶颈,降级需求或者优化设计方案就能解决,许多程序员的水平还没有达到可以指责语言性能的程度。

企业选择某款编程语言作为生产力工具,首先考虑的是人力招聘和培养的成本,其次才是语言特性和开发效率。这些年出现了许多新语言如Golang、Rust,也许概念更先进、性能更好,但是企业不会轻易在核心系统上采用新语言,以免出现不可预估的风险,保持系统的稳定才是最重要的。

2 Java的争议

1995 年 5 月 23 日,Java 正式发布。经过20多年的发展,Java孕育出了大量的开源组件和开发者。在它诞生的年代,相比其他语言有五个优势:
(1)简单易学:没有指针操作和手动内存分配,易学易用,程序员心智负担较小。
(2)面向对象:仅支持面向对象编程,单一的编程范式可以避免工程过度复杂。
(3)网络编程:提供了更简单的网络编程库,适合构建大型的网络分布式系统。
(4)反射机制:通过反射机制增强了语言的动态性,大部分开发框架都用到了这个特性。
(5)跨平台:通过JVM屏蔽了底层硬件的差异性,为上层应用提供统一的接口。应用程序被编译成与计算机结构无关的字节码,可以运行在不同的操作系统中。

事物总是有两面性,为了解决一个问题引入一个特性,也必然带来新的问题。我们应该如何辩证的看待Java广受诟病的几个问题呢?

  • 性能差

通常有GC语言不提供非常底层的内存操作方法,因此不可能达到无GC语言的性能,而且当虚拟机彻底回收垃圾时,应用程序会被强制停止,某些需要低延迟的场景绝不能接受这种状态。事实上,如果进行数值计算的基准测试,Java比 C++没有慢多少,企业开发中的Java项目速度慢的根本原因是框架滥用反射,加载反射代码要比普通程序慢几十倍,编译器没法优化反射代码。在资源紧张或者高性能场景下,C / C++仍然是首选,Java无法胜任。

  • 内存占用大

Java应用程序启动后包含JVM实例,一个“Hello World”都要消耗几十兆内存。每个Java的对象都要包含一个96 bits的对象头,一个32bits的integer就要占用内存96bits+32bits=128bits。向Go这种面向值的语言,每个值的存储消耗最低仅为2bits。企业级框架Spring大量采用HashMap缓存数据,也是极度消耗内存的。

  • GUI弱

Java桌面软件的性能低、开发体验也差。最新的GUI库JavaFX比Swing、AWT进步很多,但是远远不如Qt(C++)、WinForm / WPF (C#) 等框架。从商业角度考虑,多数企业软件并不需要跨平台,运行在Winows就足够了,少量的要兼容MacOS,Java跨平台特性在这种情况下反而是鸡肋。谷歌选择Java语言开发Android应用,看重的是庞大的Java生态,而不是语言特性。

  • 代码啰嗦

Java仅支持面向对象编程范式,书写起来极为啰嗦,比如main方法不能是一个独立的函数,必须放在没有实际意义的class中。好处是风格统一,坏处就是死板和啰嗦。采用Spring框架开发的Java项目,只要遵循基本的代码规范,每个人写出来的都差不多,可读行不会太差。与之相反,C++这种多范式语言,非结构化、结构化、面向对象、宏、模板等等应有尽有,灵活性极大,要精通也很难。维护一个大型的C++项目,就像在读一本百科全书。

3 云原生的考验

云计算是一种实施模式,是指通过互联网方式交付存储、服务器、应用等等。“云”是一种管理 IT 资源的方法,能够取代本地设备和私有数据中心。在云计算模式下,用户无需购买和维护大量的计算、存储和其他 IT 基础设施,直接访问云计算提供商的计算、网络和存储资源即可。云服务提供商能够保障物理设备的正常运转、资源的按需调配等等。

云原生是一种构建和运行应用程序的方法,是一套技术体系和方法论。云原生是Cloud+Native的组合词,Cloud表示应用程序位于云中,而不是传统的数据中心;Native表示应用程序从设计之初即考虑到云的环境,原生为云计算而设计,可以充分发挥云计算平台的弹性和分布式优势。云原生架构有4个重要的实施原则:
(1)DevOps:采用自动化工具将应用快速部署到生成环境,并且让开发、运维互相协作。
(2)持续交付:支持频繁持续的发布应用,快速反馈部署故障,有效降低发布风险。
(3)微服务:将庞大复杂的单体服务拆分为更小的微服务,每个微服服可以快速、独立的部署。
(4)容器化:将应用程序及依赖项打包成镜像,以容器化的方式快速分发。

在虚拟化技术没有广泛应用的阶段,部署应用程序是个繁琐的事情。为了让程序在Linux、Windows等平台以及x86、AMD64、SPARC、MIPS、ARM等指令集架构上都能正常运行,必须先将源码编译为对应的可执行文件,或者直接分发源代码,由使用者自行构建可执行文件。Java发布时,提出了一句动人的口号:一次编写,到处运行”(Write Once, Run Anywhere),解决了应用部署的痛点。在云原生架构下,Java以及JVM的特性面临了新的挑战。

在微服务的背景下,围绕业务能力而非技术来构建应用,允许由不同的语言构建应用程序。一个大型的微服务集群,往往有成千上万个容器在运行。为了更有效率的管理容器,对微服务有几个诉求:镜像体积小、内存消耗少、启动速度快,这些却都是Java的弱项。再小的Java程序也带着完整的虚拟机和标准类库,这样会降低拉取镜像和创建容器的效率;Java的程序都会有固定的基本内存开销和启动时间,开源框架Spring等广泛采用的依赖注入也使得容器的启动时间过长。最好解决方案是,将Java源码直接编译为二进制可执行文件,再将可执行文件打包为镜像。

GraalVM是Oracle实验室推出的基于Java开发的开源高性能多语言运行时平台,它既可以在传统的 OpenJDK 上运行,也可以通过 AOT(Ahead-Of-Time)编译成可执行文件单独运行。GraalVM将源代码打包成可执行代码,运行时不包含JRE,实现秒级别的启动,占用内存更小。

知名开源框架 Spring 的研发团队也发布了 Spring Native 项目,利用 GraalVM 将 Spring 应用生成可执行的原生镜像(Native Image)。这些原生镜像的启动速度极快、内存消耗也更少,但是比JVM的构建时间更长、运行时优化也不足。目前 Spring Navtive 仍然处于孵化阶段,国内公司用于生产环境的很少。

参考
https://www.cnblogs.com/JaxYoun/p/16483067.html
https://zhuanlan.zhihu.com/p/333926379
https://zhuanlan.zhihu.com/p/150190166

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注