在并发量比较高的系统,在部署的时候,它的RT会突然升高一下,就会有类似这种FullGC卡顿的现象。但是在这之后,它的RT会变得比原先更小,这就是我们这一期来讲的Java即时编译JIT(just in time)技术。
在整个Java应用,在启动的过程当中包含很多的步骤,包括vm的一个初始化,应用的一个初始化,包括类加载,还有一些即时编译,还有他的一个稳定期,最后再到他的一个关闭。我们平时学习Java,可能更多只是main函数启动,然后类加载,可能了解一下。很多一些底层的知识其实并没有涉及到。为什么是SpringBoot3.0他是强依赖这个JDK17的啊?
先问一个问题,Java是编译性语言还是解释性语言,其实他是半编译半解释的。Java是一门跨平台的语言,他的跨平台主要就是基于它的Java虚拟机。
它是屏蔽了底层硬件的一些细节,就因为有这个中间层,所以能够达到一个跨平台执行的一个效果。
Java其实是分两个步骤,第一个过程就是把你的Java源代码变译成字节码,然后再解释性的执行。
在JVM层面,其实在字节码这一层,其实还是跨平台的设计,这样两个步骤主要就是为了满足这个跨平台的特性。
但是在很多时候,我们一旦确定了平台之后,我们部署了之后,更多强调的是它的性能,那解释执行的性能肯定是比不上本地代码的。因为你去做很多系统性的优化,包括你的连接,动态编译,甚至跟操作系统底层的一些强绑定。本地化成机器码,这样你的执行效率才是最高的。
这就是JIT技术的一个由来,其实在jdk1.3的时候,都已经引入了这种即时编译的技术,它主要是有两个编译器。
就是c1跟c2。c1就是client,然后c2的话就是server。从字面意思大家也能感觉到这两个侧重点是不一样。clinet编译器他主要强调启动速度比较快,他不会对你的代码做过分的一些优化。那c2就是server端的这个编译器,他就会去给你的代码做大量的优化,跟你的机器做强绑定,这样的话你的代码通过这种编译就能够达到最好的一个执行的性能。JDK自己也有两种运行模式,Client模式跟Server模式,这两个也是有这种差异的。
为什么不在启动的时候都把它编译好,编译成成最底层的代码效率最高,因为我们在线上执行的代码也是满足28定律的。就是20%的代码执行80%的请求,所以说如果你去全部编译,占用了这个内存,还有启动的时间都会大大的增加。所以这个其实是得不偿失的,现阶段主要采用的是JDK8,主要还是采用动态编译的这种技术。
它触发c1跟c2主要是根据它的一个执行次数,还有它的一些其他的一些算法去捕捉你的热点代码。主要是通过这两个阈值去触发它的一个编译,但是这样就会有我们最开始提到那个问题。他可能会卡顿,所以当时为了去解决这种卡顿的问题,甚至想过用流量重放的方式。去在应用注册到服务中心的时候,去先给它做一个预热,当时只是有这样一个构想,并没有实际去实践。当时主要遇到了一些问题是你的流量怎么去重放,然后你的外部的io怎么去熔断,然后你怎么去确定你有没有触发这两个即时编译。所以我们大部分的系统其实并没有去专门针对这个事情去做很多优化,但是我觉得如果真的是这种c端的大流量的这种高并发的系统,这是值得去做的一件事情。
不过最近JDK的一些新版本发版了之后对这一块有一些优化,这就是接下来讲的GraalVM编译器,刚才也讲到c2编译器,它虽然对性能是一种极限的优化。甚至说它可以超越c++的一个性能,但是它这种优化很多时候是不可控的,甚至可能会导致你的虚拟机崩溃,再加上这两个编译器,它早期跟jvm的耦合性是比较强的。所以说这几年一直没有大的突破。那么Graal编译器,就是为了去解决这个事情的,它也是甲骨文牵头去做的,它主要就是从这种动态编译转化成静态编译,就是你在应用还没有启动,还没有部署之前帮你把很多东西给编译做好了。跟你的机器环境去做强绑定了,甚至说你编译好了这个代码之后,你JVM都不需要了。
当然,他还是遵循整个jvm的一些规范的,所以说你是JVM体系这种语言,他还是能保留多语言 ,跨平台这些特性,
而且它的编译是可控的,它不会造成你虚拟机的一个崩溃。并且他的实现跟vm本身其实是一个解耦的,所以说他两个是可以单独去进行迭代的。这种静态编译了之后,它JVM占的内存也减少了,因为它甚至不需要JVM虚拟机了。然后也解决掉了像我们最开始提到了那个卡顿的问题,因为它根本就不需要动态编译了,就从根本上解决了预热的这个问题。
它的代码执行从你部署完应用启动了之后就非常的稳定,至于他的一个工作原理,其实还设计了一个很好的数据结构,图形的数据结构。大家感兴趣的话也可以去了解一下,这里就不展开讲了。
所以最新的Spring6,SpringBoot3.0他们设计的初衷就是为了Java整个生态系统,下一步的一个云原生的一个发展趋势,所以它采用了这种native AOT的技术。
它就是依赖Graal这种编译器。我们再回答另外一个问题啊,就是Spring6,SpringBoot3他为什么必须要求JDK17.其实在JDK8之后,11是一个长期维护的版本。11之后就是17但是在官网看来给8的维护的时间其实比11要长,所以11它并不是严格的一个长期支持版本。Spring他做这样的一个选择其实也冒了一定的风险的,作为框架的开发者,他是希望引领整个潮流。 把整个生态朝更好的方向去发展,但是由于历史的一些原因,比如说兼容性啊。
Log4j的作者,他就是因为一些兼容性的代码考虑到以前的兼容性,导致了去年那个比较重大的bug。他自己也不喜欢这个特性,但是由于一些历史原因,兼容性的原因,他没办法,他改不动,所以很多人觉得Java语言很臃肿,这也是其中的一个重要原因。像Spring他本身就是为了追求这种轻量化的,所以他敢去做这种尝试啊,但是大家也不用过于担心,Spring2.7之后他可能就不一定会出新版本了,但是2.7之前这个时间他跟SpringBoot3.0的开发是处于一个并行的一个时间, 他可能会并行的去开发,有几年的时间周期。让我们去慢慢的去切换,有一个切换的过程,他会给我们留好缓冲的时间,我也不建议大家盲目的去升级jdk的高版本。我们还是要再观察一下整个Spring它后面的版本的一些新特性是不是真的能够让我们的系统运行的更好,更高效,更稳定或者更省资源,我们到时候经过充分论证,然后再去做这个升级,这才是整个切换的一个最佳实践。