直到Substrate VM出现,才算是满足了人们心中对Java提前编译的全部期待 。Substrate VM是在Graal VM 0.20版本里新出现的一个极小型的运行时环境,包括了独立的异常处理、同步调度、线程管理、内存管理(垃圾收集)和JNI访问等组件,目标是代替HotSpot用来支持提前编译后的程序执行 。它还包含了一个本地镜像的构造器(Native Image Generator)用于为用户程序建立基于Substrate VM的本地运行时镜像 。这个构造器采用指针分析(Points-To Analysis)技术,从用户提供的程序入口出发,搜索所有可达的代码 。在搜索的同时,它还将执行初始化代码,并在最终生成可执行文件时,将已初始化的堆保存至一个堆快照之中 。这样一来,Substrate VM就可以直接从目标程序开始运行,而无须重复进行Java虚拟机的初始化过程 。但相应地,原理上也决定了Substrate VM必须要求目标程序是完全封闭的,即不能动态加载其他编译期不可知的代码和类库 。基于这个假设,Substrate VM才能探索整个编译空间,并通过静态分析推算出所有虚方法调用的目标方法 。
Substrate VM带来的好处是能显著降低了内存占用及启动时间,由于HotSpot本身就会有一定的内存消耗(通常约几十MB),这对最低也从几GB内存起步的大型单体应用来说并不算什么,但在微服务下就是一笔不可忽视的成本 。根据Oracle官方给出的测试数据,运行在Substrate VM上的小规模应用,其内存占用和启动时间与运行在HotSpot相比有了5倍到50倍的下降,具体结果如下图所示:

文章插图
启动时间对比

文章插图
启动时间对比
Substrate VM补全了Graal VM“Run Programs Faster Anywhere”愿景蓝图里最后的一块拼图,让Graal VM支持其他语言时不会有重量级的运行负担 。譬如运行JavaScript代码,Node.js的V8引擎执行效率非常高,但即使是最简单的HelloWorld,它也要使用约20MB的内存,而运行在Substrate VM上的Graal.js,跑一个HelloWorld则只需要4.2MB内存而已,且运行速度与V8持平 。Substrate VM 的轻量特性,使得它十分适合于嵌入至其他系统之中,譬如Oracle自家的数据库就已经开始使用这种方式支持用不同的语言代替PL/SQL来编写存储过程 。
没有虚拟机的Java
尽管Java已经看清楚了在微服务时代的前进目标,但是,Java语言和生态在微服务、微应用环境中的天生的劣势并不会一蹴而就地被解决,通往这个目标的道路注定会充满荆棘;尽管已经有了放弃“一次编写,到处运行”、放弃语言动态性的思想准备,但是,这些特性并不单纯是宣传口号,它们在Java语言诞生之初就被植入到基因之中,当Graal VM试图打破这些规则的同时,也受到了Java语言和在其之上的生态生态的强烈反噬,笔者选择其中最主要的一些困难列举如下:
- 某些Java语言的特性,使得Graal VM编译本地镜像的过程变得极为艰难 。譬如常见的反射,除非使用安全管理器去专门进行认证许可,否则反射机制具有在运行期动态调用几乎所有API接口的能力,且具体会调用哪些接口,在程序不会真正运行起来的编译期是无法获知的 。反射显然是Java不能放弃不能妥协的重要特性,为此,只能由程序的开发者明确地告知Graal VM有哪些代码可能被反射调用(通过JSON配置文件的形式),Graal VM才能在编译本地程序时将它们囊括进来 。这是一种可操作性极其低下却又无可奈何的解决方案,即使开发者接受不厌其烦地列举出自己代码中所用到的反射API,但他们又如何能保证程序所引用的其他类库的反射行为都已全部被获知,其中没有任何遗漏?与此类似的还有另外一些语言特性,如动态代理等 。另外,一切非代码性质的资源,如最典型的配置文件等,也都必须明确加入配置中才能被Graal VM编译打包 。这导致了如果没有专门的工具去协助,使用Graal VM编译Java的遗留系统即使理论可行,实际操作也将是极度的繁琐 。
- 大多数运行期对字节码的生成和修改操作,在Graal VM看来都是无法接受的,因为Substrate VM里面不再包含即时编译器和字节码执行引擎,所以一切可能被运行的字节码,都必须经过AOT编译成为原生代码 。请不要觉得运行期直接生成字节码会很罕见,误以为导致的影响应该不算很大 。事实上,多数实际用于生产的Java系统都或直接或讲解、或多或少引用了ASM、CGLIB、Javassist这类字节码库 。举个例子,CGLIB是通过运行时产生字节码(生成代理类的子类)来做动态代理的,长期以来这都是Java世界里进行类增强的主流形式,因为面向接口的增强可以使用JDK自带的动态代理,但对类的增强则并没有多少选择的余地 。CGLIB也是Spring用来做类增强的选择,但Graal VM明确表示是不可能支持CGLIB的,因此,这点就必须由用户(面向接口编程)、框架(Spring这些DI框架放弃CGLIB增强)和Graal VM(起码得支持JDK的动态代理,留条活路可走)来共同解决 。自Spring Framework 5.2起,@Configuration注解中加入了一个新的proxyBeanMethods参数,设置为false则可避免Spring对与非接口类型的Bean进行代理 。同样地,对应在Spring Boot 2.2中,@SpringBootApplication注解也增加了proxyBeanMethods参数,通常采用Graal VM去构建的Spring Boot本地应用都需要设置该参数 。
推荐阅读
- 用云服务器搭建VPN,构建自己的企业专线
- 砍盆箐寻茶记,云南摆尾箐村
- 燕云十六州指的是哪十六州 燕云十六州为什么重要
- 渔家傲阅读理解 渔家傲解释和原文
- 困鹿山皇家古茶园介绍,千家寨茶饼原生大古茶
- 山河月明|老戏骨云集助力历史剧 《山河月明》让现代人学会职场生存!
- 云南因什么而得名 云南名称的由来和历史
- 湖红工夫茶产地先容,云南滇红红茶产地先容
- 可治病的云南百抖茶,最给力的瘦身茶
- 云主机数据库导入与导出
