你的Java代码对JIT编译友好么?

JIT编译器是Java虚拟机(以下简称JVM)中效率最高并且最重要的组成部分之一。但是很多的程序并没有充分利用JIT的高性能优化能力,很多开发者甚至也并不清楚他们的程序有效利用JIT的程度。

在本文中,我们将介绍一些简单的方法来验证你的程序是否对JIT友好。这里我们并不打算覆盖诸如JIT编译器工作原理这些细节。只是提供一些简单基础的检测和方法来帮助你的代码对JIT友好,进而得到优化。

JIT编译的关键一点就是JVM会自动地监控正在被解释器执行的方法。一旦某个方法被视为频繁调用,这个方法就会被标记,进而编译成本地机器指令。这些频繁执行的方法的编译由后台的一个JVM线程来完成。在编译完成之前,JVM会执行这个方法的解释执行版本。一旦该方法编译完成,JVM会使用将方法调度表中该方法的解释的版本替换成编译后的版本。

Hotspot虚拟机有很多JIT编译优化的技术,但是其中最重要的一个优化技术就是内联。在内联的过程中,JIT编译器有效地将一个方法的方法体提取到其调用者中,从而减少虚方法调用。举个例子,

publicintadd(intx,inty) {returnx + y;}intresult = add(a, b);

当内联发生之后,上述代码会变成

int result=a + b;

上面的变量a和b替换了方法的参数,并且add方法的方法体已经复制到了调用者的区域。使用内联可以为程序带来很多好处,比如

不会引起额外的性能损失

减少指针的间接引用

不需要对内联方法进行虚方法查找

另外,通过将方法的实现复制到调用者中,JIT编译器处理的代码增多,使得后续的优化和更多的内联成为可能。

内联取决于方法的大小。缺省情况下,含有35个字节码或更少的方法可以进行内联操作。对于被频繁调用的方法,临界值可以达到325个字节。我们可以通过设置-XX:MaxInlineSize=#

选项来修改最大的临界值,通过设置‑XX:FreqInlineSize=#选项来修改频繁调用的方法的临界值。但是在没有正确的分析的情况下,我们不应该修改这些配置。因为盲目地修改可能会对程序的性能带来不可预料的影响。

由于内联会对代码的性能有大幅提升,因此让尽可能多的方法达到内联条件尤为重要。这里我们介绍一款叫做Jarscan的工具来帮助我们检测程序中有多少方法是对内联友好的。

Jarscan工具是分析JIT编译的JITWatch开源工具套件中的一部分。和在运行时分析JIT日志的主工具不同,Jarscan是一款静态分析jar文件的工具。该工具的输出结果格式为CSV,结果中包含了超过频繁调用方法临界值的方法等信息。JITWatch和Jarscan是AdoptOpenJDK工程的一部分,该工程由Chris

Newland领导。

在使用Jarscan并得到分析结果之前,需要从AdoptOpenJDK Jenkins网站下载二进制工具

7 工具

运行很简单,如下所示

./jarScan.sh

上面产生的报告对于开发团队的开发工作很有帮助,根据报告结果,他们可以查找程序中是否包含了过大而不能JIT编译的关键路径方法。上面的操作依赖于手动执行。但是为了以后的自动化,可以开启Java的-XX:+PrintCompilation

选项。开启这个选项会生成如下的日志信息:

371java.lang.String::hashCode(67bytes)1242s!java.lang.ClassLoader::loadClass(58bytes)

其中,第一列表示从进程启动到JIT编译发生经过的时间,单位为毫秒。第二列表示的是编译id,表明该方法正在被编译(在Hotspot中一个方法可以多次去优化和再优化)。第三列表示的是附加的一些标志信息,比如s代表synchronized,!代表有异常处理。最后两列分别代表正在编译的方法名称和该方法的字节大小。

关于PrintCompilation输出的更多细节,Stephen Colebourne写过一篇博客文章详细介绍日志结果中各列的具体含义,感兴趣的可以访问这里阅读。

PrintCompilation的输出结果会提供运行时正在编译的方法的信息,Jarscan工具的输出结果可以告诉我们哪些方法不能进行JIT编译。结合两者,我们就可以清楚地知道哪些方法进行了编译,哪些没有进行。另外,PrintCompilation选项可以在线上环境使用,因为开启这个选项几乎不会影响JIT编译器的性能。

但是,PrintCompilation也存在着两个小问题,有时候会显得不是那么方便:

输出的结果中未包含方法的签名,如果存在重载方法,区分起来则比较困难。

Hotspot虚拟机目前不能将结果输出到单独的文件中,目前只能是以标准输出的形式展示。

上述的第二个问题的影响在于PrintCompilation的日志会和其他常用的日志混在一起。对于大多数服务器端程序来说,我们需要一个过滤进程来将PrintCompilation的日志过滤到一个独立的日志中。最简单的判断一个方法否是JIT友好的途径就是遵循下面这个简单的步骤:

确定程序中位于要处理的关键路径上的方法。

检查这些方法没有出现在Jarscan的输出结果中。

检查这些方法确实出现在了PrintCompilation的输出结果中。

如果一个方法超过了内联的临界值,大多数情况下最常用的方法就是讲这个重要的方法拆分成多个可以进行内联的小方法,这样修改之后通常会获取更好的执行效率。但是对于所有的性能优化而言,优化之前的执行效率需要测量记录,并且需要需要同优化后的数据进行对比之后,才能决定是否进行优化。为了性能优化而做出的改变不应该是盲目的。

几乎所有的Java程序都依赖大量的提供关键功能的库。Jarscan可以帮助我们检测哪些库或者框架的方法超过了内联的临界值。举一个具体的例子,我们这里检查JVM主要的运行时库

rt.jar文件。

为了让结果有点意思,我们分别比较Java 7 和Java 8,并查看这个库的变化。在开始之前我们需要安装Java 7 和 Java8

JDK。首先,我们分别运行Jarscan扫描各自的rt.jar文件,并得到用来后续分析的报告结果:

$ ./jarScan.sh/Library/Java/JavaVirtualMachines/jdk1.7.0_71.jdk/Contents/Home/jre/lib/rt.jar> large_jre_methods_7u71.txt$ ./jarScan.sh/Library/Java/JavaVirtualMachines/jdk1.8.0_25.jdk/Contents/Home/jre/lib/rt.jar> large_jre_methods_8u25.txt

上述操作结束之后,我们得到两个CSV文件,一个是JDK 7u71的结果,另一个是JDK

8u25。然后我们看一看不同的版本内联情况有哪些变化。首先,一个最简单的判断验证方式,看一看不同版本的JRE中有多少对JIT不友好的方法。

$ wc -l large_jre_methods_*3684large_jre_methods_7u71.txt3576large_jre_methods_8u25.txt

我们可以看到,相比Java 7,Java 8

少了100多个内联不友好的方法。下面继续深入研究,看看一些关键的包的变化。为了便于理解如何操作,我们再次介绍一下Jarscan的输出结果。Jarscan的输出结果有如下3个属性组成:

"","",

了解了上述的格式,我们可以利用一些Unix文本处理的工具来研究报告结果。比如,我们想看一下Java 7 和 Java 8

这两个版本中java.lang包下哪些方法变得内联友好了:

$ cat large_jre_methods_7u71.txt large_jre_methods_8u25.txt| grep -i^\"java.lang | sort | uniq -c

上面的语句使用grep命令过滤出每份报告中以java.lang开头的行,即只显示位于包java.lang中的类的内联不友好的方法。sort | uniq

-c

是一个比较老的Unix小技巧,首先将讲行信息进行排序(相同的信息将聚集到一起),然后对上面的排序数据进行去重操作。另外本命令还会统计一个当前行信息重复的次数,这个数据位于每一行信息的最开始部分。让我们看一下上述命令的执行结果:

$ cat large_jre_methods_7u71.txt large_jre_methods_8u25.txt | grep -i ^\"java.lang | sort | uniq -c

2 "java.lang.CharacterData00","intgetNumericValue(int)",835

2 "java.lang.CharacterData00","inttoLowerCase(int)",1339

2 "java.lang.CharacterData00","inttoUpperCase(int)",1307

// ... skipped output

2 "java.lang.invoke.DirectMethodHandle","privatestaticjava.lang.invoke.LambdaForm makePreparedLambdaForm(java.lang.invoke.MethodType,int)",613

1 "java.lang.invoke.InnerClassLambdaMetafactory","privatejava.lang.Class spinInnerClass()",497

// ... more output ----

报告中,以2(这是使用了uniq -c 对相同的信息计算数量的结果)最为起始的条目说明这些方法在Java 7 和Java 8

中起字节码大小没有改变。虽然这并不能完全肯定地说明这些方法的字节码没有改变,但通常我们也可以视为没有改变。重复次数为1的方法有如下的情况:

a)方法的字节码已经改变。

b)这些方法为新的方法。

我们看一下以1开始的行数据

1"java.lang.invoke.AbstractValidatingLambdaMetafactory","voidvalidateMetafactoryArgs()",8641"java.lang.invoke.InnerClassLambdaMetafactory","privatejava.lang.Class spinInnerClass()",4971"java.lang.reflect.Executable","java.lang.StringsharedToGenericString(int,boolean)",329

上面三个对内联不友好的方法全部来自Java

8,因此这属于新方法的情况。前两个方法与lamda表达式实现相关,第三个方法和反射子系统中继承层级调整有关。在这里,这个改变就是在Java 8

中引入了方法和构造器可以继承的通用基类。

最后,我们看一看JDK核心库一些令人惊讶的特性:

$ grep -i ^\"java.lang.String large_jre_methods_8u25.txt

"java.lang.String","publicjava.lang.String[] split(java.lang.String,int)",326

"java.lang.String","publicjava.lang.String toLowerCase(java.util.Locale)",431

"java.lang.String","publicjava.lang.String toUpperCase(java.util.Locale)",439

从上面的日志我们可以了解到,即使是Java 8

中一些java.lang.String中一些关键的方法还是处于内联不友好的状态。尤其是toLowerCase和toUpperCase这两个方法居然过大而无法内联,着实让人感到奇怪。但是,这两个方法由于要处理UTF-8数据而不是简单的ASCII数据,进而增加了方法的复杂性和大小,因而超过了内联友好的临界值。

对于性能要求较高并且确定只处理ASCII数据的程序,通常我们需要实现一个自己的StringUtils类。该类中包含一些静态的方法来实现上述内联不友好的方法的功能,但这些静态方法既保持紧凑型又能到达内联的要求。

上述我们讨论的改进都是大部分基于静态分析。除此之外,使用强大的JITWatch工具可以帮助我们更好地优化。JITWatch工具需要设置-XX:+LogCompilation选项开启日志打印。其打印出来的日志为XML格式,而非PrintCompilation简单的文本输出,并且这些日志比较大,通常会到达几百MB。它会影响正在运行的程序(默认情况下主要来自日志输出的影响),因此这个选项不适合在线上的生产环境使用。

PrintCompilation和Jarscan结合使用并不困难,但却提供了简单且很有实际作用的一步,尤其是对于开发团队打算研究其程序中即时编译执行情况时。大多数情况下,在性能优化中,一个快速的分析可以帮助我们完成一些容易实现的目标。

关于作者

Ben Evans是jClarity公司的CEO,jClarity是一家致力于Java和JVM性能分析研究的创业公司。除此之外他还是London Java

Community的负责人之一并在Java Community Process Executive Committee有一席之地。他之前的项目有Google

IPO性能测试,金融交易系统,90年代知名电影网站等。

1、具有1-5工作经验的,面对目前流行的技术不知从何下手,需要突破技术瓶颈的可以加群。

2、在公司待久了,过得很安逸,但跳槽时面试碰壁。需要在短时间内进修、跳槽拿高薪的可以加群。

3、如果没有工作经验,但基础非常扎实,对java工作机制,常用设计思想,常用java开发框架掌握熟练的,可以加群。

4、觉得自己很牛B,一般需求都能搞定。但是所学的知识点没有系统化,很难在技术领域继续突破的可以加群。

5.群号656060703java高级开发群

6.阿里Java高级大牛直播讲解知识点,分享知识,上面五大专题都是各位老师多年工作经验的梳理和总结,带着大家全面、科学地建立自己的技术体系和技术认知!

查看英文原文:Is  Your Java Application Hostile to JIT Compilation?

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容

  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,560评论 18 399
  • 两年前阿里开源了Dexposed 项目,它能够在Dalvik上无侵入地实现运行时方法拦截,正如其介绍「enable...
    weishu阅读 4,553评论 3 42
  • 一:java概述:1,JDK:Java Development Kit,java的开发和运行环境,java的开发工...
    ZaneInTheSun阅读 2,627评论 0 11
  • 《深入理解Java虚拟机》笔记_第一遍 先取看完这本书(JVM)后必须掌握的部分。 第一部分 走近 Java 从传...
    xiaogmail阅读 5,056评论 1 34
  • 外面太阳挺好。车间里,有人在放肯尼基的萨克斯曲,回家。 挺意外的,因为他们经常放的,都是流行伤感类的歌曲。放纯音乐...
    上林叶阅读 411评论 1 0