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. Fiddler使用三(Fiddler内置命令)

    参考:http://blog.csdn.net/ohmygirl/article/details/17855031 一. Fiddler内置命令. 上一节使用Fiddler进行抓包分析中,介绍到,在w ...

  2. Cucumber capybara 每个Scenario登陆一次

    hook.rb中添加: After do |scenario| Capybara.current_session.instance_variable_set(:@touched, false)end ...

  3. 【.Net】 【C++】容器类型对照

    C# 中主要有两类容器:一个是 System.Array 类(参阅:http://msdn.microsoft.com/library/default.asp?url=/library/en-us/c ...

  4. 完美解决ExtJs6上传中文文件名乱码,后端SpringMVC

    ExtJs上传中文文件名乱码,观察请求. ExtJs6上传乱码从后台无法解决,因为文件名请求里面就已经乱码了,后台无法解码. 除非请求参数正确没有乱码,后台因为编码设置不一样,可以通过后台处理乱码 这 ...

  5. step1: python & scrapy安装

    #首先安装python,这里安装python所需依赖包yum install zlib-devel bzip2-devel openssl-devel ncurses-devel sqlite-dev ...

  6. Truncated incorrect DOUBLE value: 'NO_REFUND'

    解决办法:Mysql中,如果一个字段是字符串,则一定要加单引号 问题原因: `item_refund_state` ) NOT NULL item_refund_state字段的类型是varchar但 ...

  7. MYSQL常用函数以及分组操作

    SELECT CONVERT(",SIGNED); SELECT CAST(" AS SIGNED); SELECT ; SELECT LENGTH("姜浩真帅!&quo ...

  8. System.Web.Mvc.HtmlHelper<dynamic>”没有名为“Partial”的适用方法,但似乎有一个具有该名称的扩展方法。扩展方法不能进行动态调度。请考虑强制转换动态参数,或调用该扩展方法但不使用扩展方法语法。

    MVC 调用分布式图,传了没有定义的参数,,参数写得不对

  9. 用 Redis Desktop Manager 远程连接 redis 数据库。

    环境: 本机OS:window 10(本机没有安装redis) redis 服务器:centos 7 使用 Redis Desktop Manager 工具远程连接 redis. Redis Desk ...

  10. [javaEE] 三层架构案例-用户模块(二)

    使用junit测试框架,测试查找用户和添加用户功能 com.tsh.test.xmlUserDaoTest package com.tsh.test; import org.junit.Test; i ...