HotSpot解释器和及时编译器

概述

       做为Java开发人员,我们编写的代码是以“.java”为文件后缀的,也就是常说的源码。源码在经过javac命令编译之后,就会生成一个对应“.class”文件,这个就是字节码文件。它为Java的一次编译,到处运行提供了基础。通过JVM的映射,同一份字节码文件,可以在不同的系统上运行,这里就得益于JVM的内部处理了,即:通过JVM屏蔽了底层硬件的差异。但实际上字节码文件还不能算编译完成,它是一个“半成品”,通过JVM内部的处理,才能够转换成处理器能够识别的指令代码。

       一般地将Java代码的编译分为两个阶段:前端编译和后端编译。前面说的class字节码文件就是前端编译的产物,后端编译最终的结果就是将其转换成计算机能够识别的机器语言。这里引用Hollis大神博客的一张图:
前后端编译过程.png

       这里不去深入探究具体的【词法分析】、【语法分析】、【语义分析】的具体工作原理,只要明白这些步骤是生成字节码文件所必要的校验过程就行。它们实际上就是对源代码进行各种语法层面以及语义层面的校验,以保证最终生成的字节码是标准的、没有语法和语义上的错误。

       通过上图可以看到,前端编译的最终产物是【中间代码】,这个中间代码所指的就是Java的字节码文件。因为代码到了这里还不具有被处理器运行的能力,还需要一层JVM的加工,将其与具体操作系统相关联,生成针对所处系统的机器语言代码,此时机器代码才具有被处理器识别并执行的能力。

解释器(Interpreter)

       解释器顾名思义就是解释代码用的,上面提到了前端编译的最终产物是中间代码,这时的中间代码不具有被处理器识别并执行的能力,解释器的职责就是将此时的字节码逐条解释成处理器能够识别的机器码指令。所以从这里也可以解释,为什么高级语言执行效率会这么慢,主要就是多了这个解释的过程,像Java、Python等执行效率都比较慢。所以通俗讲:解释器在Java中的作用充当了Java程序和计算机之间的“翻译官”角色,将计算机“看”不懂的语言翻译成它能够“看”懂并执行的机器语言。

及时编译器(JIT)

       因为纯粹靠解释器来工作,效率会很慢,JVM引入了及时编译器JIT(Just In Time)的概念,它可以加快Java中部分代码的执行效率。下面简单介绍一下它的工作内容:

       在我们日常编码中,经常会遇到有些方法执行频率很高,或者有些代码段执行频率很高(例如循环结构)。JVM会随着程序的不断执行,进行深入分析,将程序中那些执行频率比较高的方法或者代码段编译成机器码,然后将这段机器码缓存起来,这段代码也就是所谓的“热点”代码,HotSpot由此而得名。一旦代码被缓存,后续如果继续执行这段代码的时候,直接从缓存中获取这段机器码,就可以跳过前面的编译阶段,执行效率得到极大提高。请看下面这张图,很好地解释了JIT的工作原理:
JIT工作原理.png

       这里可能会有疑问:“既然缓存机器码执行效率那么高,为什么还会有热点代码判断?干脆全部进行缓存不是更好?”这实际上也是一种策略,因为有很多代码(大部分)都是之执行一次,如果这种代码也要进行缓存,完全是在浪费资源。而如果一段代码被多次执行,那么浪费这一次的编译就会换来后续非常客观的效率回报。

       这里还有一个概念--栈上替换(OSR) :在有的时候,可能某段循环执行的代码还在不停的循环中,如果在某次循环之后,计数器达到了某一阈值,这时JVM已经认定这段代码是热点代码,此时编译器会将这段代码编译成机器语言并缓存后,但是这段循环仍在执行,JVM就会把执行代码替换掉,那么等循环到下一次时,就会直接执行缓存的编译代码,而不需要必须等到循环结束才进行替换,这个就是所谓的栈上替换。

热点检测

想要进入JIT,就必须要通过热点代码的筛选,目前JVM的热点探测(Hot Spot Detection)主要有两种方式:

  • 基于采样的热点探测(Sample Based Hot Spot Detection):周期性地检测各个线程的栈顶,如果发现某个方法经常出现在栈顶,换句话说就是某个方法频繁被调用,导致频繁入栈和出栈,那么就可以认为这个方法是热点代码。它的缺点是无法精确确认一个方法的热度,容易受线程阻塞或别的原因干扰探测的准确性。

  • 基于计数器的热点探测(Counter Based Hot Spot Detection):这种探测方式会为每个方法,甚至是代码段添加两个计数器,用于统计执行的次数,它们一旦超过某种阈值,就判定为这段代码为热点代码。HotSpot虚拟机中采用的就是这种热点探测方法。这两个计数器分别是:方法计数器(用于统计方法调用次数)和回边计数器(统计for或者while的运行次数的计数器 )。

JIT运行模式

       JIT的运行模式有两种:client模式和server模式。这两种模式采用的编译器是不一样的,client模式采用的是代号为C1的轻量级编译器,特点是启动快,但是编译不够彻底;而server模式采用的是代号为C2的编译器,特点是启动比较慢,但是编译比较彻底,所以一旦服务起来后,性能更高。可以通过java -version命令查看当前系统中安装的jvm使用的是那种模式:

JavaVersion命令截图.png

       可以看到,我这里使用的是server模式。但是也可以看到后面有个【mixed mode】字样,这表示编译器和解释器采用的是混合模式,即:这两者会同时工作,根据具体的情况,采用不同的运行策略。

       另外,在jdk6u25之后,引入了分层编译的概念,它实际上就是一种渐进的编译策略,在系统运行初期,执行频率较高的代码采用C1编译器编译 ,随着时间的推移,执行频率较高的代码会再次被G2编译器编译,以达到最高的性能。分层编译实际上可以分为五层:

  • 第0层(解释层)启动:这一层主要是提供了一些比较关键性方法的性能,快速进入C1层。

  • 第1层(C1编译器):通过上一层提供的一些关键方法的性能信息来优化这些代码。本层不包含性能优化的信息。

  • 第2层:这一层仍然是基于C1编译器优化的结果来处理的,此时会有少数方法通过C1编译器的编译,在本层会为这些少数方法的调用次数和循环分支执行情况,收集它们的性能分析信息。

  • 第3层:这一层会得到C1编译器编译的所有方法以及对所有的性能优化信息

  • 第4层:这一层只对C2编译器有效。

优化

优化代码缓存

       提到优化,首先就不得不说缓存空间的大小,由于在运行期间,随着热点代码的不断缓存,可能会出现缓存空间不足的情况,此时一旦缓存空间被填满,后续的热点代码就无法进入,那么程序的性能必然会因此而下降,所以设置缓存空间的大小是很有必要的,目的是要最大程度保证热点代码都能被编译。并且编译器的选择不同,它的代码缓存大小也是不一样的,在OpenJDK中,如果是分层编译的话,代码缓存默认大小是240M,如果是不分层编译,缓存大小默认只有48M,这里可以使用 –XX:ReservedCodeCacheSize选项来增加缓存区的大小。另外还有一个:-XX:InitialCodeCacheSize 选项,它用来指定代码缓存空间的初始大小。

       代码缓存空间大小并不是可以无限增大的,假如JVM是32位,那么运行程序的大小就不能超过4GB,缓存内存也是包含在这个4GB空间内的,在具体应用场景中需要结合实际情况来选择最优的缓存空间大小。

优化编译阈值

       前面说过,对于热点代码的识别,主要就是通过方法的进入次数以及代码块的循环执行次数来确定,那么具体这个次数达到多少才能被认为是热点代码?这个值就是编译阈值。标准编译是被-XX:CompileThreshold 这个选项的值所触发的。client模式下它的默认值是1500,server模式下,默认值是10000,如果改变这里的阈值,会使得编译器在相对正常的情况下提前(或推迟)编译代码。改变阈值是比较流行的做法。

其他优化策略

       其他的方式基本都不是直接作用于编译器的设置上,而是一些分析手段,通过具体的内存情况分析,找出问题出现的原因,并且以此找到合理的解决方案。

  • 可以使用-XX:+PrintCompilation,它的默认值是false,添加该选项运行程序,可以打印出编译日志。

  • 也可以使用jstat命令查看一些可见性信息,如果程序启动时没有添加PrintCompilation选项,可以考虑使用此方法:

jstat命令使用.PNG

       当然还有一些其他的概念:内联、向量化、范围检查消除、循环展开等等,这些概念在还有待研究,这里就不做深入解释了。

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

推荐阅读更多精彩内容

  • 这篇文章是我之前翻阅了不少的书籍以及从网络上收集的一些资料的整理,因此不免有一些不准确的地方,同时不同JDK版本的...
    高广超阅读 15,413评论 3 83
  • JVM、Java编译器和Java解释器 java解释器就是把在java虚拟机上运行的目标代码(字节码)解释成为具体...
    禅与计算机程序设计艺术阅读 8,590评论 1 15
  • 原文:https://insistence.cnblogs.com/p/5901457.html 1. 什么是Ju...
    laosijikaichele阅读 791评论 0 2
  • 这是我去年画的,当时说是画装饰画,于是就用4开的素描纸用中性笔弄了两天给画出来的,当时觉得挺好玩的 还有两张白色的...
    木兰苡阅读 526评论 0 6
  • 思考:嫉羡,是指感觉不配得到某样东西,从而内心产生一种恨的感觉,而感恩,则是对得到东西持有一种尊重感。 婴儿时母乳...
    杨雪雪阅读 135评论 0 0