JIT引起系统Load高分析

栏目: Java · 发布时间: 6年前

内容简介:JIT引起系统Load高分析

有个应用在启动的初期系统的load会飚的很高,导致监控系统报了告警;经过分析是JVM启动后对热点代码做JIT及时编译导致CPU使用率很高,而此时Jetty容器收到了大量的请求但是系统处理请求较慢导致业务的线程池满出现异常,这篇文章主要分析load高的可能原因以及JIT编译的常见优化选项。

load高的可能原因

  1. 长时间执行了耗费cpu的操作(usr使用率较高),比如死循环执行某个计算操作
  2. 等待竞争资源(比如锁、IO)致使请求处理慢,执行时间变长(sys占比高)
  3. 运行线程数见多,频繁上下文切换导致单个任务处理时间变长(sys占比高)
  4. 有大流量进入导致load身高

如何排查CPU占用升高的原因

一般从以下三个方面考虑:

  1. 业务代码执行,比如业务代码写了不合理的循环
  2. 框架和中间件的初始化过程
  3. 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,根据当前的使用情况下一步采取的编译措施可能是如下几种:

  1. 继续解释执行(可能编译线程负载很高)
  2. 解释器开始采样分析
  3. 根据分析数据用C1的层次3进行编译,后续可以继续优化
  4. 不分析直接用C1的层次2进行编译,后续再优化的可能性较低
  5. 不需要任何信息直接用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引起系统Load高分析

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) 编写预热代码

  1. 编写WarmUpContextListener实现Spring的ApplicationContextAware接口,确保在Web容器启动完成前,调用需要预热的方法;
  2. WarmUpContextListener读取预先配置好的参数,包括要调用的目标方法、请求参数、执行次数和超时时间;
  3. 新建线程池执行目标方法,执行N次触发JIT编译;
  4. 执行完成,关闭预热线程池;
  5. Web容器启动完成,对外发布服务。

经过以上两个步骤之后,我们的系统就没出现过因jit导致的负载高的场景。

在写这篇博客的过程中参考了以下资料,在此表示感谢。


以上所述就是小编给大家介绍的《JIT引起系统Load高分析》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

浅薄

浅薄

[美] 尼古拉斯·卡尔 / 刘纯毅 / 中信出版社 / 2010-12 / 42.00元

《浅薄:互联网如何毒化了我们的大脑》在我们跟计算机越来越密不可分的过程中,我们越来越多的人生体验通过电脑屏幕上闪烁摇曳、虚无缥缈的符号完成,最大的危险就是我们即将开始丧失我们的人性,牺牲人之所以区别于机器的本质属性。——尼古拉斯•卡尔“谷歌在把我们变傻吗?”当尼古拉斯•卡尔在发表于《大西洋月刊》上赫赫有名的那篇封面文章中提出这个问题的时候,他就开启了人们热切渴望的期盼源泉,让人急于弄清楚互联网是在......一起来看看 《浅薄》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具