当前位置: 首页 > 科技观察

为什么Java程序在一段时间后运行速度会变快?

时间:2023-03-12 06:05:41 科技观察

对于Java应用程序,在程序员中流传着这样一句话:看一个Java程序跑得快不快,要多跑几遍;另外,Java程序运行一段时间后会提速。速度甚至可以赶上C/C++程序的速度。如果你问为什么一段时间后它运行得更快?你通常可以听到这样的回答“因为JVM会大量调用编译和执行热方法”。通俗点说,JVM会将热点方法编译成机器码,执行效率会更高。就像在公司、工厂里,对于一个任务,老手一般比新人快,因为老手比较熟悉。因此,在招聘要求中,你很少会看到新入职的,而且大部分都必须有工作经验。JVM编译hot方法生成的机器代码。因为是针对当前平台和硬件生成的,所以是分析应用程序的具体执行情况后编译的。所以,像老手一样,更能看清情况,当然效率也更高。在幕后默默编译的人就是JIT(Just-In-Time)编译器,一般也叫即时编译器。今天,我们就来看看越来越快的运行背后JIT是如何工作的。我们都知道Java本来就是一种解释型语言,也是解释执行的。为什么要编译执行呢?在执行java-version的时候,我们一般可以看到当前的Java版本号,并且会有一个混合模式,说明当前的JVM运行在混合模式下,既包括解释执行,也包括编译执行。我们也可以通过参数强制只执行一种模式。每个环境根据自己的需要选择执行方式。与编译执行相比,解释执行要慢得多,但它仍然广泛用于各种虚拟机,例如它的内存占用小,应用程序启动时间更短。一个更重要的优势是它的简单性。当一种新的语言或一种语言的新特性出现时,解释器可以比编译器更快地实现它。另外,开发者会考虑成本效益,有些语言特性比较难,只用编译器中的解释器是不划算的。在开发和实现一种语言时,使用解释器只有两个要求:熟悉VM实现语言了解新的语言特性、语法和语义但是就像在JIT编译器中实现新的语言特性一样,对开发人员的要求更多:熟悉目标机的应用程序二进制接口规范将新的语言特性映射到目标机的接口上。运行时掌握开发编译器生成目标机器码的能力。为了应用程序的执行效率和速度,Java尤其需要JIT。可以编译一些高频代码,换取更好的效率。JIT是一种通过编译热方法和代码段来生成机器代码的形式。下次调用该方法时,直接通过vtable中链接的机器码执行,效率高。那么问题来了,什么样的方法才算是热点方法,如何判断热点方法呢?对于热点方法的计算,在虚拟机中一般有以下几种实现方式:Method-basedJIT,JVM常用trace-basedJIT,Dalvik和TraceMonkey使用region-basedJIT,HHVM使用method-based这种形式准时制。一般的热点检测方法有基于采样的热点检测,即定期检查线程的调用栈顶。如果方法经常出现在栈顶,那么就是热点方法。另一种是基于计数器的热点检测,它会为每个方法创建一个计数器来统计该方法的执行次数。那些超过阈值的被认为是热点方法。当然需要注意的是,这里统计的次数并不是一个绝对的数字。类似于我们之前做限流降级的时候说的。它是一个时间段内的相对频率。如果在此期间没有超过,则不算。事实证明会减少。JIT编译出来的代码存放在CodeCache的内存区域。空间有限。当JVM启动时,会设置一个固定的最大值。实现形式也是堆。当分配满时,编译会停止,该类会被卸载并换成新版本等,也会从CodeCache中删除。此外,JVMJIT编译器包括两个编译器,C1和C2。在具体的编译过程中,一般采用分层编译,具体使用不同的编译器。相比C1和C2,编译时间更长,做的优化更多,比如内联、循环展开、逃逸分析、锁消除合并、堆栈替换……我们已经大致了解了JIT的原理,以及还了解到经过JIT编译后,机器码的执行效率更高。high,请问有什么方法可以知道我们自己的应用程序是否执行了JIT,我们使用的是C1还是C2,编译优化了哪些代码?有没有什么方法可以让我们知道哪些方法是JIT编译的,哪些方法本来是想更高效的,但是在期望编译的时候没有考虑到,能不能更直观的知道呢?一种方法是在应用程序启动时增加JVM参数:-XX:+UnlockDiagnosticVMOptions-XX:+PrintCompilation-XX:+PrintInlining-XX:+PrintCodeCache-XX:+PrintCodeCacheOnCompilation-XX:+TraceClassLoading-XX:+LogCompilation-XX:LogFile=~/a.log然后根据这些输出内容和日志文件中的内容进行分析。当然,如果用肉眼看的话,会很累。幸运的是,有一个优秀的开源工具可以解析日志文件。铛铛铛,我们来了。就是这样,JITWatch。https://github.com/AdoptOpenJDK/jitwatch是使用JavaFX开发的,非常强大。可以通过OpenLog直接解析上面输出的日志文件。比如一个简单的应用,打开日志后,会看到不同包下的内容,这里以example111为例。publicvoidjitTest(){longx=calc();System.out.println(x);}publiclongcalc(){longsum=0;for(longi=0;i<1000000;i++){sum=plus(sum,i);}returnsum;}publiclongplus(longa,longb){returna+b;}点击右边一个JIT编译后的具体方法后,点击TriView,会看到生成的字节码以及对应的源码与字节码和汇编码的对应关系.点击Chain,你会看到编译链接Inline-info会显示哪些方法被inline优化了。这里看到的OSR就是经常听到的栈上替换(On-stackreplacement),用于在解释器中优化执行。当向后跳转的循环分支达到一定阈值时,就会被编译。JITWatch还有一个沙箱环??境,可以用来实验和观察JIT的行为,观察JIT在JVM中的决策过程。借助工具,我们可以更好地理解JIT对应用程序优化的决策,从而使应用程序性能更好。