1、概述

Java程序最初是通过解释器(Interpreter)进行解释执行的,当虚拟机发现某个方法或代码块的运行特别频繁时,就会把这些代码认定为“热点代码”(Hot Spot Code)。

为了提高热点代码的执行效率,在运行时,虚拟机将会把这些代码编译成与本地平台相关的机器码,并进行各种层次的优化,完成这个任务的编译器称为即时编译器(Just In Time Compiler,下文中简称JIT编译器)。

2 HotSpot虚拟机内的即时编译器

2.1、解释器和编译器

解释器的优势:快速启动和执行,节约内存。

编译器的优势:更高的执行效率。

HotSpot虚拟机中内置了两个即时编译器,分别称为Client Compiler和Server Compiler,或者简称为C1编译器和C2编译器。

用户可以使用参数“-Xint”强制虚拟机运行于“解释模式”(Interpreted Mode) ,也可以使用参数“-Xcomp”强制虚拟机运行于“编译模式”(Compiled Mode)

2.2、分层编译

分层编译根据编译器编译、 优化的规模与耗时,划分出不同的编译层次,其中包括:
第0层,程序解释执行,解释器不开启性能监控功能(Profiling),可触发第1层编译。
第1层,也称为C1编译,将字节码编译为本地代码,进行简单、 可靠的优化,如有必要将加入性能监控的逻辑。
第2层(或2层以上),也称为C2编译,也是将字节码编译为本地代码,但是会启用一些编译耗时较长的优化,甚至会根据性能监控信息进行一些不可靠的激进优化。

解释器还可以作为编译器激进优化时的一个“逃生门”

2.3、编译对象与触发条件

在运行过程中会被即时编译器编译的“热点代码”有两类,即:
  被多次调用的方法。
  被多次执行的循环体  --  仍然会编译整个方法

热点探测方法:

基于采样的热点探测(Sample Based Hot Spot Detection) :采用这种方法的虚拟机会周期性地检查各个线程的栈顶,如果发现某个(或某些)方法经常出现在栈顶,那这个方法就是“热点方法”。 容易受干扰。

基于计数器的热点探测(Counter Based Hot Spot Detection): 麻烦,但精确。

HotSpot虚拟机中使用的是第二种——基于计数器的热点探测方法 ,因此它为每个方法准备了两类计数器:方法调用计数器(Invocation Counter)和回边计数器(Back Edge Counter)。 当计数器超过阈值溢出了,就会触发JIT编译 。

方法调用计数器

当超过一定的时间限度,如果方法的调用次数仍然不足以让它提交给即时编译器编译,那这个方法的调用计数器就会被减少一半,

  这个过程称为方法调用计数器热度的衰减(Counter Decay),

  而这段时间就称为此方法统计的半衰周期(Counter Half Life Time)。

回边计数器:对代码段执行次数计数

它的作用是统计一个方法中循环体代码执行的次数[2],在字节码中遇到控制流向后跳转的指令称为“回边”(Back Edge)

回边计数器没有计数热度衰减的过程,因此这个计数器统计的就是该方法循环执行的绝对次数。

2.4、编译过程

这里就会用到很多优化手段,其中Client侧用的少些,Server侧用的多些。

优化方法后面讲,

还可以通过参数配置查看优化过程,但需要Debug版本的虚拟机。

3、编译优化技术

3.1 优化技术概览

3.2、方法内联(Method Inlining)

方法内联的重要性要高于其他优化措施,它的主要目的有两个,

  一是去除方法调用的成本(如建立栈帧等),

  二是为其他优化建立良好的基础,方法内联膨胀之后可以便于在更大范围上采取后续的优化手段,从而获取更好的优化效果。

因此,各种编译器一般都会把内联优化放在优化序列的最靠前位置。

方法内联的难点:虚方法。因为虚方法在编译时是无法确定指向的,而java又大量存在虚方法。

所以在许多情况下虚拟机进行的内联都是一种激进优化。

3.3、公共子表达式消除(Common Subexpression Elimination)

原理很简单,int d=(c * b)*12+a+(a+b * c);此表达式中,b*c即被公共子表达式。

3.4、数组边界检查消除(Array Bounds Checking Elimination)

解决问题:数组每次访问都去判断下标是否越界太耗性能。

思路:

1、把运行期检查提前到编译期完成,通过语义,断定不会异常的就不检查。

2、隐式异常处理,直接访问,然后捕获segment_fault,转化抛出该抛的异常。

3.5、逃逸分析(Escape Analysis)

并不是直接优化代码的手段,而是为其他优化手段提供依据的分析技术。

逃逸分析的基本行为就是分析对象动态作用域:

当一个对象在方法中被定义后,它可能被外部方法所引用,例如作为调用参数传递到其他方法中,称为方法逃逸。

甚至还有可能被外部线程访问到,譬如赋值给类变量或可以在其他线程中访问的实例变量,称为线程逃逸。

对未逃逸对象的优化方法:

1、栈上分配(Stack Allocation) ,可极大减小内存回收的压力。  -- HotSpot中暂时还没有做这项优化。

2、同步消除(Synchronization Elimination) :由于对象不会被其他线程使用,所以不需要同步锁了。

3、标量替换(Scalar Replacement) :把局部对象拆成多个局部标量,除了可以用栈存标量外,还可以做进一步优化。

标量(Scalar)是指一个数据已经无法再分解成更小的数据来表示了,Java虚拟机中的原始数据类型(int、 long等数值类型以及reference类型等)都不能再进一步分解,它们就可以称为标量。

相对的,如果一个数据可以继续分解,那它就称作聚合量(Aggregate),Java中的对象就是最典型的聚合量。

如果把一个Java对象拆散,根据程序访问的情况,将其使用到的成员变量恢复原始类型来访问就叫做标量替换。

逃逸分析的缺点:

不能保证逃逸分析的性能收益必定高于它的消耗。

如果要完全准确地判断一个对象是否会逃逸,需要进行数据流敏感的一系列复杂分析,从而确定程序各个分支执行时对此对象的影响。

这是一个相对高耗时的过程,如果分析完后发现没有几个不逃逸的对象,那这些运行期耗用的时间就白白浪费了,所以目前虚拟机只能采用不那么准确,但时间压力相对较小的算法来完成逃逸分析。

还有一点是,基于逃逸分析的一些优化手段,如上面提到的“栈上分配”,由于HotSpot虚拟机目前的实现方式导致栈上分配实现起来比较复杂,因此在HotSpot中暂时还没有做这项优化。

4、Java与C/C++的编译器对比

Java与C/C++的编译器对比实际上代表了最经典的即时编译器与静态编译器的对比。

JAVA的劣势:

1、即时编译占运行时间。

2、动态类型安全检查耗费时间。

3、虚方法的使用频率远远大于C++,多态选择的频率也就大。

4、动态扩展使得全局优化难以进行。

5、堆内存回收

Java的劣势虽多,但劣势都是为了换取开发效率上的优势而付出的代价。

Java的优势:

1、即时编译器可以在运行时根据动态状态做优化。如调用频率等。

【深入理解JAVA虚拟机】第4部分.程序编译与代码优化.2.运行期优化。这章提到的具体的优化技术,应该对以后做性能工作会有帮助。的更多相关文章

  1. 《深入理解java虚拟机》学习笔记之编译优化技术

    郑重声明:本片博客是学习<深入理解Java虚拟机>一书所记录的笔记,内容基本为书中知识. Java程序员有一个共识,以编译方式执行本地代码比解释方式更快,之所以有这样的共识,除去虚拟机解释 ...

  2. 深入理解java虚拟机-第十章-早期(编译期)优化

    第10章  早期(编译期)优化 javac编译过程: 1.解析与填充符号表过程 词法.语法分析 将源代码的字条流转变为标记(Token)集合.如“int a = b + 2”这名代码包含了6个标记,分 ...

  3. 深入理解Java虚拟机--下

    深入理解Java虚拟机--下 参考:https://www.zybuluo.com/jewes/note/57352 第10章 早期(编译期)优化 10.1 概述 Java语言的"编译期&q ...

  4. 深入理解Java虚拟机(自动内存管理机制)

    文章首发于公众号:BaronTalk 书籍真的是常读常新,古人说「书读百遍其义自见」还是很有道理的.周志明老师的这本<深入理解 Java 虚拟机>我细读了不下三遍,每一次阅读都有新的收获, ...

  5. 《深入理解Java虚拟机》第 3 版里面到底多了哪些知识点?本文竟然得到了本书作者的认可!

    这是why的第 47 篇原创文章 荒腔走板 大家好,我是 why.老规矩,先是简短的荒腔走板聊聊生活. 上面的图是前几天拍的,那天晚上下班后,刚刚走进小区就看到了这一轮弯月和旁边那一颗特别特别亮的星星 ...

  6. 深入理解Java虚拟机(程序编译与代码优化)

    文章首发于微信公众号:BaronTalk,欢迎关注! 对于性能和效率的追求一直是程序开发中永恒不变的宗旨,除了我们自己在编码过程中要充分考虑代码的性能和效率,虚拟机在编译阶段也会对代码进行优化.本文就 ...

  7. 《深入理解Java虚拟机》-----第10章 程序编译与代码优化-早期(编译期)优化

    概述 Java语言的“编译期”其实是一段“不确定”的操作过程,因为它可能是指一个前端编译器(其实叫“编译器的前端”更准确一些)把*.java文件转变成*.class文件的过程;也可能是指虚拟机的后端运 ...

  8. 深入理解JAVA虚拟机 程序编译和代码优化

    泛型类型擦除 C#中的泛型,不论是代码中,还是编译后,还是运行期,都是切实存在的.List<String>和List<Int>是两个截然不同的类型,有自己的虚方法表和类型数据, ...

  9. 《深入理解Java虚拟机》虚拟机性能监控与故障处理工具

    上节学习回顾 从课本章节划分,<垃圾收集器>和<内存分配策略>这两篇随笔同属一章节,主要是从理论+实验的手段来讲解JVM的内存处理机制.好让我们对JVM运行机制有一个良好的概念 ...

随机推荐

  1. IDEA里如何安装Python插件打造开发环境(图文详解)

    前言 python是一种功能强大和适用面很广的开发语言,在大数据应用和机器学习日益流行的年代,python凭借其简洁.易用和可扩展性获得很多用户的支持,近年来使用率高速增长.python环境下,集成了 ...

  2. css消除空白节点的方法

    在做配置页面的时候,出现一个现在现象,两个同样的div(外框尺寸也是一样的),div里面包含有三个小的div ,三个小的div宽度也是一样的,同为33.3%,但是出现奇怪现象的就是左边一个有滚动条,右 ...

  3. Why Isn't curr_items Decreasing When Items Expire?

    Why Isn't curr_items Decreasing When Items Expire?

  4. JS实现队列

    JS实现队列: 队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表.进行插入操作的端称为队尾 ...

  5. [javaSE] java上传图片给PHP

    java通过http协议上传图片给php文件,对安卓上传图片给php接口的理解 java文件: import java.io.DataOutputStream; import java.io.File ...

  6. IE 8兼容:<meta http-equiv="X-UA-Compatible" content="IE=edge" /> X-UA-Compatible的解释

    前言:9月份开始了,大四也真正的到来了.深知自己网页布局还有很大的缺陷,接下来打算从工作中抽时间出来模仿着一些互联网公司的网站.顺便把基础理论知识打好. 第一个目标:小米官网:后续会把练习的项目放到g ...

  7. javascript刷新页面的集中办法

    1. history.go(0) 2. location.reload() 3. location=location 4. location.assign(location) 5. document. ...

  8. jquery解析XML在IE7下不兼容的问题

    jquery在解析XML内容的时候在IE7下无法显示,是因为数据格式的问题,解决办法如下: $.ajax({        type:"POST",        url:&quo ...

  9. opencv 从摄像头中读取视频并保存(c++版)

    原文:http://blog.csdn.net/zhongshijunacm/article/details/68947890 OpenCV中的视频操作函数如下表所列: VideoCapture Vi ...

  10. C语言字符串操作函数 - strcpy、strcmp、strcat、反转、回文

    原文:http://www.cnblogs.com/JCSU/articles/1305401.html C语言字符串操作函数 1. 字符串反转 - strRev2. 字符串复制 - strcpy3. ...