晚期(运行期)优化

JIT:Java程序最初是由解释器解释执行的,当虚拟机发现某个方法或代码块的运行特别频繁时,就会把这些“热点代码”编译成机器码,提高运行效率。

1. 为什么要使用解释器和编译器并存?

  • 当需要程序快速启动和执行的时候,可以使用解释器,省去编译时间,立即执行。随着时间推移,编译器逐渐发挥作用,编译为机器码,可以提高效率。
  • 内存限制较大时,解释器执行可以节约内存,反之编译器执行可以提高效率。
  • 解释器可以作为编译器激进优化的逃生门。

2. HotSpot的两个不同的即时编译器

  • Client Compiler:C1编译器
  • Server Complier:C2编译器
  1. interpreted mode:纯解释模式
  2. compiled mode:纯编译模式,无法编译是解释器还是会介入的
  3. mixed mode:解释器和编译器搭配

要想编译出优化程度高的代码,需要时间成本,所以虚拟机为了权衡,采用了分层编译。采用分层编译后,C1和C2同时工作,C1获得更高的编译速度,C2获得更好的编译质量。

  • 第0层:程序解释执行,解释器不开启性能监控,可触发第1层编译。
  • 第1层:C1编译,简单、可靠的优化,必要时加入性能监控。
  • 第2层:启动一些编译耗时较长的优化,甚至根据性能监控信息进行不可靠的激进优化。

3. 编译对象和触发条件

热点代码

  1. 被多次调用的方法:整个方法为编译对象,虚拟机标准的JIT编译方式。
  2. 被多次执行的循环体。虽然循环在方法体内,但还是以整个方法为编译对象,这种编译方式为栈上替换,因为发生在方法执行过程中,方法帧还在栈上。

热点探测

  1. 基于采样的热点探测:虚拟机周期性检查各个线程的栈顶,如果某个方法经常出现在栈顶,说明是热点。优点:简单高效、容易获取方法调用关系。缺点:很难精确确认一个方法的热度,容易受到线程阻塞或别的干扰。
  2. 基于计数器的热点探测:虚拟机为每个方法甚至代码块建立计数器,统计方法执行次数。优点:精确。缺点:成本高。

HotSpot使用的是第二种方法。它有两类计数器:方法调用计数器和回边计数器。

方法调用计数器:统计方法被调用的次数,默认阈值Client下1500次,Server下10000次。

如果不做任何设置,方法调用计数器统计的不是方法调用的绝对次数,而是一段时间内的次数,超过一定时间后,计数器会减半。这种衰减实在GC时顺便进行的。可以用-XX:CounterDecay设置是否衰减,如果不衰减,那么随着时间的推移,总会达到次数进行编译。

回边计数器:统计一个方法中循环体执行的次数。准确的说是回边次数,空循环不会回边,只跳转到自己。
client模式阈值:方法调用计数器阈值OSR比率/100,OSR比率默认933。
server模式阈值:方法调用计数器阈值
(OSR比率 - 解释器监控比率)/100,OSR比率默认140,解释器监控比率默认33。

回边计数器没有热度衰减。

4. 一些编译技术

  • 语言无关的经典优化技术之一:公共子表达式消除。
  • 语言相关的经典优化技术之一:数组范围检查消除。
  • 最重要的优化技术之一:方法内联。
  • 最前沿的优化技术之一:逃逸分析。

公共子表达式消除:如果一个表达式前面已经计算过了,后面表达式的变量也没有变化过,这个表达式就是公共子表达式,就没有必要计算了。

//javac不会作任何优化,但是JIT会优化b * c
int d = (c * b) * 12 + a + +(a + b * c)

数组边界检查消除:Java在访问数组元素时会自动进行上下界的范围检查,越界则抛出ArrayIndexOutOfBoundsException,但是这也是一种性能负担。虚拟机根据情况在编译器判断是否可能越界,如果不越界执行时就不需要检查了。
类似情况还有NullPointException,除数为0异常等。

方法内联:编译器最重要的优化手段之一,消除了方法调用的成本,还为其它优化建立了基础。

  • 方法内联不是代码复制那么简单,因为Java的方法(除了编译期解析的),编译期都不能确定版本,运行期才可以。采用“类型继承关系分析CHA”解决这一问题。

类型继承关系分析CHA:基于整个应用,确定目前已加载的类中,某个接口是否有多于一种实现,某个类是否有子类,子类是否为抽象类等信息。

  1. 编译器进行内联时,如果是非虚方法,那么直接内联。如果遇到虚方法,则查询CHA是否有多个版本,如果只有一个,那么进行内联(激进的,需要逃生门)。如果后续虚拟机没有加载其它类改变继承关系,则一直内联,否则退回解释状态,或重新编译。
  2. 如果CHA查询出多个版本,编译器会使用内联缓存。在未发生调用前,缓存为空,发生调用后,缓存记录下方法版本信息,以后每次调用都比较版本,如果一直,内联继续,如果不一致,取消内联。查找虚方法表。

逃逸分析:分析对象的动态作用域。不是代码优化手段,而是为其它手段提供依据。

  • 方法逃逸:一个对象被外部方法引用,如传参。
  • 线程逃逸:对象被外部线程访问到,如赋值给类变量。

如果证明一个对象不会逃逸,那么可以进行一些高效的优化。

栈上分配:一般对象都在堆上分配,各个线程共享,GC回收内存需要耗费时间。如果一个对象确定不会逃逸出方法,比如局部变量,那么分配在栈上就很舒服,可以随栈帧出栈而销毁。GC压力减小。

同步消除:如果一个对象确定不是线程逃逸,那么就不会被其它线程访问,就不存在竞争,完全可以消除同步。

标量替换:如果一个对象确定不会被外部访问,那么真正执行的时候就不需要创建这个对象,改为在栈上创建对象拆散后的标量。

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

推荐阅读更多精彩内容