JVM(4)解释器和即时编译器

1、虚拟机运行模式

java是一种解释性编程语言,在Hotspot实现中,提供了解释器和即时编译器,即时编译器能将热点代码编译为效率更高的机器代码,以提升执行效率,加快系统运行速度。

编译.png

hotspot运行模式配置:

  • 解释模式:可通过 -Xint 选项指定,让 JVM 以解释模式运行 Java 程序。
  • 编译模式:可通过 -Xcomp 选项指定,让 JVM 以编译模式运行 Java 程序。
  • 混合模式:可通过 -Xmixed 选项指定,让 JVM 以解释+编译模式运行 Java 程序,这也是 Hotspot 的默认模式.

2、解释器

系统启动时,解释器按照预定义的规则,为所有字节码分别创建能够在具体计算机平台上运行的机器码,并存放在特定位置。当运行时环境需要解释字节码时,就到指定位置取出相应的code,直接在机器上运行。

3、即时编译

解释性语言的优点是可移植性强,可以放在任何有解释器的机器上运行,但其运行性能较差。原因是解释器需要实时将指令解释为机器码运行,而且解释器未对指令做更好的优化。

编译器有足够的上下文信息、统计信息等,可以对代码进行更好的优化。

3.1、编译器类型

编译器类型有client(C1编译器)和server(C2编译器)两种类型。两种编译器的主要差别是编译代码的时机不同。client编译器在代码执行的开始阶段,其编译速度比server编译器块,而编译的代码比server编译的多。server编译器在编译的时候会更好地进行优化。

一种列外是分层编译,在虚拟机启动时由client快速编译,随着代码变热后使用server编译器重新编译,以提高系统的整体性能。

编译器版本:

  • -client:32位client编译器
  • -server:32位server编译器
  • -d64:64位server编译器
  • -XX:+TieredCompilation:分层编译器,其使用server编译器。

3.2、即时编译器版本选择

  • 32位系统:必须选用32位的JVM,即client编译器;
  • 64位系统:可选32位或64位。如果内存小于3GB,32位编译器更快,占用内存更少。因为虚拟机指针只有32位,所占内存空间少,而且操作代价也少于64位的指针。32位编译器的缺点是内存最多只能为4GB。而且还包括堆、永久带、本地代码及JVM所使用的本地内存。另外,32位编译器无法使用64位CPU的寄存器,无法进行寄存器相关的优化,故大量使用long或double变量的应用会比较慢。在 32 位 JVM 上运行的程序、只要与 32 位寻址空间吻合,无论机器是 32 位还是 64 位,都要比在类似配置的 64 位 JVM 上运行时快 5 %到 20 %。

3.3、代码缓存

虚拟机编译时,会在代码缓存中保留编译之后的汇编语言指令集。代码缓存的大小固定,一旦填满,虚拟机就不能编译更多代码了。故若代码缓存过小,只有少部分热点代码被编译执行,其他的则没有,最终会导致大部分代码都是解释运行,系统性能较差。

以上情况在client或分层编译的情况下很常见。在使用常规server编译器时,因通常只有少量类会被编译,所以代码缓存不太可能被填满。而在client或分层编译时,可被编译的类可能会非常多。

参数 说明
-XX:CodeCacheExpansionSize 配置CodeCache空间扩展大小的参数
-XX:InitialCodeCacheSize 配置CodeCache空间的初始值
-XX:ReservedCodeCache 配置CodeCache空间的最大值
-XX:PrintCodeCache 退出时输出CodeCache信息

3.4、编译阈值

触发代码编译的主要因素是代码执行的频度,当执行达到一定次数,且到达编译阈值,编译器就可获得足够的信息进行代码编译了。

编译时基于两种计数器:方法调用计数器和方法中循环回边计数器。回边为循环执行的次数。

虚拟机在执行某个方法时,会检查方法中的两种计数器总数,然后判断是否需要编译。需要编译则将该方法放入编译队列,进行标准编译。

标准编译由 -XX:CompileThreshold=N标志触发,N=回边计数器+方法调用次数。使用client编译器时,N默认值为1500,使用server编译器时为10000。

实际上每种计数器的值都会周期性减少,计数器只是方法或循环最新热度的度量。

3.5、编译线程

编译队列中的任务是被后台多个线程处理的。编译队列并不遵循先进先出的原则,而是根据调用次数的多少作为优先级。故当在出现开始执行并有大量代码需要编译时,最重要、执行次数最多的代码会被优先编译。

当使用 client 编译器时, JVM 会开启一个编译线程;使用 Server编译器时,JVM 会开启两个这样的线程。当启用分层编译时, JVM 默认开启多个 client 和 server 线程,线程数依据一个略复杂的等式而定,包括目标平台 CPU 数取双对数之后的数值。

编译器的线程数( 3 种编译器都是如此)可通过 -xx:CICompilerCount=N 标志来设置。这是JVM 处理队列的线程总数;对分层编译来说,其中三分之一(至少一个)将用来处理client 编译器队列,其余的线程(至少一个)用来处理 server编译器队列。

3.6、内联

编译器所做的最重要的优化方法内联。面向对象设计都会有很多getter和setter方法,此类方法调用开销很大,特别是相对于方法的代码量而言。当前的JVM都会用内联代码的方式执行这些方法。

内联是默认开启的,可通过 -xx:-InLine关闭,然而其对性能影响巨大,最好不要关闭。

方法是否内联取决干它有多热以及它的大小。 JVM 依据内部计算来判定方法是否是热点(譬如,调用很频繁);是否是热点并不直接与任何调优参数相关。如果方法因调用频繁而可以内联,那只有在它的字节码小于 325 字节时(或 -XX:MaxFreqInlineSize = N 所设定的任意值)才会内联。否则,只有方法很小时,即小于 35 字节(或 -xx : MaxlnlineSize = N 所设定的任意值)时才会内联。

有时你会看到增加MaxInlineSize 的值以便内联更多方法的建议。两者之间常被忽略的是,MaxInlineSize 超过35意味着第一次调用方法是就会被内联。然而,方法只有经常被调用时—在这种情况下它的性能会受更大影响― 最终才值得内联(假定它的大小小于 325 字节)。否则,MaxInlineSize调用的最终结果就是减少了热身测试所需要的时间,但不太可能对长期运行的程序产生重大影响。

3.7、逃逸分析

如果开启逃逸分析(-XX:DoEscapeAnalysist,默认为true),server编译器会执行一些非常激进的优化措施。

4、分层编译级别

当使用分层编译时,编译日志中会输出代码所编译的分层级别。

共5种编译级别:

  • 0级:CompLevel_none,采用解释器解释执行,不采集性能监控数据,可以升级到1级;
  • 1级:CompLevel_simple,采用C1编译器,会把热点代码迅速的编译成本地代码,如果需要可以采用性能数据;
  • 2级:CompLevel_limited_profile,采用C2编译器,进行更好的优化,甚至可能根据第1级采集的性能数据采取激进的优化措施。
  • 3级:CompLevel_full_profile,采用C1编译器,采集性能数据进行优化措施;
  • 4级:CompLevel_full_optimization,采用C2编译器,进行完全的优化;

多数方法第一次编译级别是3,即完全C1编译,如果方法运行的足够频繁,它就会编译成4。

影响编译策略的因素有两个:

  • C2 队列的长度决定了下一个等级.据观察,第 2 级比第 3 级快约 30 % ,因此我们需要将一个 Java 方法花费在第 3 级上的时间尽可能地最小化.所以,若 C2队列很长,直接选择第 3 级会导致排队,直到所提交的 C2 编译请求通历整个队列.因此此时较为明智的做法是先使用第 2 级,待 C2 负载回落,再启动第 3 级重新编译并开始收集性能数据。
  • C1 队列的长度用来动态调整阈值,从而在编译器过载时引入额外的过滤.

如果server编译器队列满了,就会从 server 队列中取出方法,以级别 2 进行编译,在这个级别上, C1 编译器使用方法调用计数器和回边计数器(但不需要性能分析的反馈信息)。这使得方法编译得更快,而方法也将在 C1 编译器收集分析信息、之后被编译为级别 3 ,最终当 server 编译器队列不太忙的时候被编译为级别 4 。

另一方面,如果client 编译器全忙,原本排程在级别 3 编译的方法就既可以等待级别 3 编译,也适合进行级别 4 的编译。在这种情况下,方法编译会很快转到级别 2 ,然后由级别 2 转到级别 4 。

那些不太重要的方法可以从级别 2 或级别3 开始编译,但随后会因为它们的重要性没那么高而转为级别 1 。另外,如果 Server 编译器出于某些原因无法编译代码,也会转为级别 1。当然,代码在逆编译时会转为级别0 。

有些标志可以控制某些级别转换行为,但调优能够得到很乐观的结果。当方法按期望的顺序,即级别 0 —>级别 3 —>级别 4 编译时,性能可以达到最优。如果方法经常被编译为级别 2 ,并且还额外有可用的 CPU 周期,那就可以考虑增加编译器的线程数,从而减少 server编译器队列的长度。如果没有额外可用的 CPU 周期,那你唯一能做的就是尽力减小应用的大小。

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

推荐阅读更多精彩内容