第11代Java国王坐在宝座上,俯视着臣民。
经过历代国王的励精图治,他的Java帝国正处于巅峰状态。
一群大臣看到新王登基,马上上来拍马屁。
“从后端到手机端,从手机端到大数据,帝国疆域无边无际。” 线程大臣率先定了基调。
“Java是企业级应用无可撼动的霸主,生态环境极大丰富。Spring已经统治了后端开发。” 年迈的JVM大臣居然夸起Spring来!
“Java虚拟机性能强大,其他语言虚拟机都是玩具。” Spring大臣赶紧投桃报李。
......
都是一些听过几百遍的、老掉牙的东西。
国王听得有些烦,挥手让众人退下。
他决定带几个保镖,微服出宫,到外边亲自走一走,看一看。
1.微服私访
走出都城大门,国王看到了一望无际的代码田地。
烈日下,无数的Java码农在这里辛苦劳作,CRUD的劳动号子响彻云霄。
国王走近一看,果然,码农们用的工具都是SpringBoot和Spring Cloud,看来大臣所言不虚。
前面的大树下,一个中年人开着小茶铺,几个码农聚在那里,一边休息喝水、一边乘凉聊天。
国王悄悄走近。
中年人打着蒲扇,笑眯眯地说:诸位,你们知不知道,Java已经大祸临头,你们有可能要失业了。
一个戴着厚厚眼镜的码农笑得把茶都喷了出来:哈哈哈,危言耸听,这怎么可能?
中年人慢悠悠地说:时代变了,原来的Java特别适合大规模的服务器端应用,尤其擅长时间高性能运行。现在是云计算时代,微服务时代,有了容器,集群,服务可以随时重启,并且微服务越来越小,用什么语言都可以。
另一个花格子衬衫码农说:那也可以用Java写啊,SpringBoot挺好的啊,约定重于配置,内置服务器,一个jar包就跑起来。
其余几个码农纷纷附和,国王也暗自点头。
中年人笑道:云端应用要求1. 镜像小 2. 启动速度快,即起即用。Java能做到吗?
厚眼镜码农说:嗯,Java的docker镜像动辄上G, 冷启动实在太慢了,每次都得等半天!
花格子衬衫说:还有Spring启动时用了太多的反射黑魔法,启动速度更慢。
中年人说道:这就对了,我带着小茶铺游历过Python王国、JavaScript王国,Go王国,人家那里就没有这样的问题,非常适合云端应用,你们不妨去看看啊。
一番话说得这几个Java码农动了心,开始窃窃私语,打探去那些王国的道路。
国王意识到这个中年人来者不善,给保镖使了个颜色。
保镖掀翻小茶铺,扭起中年人就走,留下几个码农目瞪口呆。
2.三个计策
国王召来Spring大臣和JVM大臣,一起审问这个中年人。
国王:你是何人,为什么在那里危言耸听、鼓惑我朝年轻人?
中年人:小民说的都是事实啊,陛下,您可能被蒙蔽了,外界正在发生翻天覆地的变化啊,Java如果不与时俱进,岌岌可危啊。
Spring大臣和JVM大臣互相看了一眼,意味深长。
国王倒不在意,问道:你有什么建议?
中年人:小民有一个上策、中策和下策,陛下想先听哪一个?
国王:哦?三个计策?先说说下策。
中年人:下策自然是保留现状不变。
Spring大臣:相当于没说,中策呢?
中年人:中策就是改Spring,Spring应用在启动时会扫描代码中的bean,然后用反射的方式注册bean,这种做法的耗时与应用的代码量成正比,所以启动性能会很差。
如果在编译时把反射转化为直接调用的类,将会大幅提升应用的启动速度。我的研究显示,这种办法至少可以将成本降低50%,并且民间已经出现了一个叫做Micronaut的框架,它已经实现了编译期的依赖注入!
Spring大臣一听这家伙要把自己干掉,大惊失色,赶紧跪倒。
他先回顾了祖上如何用SpringMVC干死Struts的英勇事迹,又不动声色地提起自己如何与时俱进,用SpringBoot、Spring Cloud,Spring WebFlux在微服务时代和反应式编程时代勇立潮头。希望Java国王能念起旧情。
国王眼珠一转,看了一眼JVM大臣:好吧,也许这种办法能提升Spring应用的启动速度,但是据我所知JVM的启动速度也很慢,这又该怎么办?
中年人:这就是我要说的上策了,抛弃JVM,把Java程序编译成本地代码来执行!
“大胆!你这是要革命,要谋反!” JVM大臣忍不住了。
“陛下,这等狂悖之徒,拉下去问斩吧!” Spring大臣也立刻拱火。
国王心里很清楚,二十多年了,Java帝国最厉害的无过于字节码和JVM,如今ZGC垃圾回收器停顿时间不超过10ms,停顿时间还不会随着堆的增大而增大,JVM的JIT也炉火纯青,在运行时找到最热点的代码,编译成本地二进制执行,效率直逼C语言!
相比之下,JavaScript和Python虚拟机能叫虚拟机吗?玩具而已!它们怎么不强调自己的停顿时长?
不过这个计策倒是非常大胆,云计算时代,真的需要JVM吗?
国王陷入沉思。
3.抛弃JVM
JVM大臣看到国王不说话,又描述了一遍Java程序的生命周期。
- JVM初始化
- 应用初始化
- 应用预热
- 应用稳定
- 关闭
每个阶段都有着重要使命,尤其是应用预热的时候,会把Java字节码编译成本地代码。
“如果抛弃JVM,前辈们所做的所有努力都不复存在!这会动摇我Java帝国的国本啊!” JVM大臣伏地干嚎。
Java程序监控、扩展、jstat、jstack、jmap都用不了了。
调试的时候,也只能用复杂的GDB汇编调试,非常麻烦。
但是编译成本地代码,好处也非常明显,没有冷启动问题,启动即巅峰。
看到国王依然没有反应,JVM大臣决定抛出杀手锏:
“陛下,我Java帝国之所以能称雄世界,关键就是生态极其丰富,框架和类库覆盖了后端开发的所有方面。”
“而这些框架和类库中在大量地使用反射,甚至用动态代理在运行时动态生成字节码,换句话这些东西在编译时根本无法确定,只有到运行时才能确定。”
“举个例子,对于Class.forName("x.y.z")这样的代码,如何编译时就把它变成成本地代码?”
姜果然是老的辣,JVM大臣一下子就抓住了最关键的点,把皮球踢给了中年人。
没想到中年人胸有成竹:“这非常简单,在做静态代码分析的时候我会发现x.y.z是个需要被装载的类,然后把它也编译成本地代码!”
“那如果这里不是个字符串的值,而是一个变量呢?Class.forName(someClassName)” JVM老头得意地笑,他早就挖好了坑。
“那就没办法了,只好让用户在配置文件中告诉我们哪些类需要编译成本地代码了。”
“哈哈哈,说得轻巧,一个框架用了那么多反射,你让用户在配置文件中全部提前告诉你,怎么可能?”
中年人不甘示弱:“那我可以开发一个程序,让用户的程序运行一遍,我的程序监控用户的程序哪些地方用了反射,然后自动生成配置文件!”
“程序那么多分支,你运行一遍就能找到所有用到反射的地方?”
JVM大臣转向国王,斩钉截铁地说:“陛下,此法断不可行。”
“寡人觉得这其实就是不满足封闭性原则。除了反射之外,还有动态代理,JNI,序列化等,当Java代码使用这些特性的时候,静态编译就会遇到问题,需要想变通办法,而变通办法又无法覆盖所有情况。”
国王果然是国王,高屋建瓴。
“陛下真是英明,一下子就上升到了理论层面,我等望尘莫及。” JVM赶紧拍马屁。
4.编译
“陛下,把这个散播谣言,鼓惑人心的家伙拉下去宰了吧!” Spring大臣提醒道。
“虽然Java的动态性无法完美满足封闭性原则,但是静态编译确实是非常诱人,你说说,具体怎么做。” 国王不理Spring大臣,继续询问中年人。
“这个嘛,小民有个基本的思路,就是由用户指定程序入口,嗯,相当于main函数,然后静态编译器从这里开始分析程序的可达范围,把所有的可达的函数和一个小的运行时支持代码编译成native image。”
“可笑啊可笑,你难道忘记了Java是个面向对象的语言,多态无处不在?” JVM大臣讽刺。
“我给你举个例子,看看你怎么做静态分析。”
void process(List employees){
int size = employees.size();
......
}
- 1.
- 2.
- 3.
- 4.
“这个List是JDK的一个接口,JDK有很多实现类(ArrayList,LinkedList,Vector等),我们的项目也有很多自定义的List实现类,employees的实际类型只能在运行时确定,你的静态分析如何确定呢?”
“你不会把List的所有实现类都给编译成二进制代码吧?” Spring大臣马上添油加醋。
“如果是这样的函数 void process(Object o) ,Object是所有类型的根,难道你要编译所有的类?哈哈哈!” JVM大臣不由得大笑起来。
“那肯定不行,我有个独门绝技,叫‘指向性分析’,可以在不运行程序的情况下,找到一个类型变量在运行时的可能类型。” 中年人不慌不忙。
指向性分析?Spring大臣和JVM大臣再次对视,他们明白这位中年人不会多说了。
国王盯着这位中年人,问道:“你叫什么名字?”
“小民叫Graal。”
国王心里盘算起来。
云计算时代,容器技术的出现,write once, run anywhere已经不重要了。
相反,Java确实面临着镜像大,冷启动慢的严峻挑战。
把Java代码编译成本地代码,要抛弃祖宗的基业,但可能是破局的关键。
自己作为新一代国王,坚决不能吃老本,更不能成为亡国之君,所有可能的方向都要尝试。
想到此处,国王对中年人说:“好吧Graal,寡人已经明白你的意图,现在给你一队人马,专门研究静态编译技术!Spring大臣你要密切配合!”
5.尾声
几个月后,中年人推出了一个新的虚拟机,叫做GraalVM,这个VM野心极大,不仅实现了把Java编译成本地代码,还支持JavaScript, Ruby, R,Python等语言。
虽然Spring大臣不太情愿,但是国王的圣旨不可违抗,他再次与时俱进,配合GraalVM推出了SpringNative ,把Spring应用编译成了原生镜像。
SpringNative启动时间提升了50倍,并且启动即巅峰,内存占用减少了5倍。
Java在云计算时代的危机暂时度过,未来它还会遇到什么挑战呢?