一、JWarmup背景
(一)应用程序预热
Java的方法要被执行时,首先这个方法所在的类需要被JVM加载,这个过程包括各类文件的验证、解析、链接以及类的初始化。当这个类被加载进来了以后,JVM就可以去执行这个方法。
JVM在刚开始的时候会使用模板解释器去解释执行方法,模板解释器除了一个个去执行方法中的Bytecodes之外,还会额外收集关于方法执行动态运行的信息,例如方法执行的调用次数,调用时一些类型的信息等。这些信息都会提供给JVM的即时编译器,由它利用这些信息将刚才解释执行发现的热点方法编译成为Native Code。这样JVM就不用模板解释器去执行这些方法,而是去执行性能更高的Native Code,从而使应用程序的性能得到大幅提升。
当大多数热点方法都被编译成为Native Code以后,应用程序的预热就完成了。
(二)遇到的问题
Java有非常丰富的应用场景,一个典型的场景就是我们会用Java写一些 Web服务。在Web服务的部署过程中,会发现预热给我们带来非常大的困扰。当我们把一个Web服务部署到线上后,应用启动完成,此时就会有大量的用户请求进入。
这个时候由于有大量的热点方法需要被编译,JVM的编译线程会非常忙碌,因为它需要占用大量的CPU将这些方法编译成为Native Code。同时又因为用户的线程需要执行解决用户的请求,因此它也会占用大量的CPU,并且会抢占编译线程所使用的CPU,这样就会导致编译线程无法尽快地把这些热点方法编译到Native Code,使得应用程序长时间运行在解释执行的状态,降低服务的质量。同时,服务的RT会增加,服务所使用的CPU也会非常的高,这就是在真实场景中所遇到应用程序预热的问题,接下来我们来看一下阿里巴巴Dragonwell 8中的JWarmup特性是如何解决这个问题。
二、JWarmup功能
上方为JWarmup的流程图,它将应用程序的发布分成了两个阶段,分别是Recording和Replaying。在Recording阶段,JVM会接受线上的请求,同时记录JVM即时编译器它所编译方法的信息,并且将这些信息都输出到一个文件之中。
等到第二次再去启动的时候,JVM就可以去读取刚刚所记录的这些方法编译的信息,同时会主动的触发即时编译器编译刚刚记录的热点方法,使得在用户请求到来之前,就把热点方法编译成为性能较高的Native Code,避免了在用户请求大量进入的时候做编译,这样就能够进一步提高应用程序的性能,节约CPU使用率。
三、案例演示
(一)Demo代码
下面根据一个简单的例子展示如何使用JWarmup功能。
上方为一个简单的 Java程序,在这个程序之中有一个循环用来模拟线上应用的一个热点,循环会反复调用一个方法。
(二)Recording
如何去使用JWarmup的记录功能Recording?
- 首先,从http://dragonwell-jdk.io/下载Dragonwell 8;
- 添加JVM参数启动JWarmup的记录功能:
-XX:+CompilationWarmUpRecording
-XX: CompilationWarmUpRecording=30
-XX: CompilationWarmUpLogfile=./jwarmup.log
-XX:-ClassUnloading
第一个参数表示去打开JWarmup的记录功能;
第二个参数表示需要记录的时间,在当前Demo之中选择记录30秒;
第三个参数表示记录编译信息生成文件的路径,在这个Demo中,我们将这个文件生成在当前目录下的jwarmup.log这个文件;
第四个参数是由于JWarmup的Recording功能不支持ClassUnloading,所以需要将这一功能关闭。
当设定的记录时间到了以后,JWarmup会将记录好的编译信息输出到指定的文件之中,同时会在应用程序的输出中看到以下这样一条日志,表明记录是成功的。
(三)Replaying
如何使用JWarmup的Replaying功能?
- 添加JVM参数:
-XX:+CompilationWarmUp
-XX:+CompilationWarmUpLogfile=./jwarmup.log
-XX:+PrintCompilationWarmUpDetail
第一个参数表示要使用JWarmup的编译功能;
第二个参数需要指定刚刚记录的包含编译信息的文件,在当前Demo之中,就是刚刚所记录的当前目录下的jwarmup.log文件;
第三个参数表示我希望JWarmup打印出一些详细的日志,帮助我记录JWarmup工具的一些行为。
当把这些参数配置好以后,将服务启动,等待一些关键的类的加载完成,可以使用jcmd <pid> JWarmup -notify主动触发Warmup的编译。
当Warmup编译完成后,可以在程序的标准输出中看到下面三条log,就表示这一次Warmup编译是成功的。
以上就是关于JWarmup的基本介绍,包含JWarmup所需要解决的问题,解决方法,以及用案例讲述如何使用JWarmup功能。同时,我们对JWarmup这个功能还做了非常多的改进,形成了我们另外一份工作:JWarmup2。
上方为JWarmup2整体流程图,可以看到在这份工作之中,我们记录了更加丰富的信息,去更好的解决应用程序预热的问题。
首先我们会使用JFR(Java Flight Recorder)统一所记录的编译的信息,这些信息也可以形成一个JFR文件,使用JDK官方所提供的Java Mission Control浏览所记录的所有热点方法的信息。
此外,我们除了记录一些方法的编译,还记录了每一个方法它所依赖的类的信息。这样子在我们第二步预热的时候,就可以根据这些依赖的信息,当我们看到一个方法,它所有的依赖的类都被加载了以后,就会自动触发这个方法的Warmup编译,避免了人工手动触发Warmup编译。
此外,我们还额外记录了这些方法所有的Profiling信息,这些信息能够帮助我们在第二步去更好地生成Warmup的代码,从而进一步提高应用程序的性能。
JWarmup2未来将在阿里巴巴Dragonwell 11中开源,欢迎大家使用。