美文网首页
记一次生产事故—JIT编译与CPU使用率飙升

记一次生产事故—JIT编译与CPU使用率飙升

作者: 小胖学编程 | 来源:发表于2021-11-15 17:44 被阅读0次

    一个隐藏比较深的问题。项目使用grpc完成远程调用,但是grpc定义的pd文件,由于message中参数过多,达到190多个,在pb的该message某次新增一个字段后。项目的CPU使用率直接由10%飙升到70%。

    1. 基础知识

    1.1 什么叫做JIT

    在计算机技术中,即时编译(just-in-time,缩写JIT;又译及时编译、实时编译),也被称为动态编译或者运行时编译,是一种执行计算机代码的方法,这种方法涉及在程序执行过程中(在执行期)而不是执行之前进行编译。

    1.2 JIT工作原理

    在字节码编译的系统中,源代码被转换为称为字节码的中间表示形式。字节码不是任何特定计算机的机器代码,可以在计算机体系结构之间移植(即JAVA的跨平台性)。然后可以在虚拟机上解释或者运行字节码、JIT编译器在许多(或全部、很少)部分读取字节码,并将他们动态的编译成机器代码,以便程序更快速运行。这可以针对每个文件、每个函数甚至任何任意代码片段进行编译;代码可以在即将执行时进行编译(因此称为“即时”),然后缓存并在以后重用,无需重新编译。

    缺省情况下,启用JIT编译器。在编译方法时,JVM直接调用该方法的已编译代码,而不是对代码进行解释。理论上,如果编译不需要占用处理器时间和内存,那么编译每个方法都可能使JAVA程序速度接近于本机应用程序的速度。

    JVM流程.png

    图片来源:如何通俗易懂地介绍「即时编译」(JIT)

    疑问?为什么JVM里既有compiler,也有interpreter(解释器)?

    JVM在解释器之外引入了即时(Just In Time)编译器:当程序运行时,解释器首先发挥作用,代码可以直接执行。随着时间推移,即时编译器逐渐发挥作用,把越来越多的代码编译优化成本地代码,来获取更高的执行效率。解释器这时可以作为编译运行的降级手段,在一些不可靠的编译优化出现问题时,再切换回解释执行,保证程序可以正常运行。

    1.3 JIT编译是在项目启动时编译的吗?

    不是!JIT编译不需要占用处理器时间和内存。在JVM首次启动时,将调用数千种方法。即使程序最终实现了较高的峰值性能,编译所有这些方法也会对启动时间产生显著影响。实际上,第一次调用方法时不会对方法进行编译。对于每个方法,JVM都会保留一个调用计数,以预定义的编译阈值开始,并在每次调用方法时递减。在调用计数达到零时,将触发方法的即时编译。因此,在JVM启动后将立即编译常用方法,而较长时间(或者根本不编译)不常用的方法。JIT编译阈值帮助JVM快速启动并且还可以提高性能。选择阈值以在启动时与长期性能之间实现最佳平衡。

    image.png

    图片来源:基本功 | Java即时编译器原理解析及实践

    为了提升执行速度,Hotspot JVM采用了JIT compile技术,JIT编译器将运行频率很高的字节码(热点代码)直接编译为机器码执行以提高性能。

    1.4 JIT编译与静态编译

    常见的编译型语言如C++,通常会把代码直接编译成CPU所能理解的机器码来运行。而Java为了实现“一次编译,处处运行”的特性,把编译的过程分成两部分,首先它会先由javac编译成通用的中间形式——字节码,然后再由解释器逐条将字节码解释为机器码来执行。所以在性能上,Java通常不如C++这类编译型语言。

    为了优化Java的性能 ,JVM在解释器之外引入了即时(Just In Time)编译器:当程序运行时,解释器首先发挥作用,代码可以直接执行。随着时间推移,即时编译器逐渐发挥作用,把越来越多的代码编译优化成本地代码,来获取更高的执行效率。解释器这时可以作为编译运行的降级手段,在一些不可靠的编译优化出现问题时,再切换回解释执行,保证程序可以正常运行。

    即时编译器极大地提高了Java程序的运行速度,而且跟静态编译相比,JIT(即时编译器)可以选择性地编译热点代码,省去了很多编译时间,也节省很多的空间。目前,即时编译器已经非常成熟了,在性能层面甚至可以和编译型语言相比。不过在这个领域,大家依然在不断探索如何结合不同的编译方式,使用更加智能的手段来提升程序的运行速度。

    2. JIT编译与CPU使用率飙升

    上面说到JIT编译,那么他和CPU使用率飙升有什么关系呢?

    上面说到,热点代码会被直接编译成机器码执行来提高性能,但是考虑这样一个场景:热点代码每次调用时均逐条解释(Interpreter),是不是需要消耗CPU资源?

    那么您又得好奇了,JVM不是采用了JIT技术了吗,为啥热点代码不会直接编译成机器码执行?

    2.1 大方法默认关闭JIT编译

    上面说到,JVM是默认开启JIT编译器,但是存在一个参数,若一个方法中字节码大小超过8000字节,那么就不允许被JIT编译,而8000这个阈值在产品版的HotSpot里无法被调整。

    关键词:

    -XX:+DontCompileHugeMethods
    -XX:HugeMethodLimit=8000
    

    解决方案:可以通过JVM参数:-XX:-DontCompileHugeMethods来允许大方法被JIT编译。

    但是这种方式存在风险,会导致所有的方法都有可能编译成字节码,但是一旦CodeCache满了,后续的方法都无法编译成字节码,这种方法是存在一定风险的。

    2.2 查看方法占用的字节

    可以下载:jclasslib Bytecode viewer插件。

    使用方法:IDEA字节码学习查看神器jclasslib bytecode viewer介绍

    2.3 触发场景以及解决方案

    测试案例——Java中一个方法字节码的长度为什么会影响程序并发下的性能?

    在实际工作中,一个方法很少能超过8000字节,但是自动生成的方法便存在这种风险。例如本次事故,就是grpc的pb文件中,一个message{}中的属性字段太多导致的。

    针对grpc的这种场景:可以增加一个嵌套的message字段,新增的字段都放在这个嵌套结构下。可以解决这种业务场景。

    参考资料

    JIT 编译器

    如何通俗易懂地介绍「即时编译」(JIT)

    IDEA字节码学习查看神器jclasslib bytecode viewer介绍

    Java中一个方法字节码的长度为什么会影响程序并发下的性能?

    基本功 | Java即时编译器原理解析及实践

    相关文章

      网友评论

          本文标题:记一次生产事故—JIT编译与CPU使用率飙升

          本文链接:https://www.haomeiwen.com/subject/ahmozltx.html