内容简介:JIT引起系统Load高分析
有个应用在启动的初期系统的load会飚的很高,导致监控系统报了告警;经过分析是JVM启动后对热点代码做JIT及时编译导致CPU使用率很高,而此时Jetty容器收到了大量的请求但是系统处理请求较慢导致业务的线程池满出现异常,这篇文章主要分析load高的可能原因以及JIT编译的常见优化选项。
load高的可能原因
- 长时间执行了耗费cpu的操作(usr使用率较高),比如死循环执行某个计算操作
- 等待竞争资源(比如锁、IO)致使请求处理慢,执行时间变长(sys占比高)
- 运行线程数见多,频繁上下文切换导致单个任务处理时间变长(sys占比高)
- 有大流量进入导致load身高
如何排查CPU占用升高的原因
一般从以下三个方面考虑:
- 业务代码执行,比如业务代码写了不合理的循环
- 框架和中间件的初始化过程
- JVM或者web容器(Tomcat/Jetty/Jboss等)
我们问题的背景是在启动的初期load较高,一段时间之后Load又降低下来,而且在多个主机上都出现了;根据这些特点首先排查了业务是否在启动过程做了一些复杂的操作,排查结果是没有;然后查了下接口调用日志发现在那个时间点收到了很多调用请求,联想到JIT的编译过程:JVM在启动初期解释字节码进行执行,当方法执行次数达到指定阈值后,触发JIT及时编译,JIT把字节码编译成机器码后执行,执行效率提高。这个时间点load飚高原因就是收到大量调用请求导致很多方法变成热点代码,JIT会启动对热点代码的编译,这个时候系统Load会升高导致请求处理较慢,最后就导致了业务线程池满。
JIT分层编译相关概念
需要注意的是,jdk1.8默认开启了分层编译,你可以通过 -XX:+TieredCompilation
开启分层编译,关于分层编译网上介绍的文章不多,主要分为C1和C2编译器,C1又称为客户端编译,C2编译器称为服务端编译器,编译级别如下:
0:解释执行,这是最慢的一种方式
1:简单C1编译代码
2:受限的C1编译代码,不做性能分析,根据方法调用次数和方法内部循环次数来启动
3:完全C1编译代码,编译器收集分析信息之后做的编译
4:C2编译代码,编译最慢,编译后执行速度最快
编译级别的转换主要是根据方法调用计数器和回边计数器(方法内循环次数)以及 C1和C2编译线程当前的负载情况 来决定是否开启更高级别的编译,每个层次的阈值可以通过 PrintFlagsFinal
参数的输出来查看,比如我在jdk1.8.0—25时参数如下:
CompileThreshold = 10000 IncreaseFirstTierCompileThresholdAt = 50 Tier2CompileThreshold = 0 Tier3CompileThreshold = 2000 Tier4CompileThreshold = 15000
可以看到层次2的阈值为0,层次3为2000,层次4(开启C2编译)为15000,根据当前的使用情况下一步采取的编译措施可能是如下几种:
- 继续解释执行(可能编译线程负载很高)
- 解释器开始采样分析
- 根据分析数据用C1的层次3进行编译,后续可以继续优化
- 不分析直接用C1的层次2进行编译,后续再优化的可能性较低
- 不需要任何信息直接用C1的层次1进行编译
如果server编译器队列满了,就会从server队列中取出方法,以级别2进行编译,在这个级别上,C1编译器使用方法调用计数器和回边计数器(但不需要性能分析的反馈信息)。这使得方法编译得更快,而方法也将在C1编译器收集分析信息之后被编译为级别3,最终当server编译器队列不太忙的时候被编译为级别4。
可以开启 PrintCompilation
选项来查看编译详情,下面这个列表是一个样例的编译输出:
编译时间戳 编译ID 方法属性 编译层次 方法签名(大小) 180 4 1 sun.instrument.TransformerManager::getSnapshotTransformerList (5 bytes) 180 5 3 java.lang.String::lastIndexOf (13 bytes) 181 6 3 java.lang.String::indexOf (7 bytes) 182 7 3 java.lang.System::getSecurityManager (4 bytes) 183 2 4 java.lang.String::hashCode (55 bytes) 183 1 4 java.lang.Object::<init> (1 bytes) 184 8 4 java.lang.String::length (6 bytes) 184 3 4 java.lang.String::charAt (29 bytes) 185 9 3 java.util.HashMap$Node::<init> (26 bytes) 185 10 4 java.lang.Math::max (11 bytes) 185 11 4 java.lang.String::indexOf (70 bytes) 186 12 4 java.lang.AbstractStringBuilder::append (29 bytes) 188 13 3 java.util.Arrays::copyOf (19 bytes) 189 14 3 java.lang.String::equals (81 bytes) 190 15 1 java.nio.Buffer::position (5 bytes) 190 16 n 0 java.lang.System::arraycopy (native) (static) 190 17 3 java.lang.String::startsWith (72 bytes) 190 23 4 java.lang.AbstractStringBuilder::ensureCapacityInternal (16 bytes) 191 19 3 sun.nio.cs.UTF_8$Encoder::encode (359 bytes) 192 30 3 java.io.WinNTFileSystem::normalize (231 bytes) 192 28 4 java.io.WinNTFileSystem::isSlash (18 bytes) 192 29 s 4 java.lang.StringBuffer::append (13 bytes) 194 24 4 java.lang.CharacterData::of (120 bytes) 194 25 4 java.lang.CharacterDataLatin1::getProperties (11 bytes) 196 27 3 java.lang.String::<init> (62 bytes) 196 26 3 java.lang.Character::toLowerCase (6 bytes) 196 20 3 java.lang.Math::min (11 bytes) 196 18 ! 3 sun.misc.URLClassPath$JarLoader::ensureOpen (32 bytes) 196 21 3 java.util.jar.JarFile::getJarEntry (9 bytes) 197 22 3 java.util.jar.JarFile::getEntry (22 bytes) 1195 31 3 java.lang.String::replace (127 bytes) 1196 34 3 java.lang.String::indexOf (166 bytes) 1196 32 3 java.lang.String::startsWith (7 bytes) 1196 35 s! 3 sun.misc.URLClassPath::getLoader (154 bytes) 1199 44 3 java.util.HashMap::hash (20 bytes) 1199 46 3 java.util.HashMap::putVal (300 bytes) 1200 45 3 java.util.HashMap::put (13 bytes) 1200 47 3 java.lang.String::<init> (10 bytes)
可以看到大部分方法的编译级别是C1的级别3,有些方法比如 java.lang.String::indexOf
先经过级别3编译然后用C2进行编译。
方法属性的解释如下:
const char compile_type = is_osr_method ? ‘%’ : ‘ ‘;
const char sync_char = is_synchronized ? ‘s’ : ‘ ‘;
const char exception_char = has_exception_handler ? ‘!’ : ‘ ‘;
const char blocking_char = is_blocking ? ‘b’ : ‘ ‘;
const char native_char = is_native ? ‘n’ : ‘ ‘;
关于分层编译的一些要点:
- 开始都是解释执行
- 理想情况下应转成level3编译
- 根据C1队列长度和C1编译线程数来调整编译的阈值
- 根据C2队列长度可能转向C2编译
- 根据C2队列长度、C2编译线程数调整level4编译阈值
- 如果方法非常小,没什么可以优化的空间,直接转level1编译
- 最常见的编译层次转换:0 -> 3 -> 4
最后来看一下编译层次转换图:
JIT相关JVM参数简介
选项 | 默认值 | 解释 |
---|---|---|
CompileThreshold | 1000 or 1500/10000 | 编译阈值,方法执行多少次后进行编译 |
PrintCompilation | false | jit编译时输出日志 |
InitialCodeCacheSize | 160K (varies) | 初始codecache大小 |
ReservedCodeCacheSize | 32M/48M | codecache最大值 |
ExitOnFullCodeCache | false | codecache满了退出jvm |
UseCodeCacheFlushing | false | codecache满了时清空一半的codecache |
PrintFlagsFinal | false | 打印所有的jvm选项 |
PrintCodeCache | false | jvm退出时打印codecache |
PrintCodeCacheOnCompilation | false | 编译时打印codecache使用情况 |
如何解决Jit引起的load高
前面分析了这么多jit的相关知识,那针对这个场景怎么去解决前面出现的系统负载高的问题呢?根据JIT的特点,当方法执行次数到达某个阈值(server虚拟机默认是10000次)时会触发JIT编译,当应用的调用量很大时,大量的请求同时进入,多个方法同时触发JIT,会出现短期内load较高的情况,从而可能导致服务调用超时,因此问题出现具有随机性。我们决定采用提前触发编译的方法来解决服务发布后可能出现的load高的问题;具体做法就是在Web容器启动时循环调用热点方法,触发JIT编译,然后容器启动完成对外发布Rest服务。
这样做的缺点是侵入式,要增加代码,增加了启动时间,优点是实现成本较低,不影响业务。
具体实施方案
1)为了避免CodeCache满导致JIT停止编译或者CodeCacheFlushing的情况,先通过JMX获取到当前JIT的CodeCache大小(参考 hellojava 中的方法来获取),再通过 jinfo -flag ReservedCodeCacheSize javaPid
命令获取当前jvm的ReservedCodeCacheSize参数大小,根据实际情况调整ReservedCodeCacheSize的大小,最后调整之后我们在jvm启动脚本中加上了如下两个参数:
-XX:ReservedCodeCacheSize=512m -XX:-UseCodeCacheFlushing(禁用CodeCacheFlushing机制)
2) 编写预热代码
- 编写WarmUpContextListener实现Spring的ApplicationContextAware接口,确保在Web容器启动完成前,调用需要预热的方法;
- WarmUpContextListener读取预先配置好的参数,包括要调用的目标方法、请求参数、执行次数和超时时间;
- 新建线程池执行目标方法,执行N次触发JIT编译;
- 执行完成,关闭预热线程池;
- Web容器启动完成,对外发布服务。
经过以上两个步骤之后,我们的系统就没出现过因jit导致的负载高的场景。
在写这篇博客的过程中参考了以下资料,在此表示感谢。
- https://stackoverflow.com/questions/41134037/whats-this-new-column-in-xxprintcompilation-output
- https://stackoverflow.com/questions/35601841/how-does-the-jvm-decided-to-jit-compile-a-method-categorize-a-method-as-hot/35614237#35614237
- Code Cache满导致接口性能变慢的排查过程
- 一个RT下降的奇怪Case
以上所述就是小编给大家介绍的《JIT引起系统Load高分析》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 使用前端方式挑战 Chrome 小恐龙游戏高分
- 使用前端方式挑战 Chrome 小恐龙游戏高分
- SARscape中高分三号数据轨道纠正
- 基于 python + WebDriverAgent 的“跳一跳”小程序高分教程
- 冠军方案 | 第二届中国“高分杯”美丽乡村大赛(遥感图像)第一名总结
- GIMP 2.99.2 发布:UI 采用 GTK3 实现、原生支持高分屏和 Wayland
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。