本文来说下JVM中的逃逸分析

文章目录

  • JIT
  • 逃逸分析
  • 同步消除
  • 标量替换
  • 栈上分配
  • 编译阈值
  • 本文小结

JIT

即时编译(Just-in-time Compilation,JIT)是一种通过在运行时将字节码翻译为机器码,从而改善字节码编译语言性能的技术。在HotSpot实现中有多种选择:C1、C2和C1+C2,分别对应client、server和分层编译。

  • C1编译速度快,优化方式比较保守;
  • C2编译速度慢,优化方式比较激进;
  • C1+C2在开始阶段采用C1编译,当代码运行到一定热度之后采用G2重新编译;

在1.8之前,分层编译默认是关闭的,可以添加-server -XX:+TieredCompilation参数进行开启


逃逸分析

逃逸分析并不是直接的优化手段,而是一个代码分析,通过动态分析对象的作用域,为其它优化手段如栈上分配、标量替换和同步消除等提供依据,发生逃逸行为的情况有两种:方法逃逸和线程逃逸。

  • 方法逃逸:当一个对象在方法中定义之后,作为参数传递到其它方法中;
  • 线程逃逸:如类变量或实例变量,可能被其它线程访问到;

如果不存在逃逸行为,则可以对该对象进行如下优化:同步消除、标量替换和栈上分配。


同步消除

线程同步本身比较耗,如果确定一个对象不会逃逸出线程,无法被其它线程访问到,那该对象的读写就不会存在竞争,则可以消除对该对象的同步锁,通过-XX:+EliminateLocks可以开启同步消除。


标量替换

标量替换

  • 标量是指不可分割的量,如java中基本数据类型和reference类型,相对的一个数据可以继续分解,称为聚合量;
  • 如果把一个对象拆散,将其成员变量恢复到基本类型来访问就叫做标量替换;
  • 如果逃逸分析发现一个对象不会被外部访问,并且该对象可以被拆散,那么经过优化之后,并不直接生成该对象,而是在栈上创建若干个成员变量;

通过-XX:+EliminateAllocations可以开启标量替换, -XX:+PrintEliminateAllocations查看标量替换情况。


栈上分配

故名思议就是在栈上分配对象,其实目前Hotspot并没有实现真正意义上的栈上分配,实际上是标量替换。

private static int fn(int age) {User user = new User(age);int i = user.getAge();return i;}

User对象的作用域局限在方法fn中,可以使用标量替换的优化手段在栈上分配对象的成员变量,这样就不会生成User对象,大大减轻GC的压力,下面通过例子看看逃逸分析的影响。

public class JVM {public static void main(String[] args) throws Exception {int sum = 0;int count = 1000000;//warm upfor (int i = 0; i < count ; i++) {sum += fn(i);}Thread.sleep(500);for (int i = 0; i < count ; i++) {sum += fn(i);}System.out.println(sum);System.in.read();}private static int fn(int age) {User user = new User(age);int i = user.getAge();return i;}
}class User {private final int age;public User(int age) {this.age = age;}public int getAge() {return age;}
}

分层编译和逃逸分析在1.8中是默认是开启的,例子中fn方法被执行了200w次,按理说应该在Java堆生成200w个User对象。

1、通过java -cp . -Xmx3G -Xmn2G -server -XX:-DoEscapeAnalysis JVM运行代码,-XX:-DoEscapeAnalysis关闭逃逸分析,通过jps查看java进程的PID,接着通过jmap -histo [pid]查看java堆上的对象分布情况,结果如下:


可以发现:关闭逃逸分析之后,User对象一个不少的都在堆上进行分配。

2、通过java -cp . -Xmx3G -Xmn2G -server JVM运行代码,结果如下:


可以发现:开启逃逸分析之后,只有41w左右的User对象在Java堆上分配,其余的对象已经通过标量替换优化了。

3、通过java -cp . -Xmx3G -Xmn2G -server -XX:-TieredCompilation运行代码,关闭分层编译,结果如下:


可以发现:关闭了分层编译之后,在Java堆上分配的User对象降低到1w多个,分层编译对逃逸分析还是有影响的。


编译阈值

即时编译JIT只在代码段执行足够次数才会进行优化,在执行过程中不断收集各种数据,作为优化的决策,所以在优化完成之前,例子中的User对象还是在堆上进行分配。

那么一段代码需要执行多少次才会触发JIT优化呢?通常这个值由-XX:CompileThreshold参数进行设置:

  • 使用client编译器时,默认为1500;
  • 使用server编译器时,默认为10000;

意味着如果方法调用次数或循环次数达到这个阈值就会触发标准编译,更改CompileThreshold标志的值,将使编译器提早(或延迟)编译。

除了标准编译,还有一个叫做OSR(On Stack Replacement)栈上替换的编译,如上述例子中的main方法,只执行一次,远远达不到阈值,但是方法体中执行了多次循环,OSR编译就是只编译该循环代码,然后将其替换,下次循环时就执行编译好的代码,不过触发OSR编译也需要一个阈值,可以通过以下公式得到。

-XX:CompileThreshold = 10000
-XX:OnStackReplacePercentage = 140
-XX:InterpreterProfilePercentage = 33
OSR trigger = (CompileThreshold * (OnStackReplacePercentage - InterpreterProfilePercentage)) / 100 = 10700

其中trigger即为OSR编译的阈值。那么如果把CompileThreshold设置适当小一点,是不是可以提早触发编译行为,减少在堆上生成User对象?我们可以进行通过不同参数验证一下:

1、-XX:CompileThreshold = 5000,结果如下:

2、-XX:CompileThreshold = 2500,结果如下:

3、-XX:CompileThreshold = 2000,结果如下:

4、-XX:CompileThreshold = 1500,结果如下:


在我的机器中,当设置到1500时,在堆上生成的User对象反而升到4w个,目前还不清楚原因是啥…

JIT编译在默认情况是异步进行的,当触发某方法或某代码块的优化时,先将其放入编译队列,然后由编译线程进行编译,编译之后的代码放在CodeCache中,CodeCache的大小也是有限的,通过-XX:-BackgroundCompilation参数可以关闭异步编译,我们可以通过执行java -cp . -Xmx3G -Xmn2G -server -XX:CompileThreshold=1 -XX:-TieredCompilation -XX:-BackgroundCompilation JVM命令看看同步编译的效果:在java堆上只生成了2个对象。

当然了,这是为了好玩而进行的测试,生产环境不要随意修改这些参数:

  • 热点代码的编译过程是有成本的,如果逻辑复杂,编程成本更高;
  • 编译后的代码会被存放在有大小限制的CodeCache中,如果CompileThreshold设置的太低,JIT会将一大堆执行不那么频繁的代码进行编译,并放入CodeCache,导致之后真正执行频繁的代码没有足够的空间存放;

本文小结

本文详细介绍了JVM中有关逃逸分析相关的知识与内容。

深入理解JVM逃逸分析相关推荐

  1. 通过实例理解 Go 逃逸分析

    本文转载自白明老师,这是中文社区里面最好.最全面的一篇关于逃逸分析的文章,写得非常好.既有理论.又有实践,引经据典,精彩至及. 翻看了一下自己的Go文章归档[1],发现自己从未专门写过有关Go逃逸分析 ...

  2. JVM 逃逸分析 (史上最全)

    对于JVM"逃逸分析" 特性,也是近年来大厂面试.高薪面试的常见面试题. 和逃逸分析有关的常见面试题: Java中的对象一定是在堆上分配的吗? 注:本文以 PDF 持续更新,最新尼 ...

  3. 通过实例理解Go逃逸分析

    翻看了一下自己的Go文章归档[1],发现自己从未专门写过有关Go逃逸分析(escape analysis)的文章.关于Go变量的逃逸分析,大多数Gopher其实并不用关心,甚至可以无视.但是如果你将G ...

  4. JVM逃逸分析(同步省略、标量替换、栈上分配)

    在Java的编译体系中,一个Java的源代码文件变成计算机可执行的机器指令的过程中,需要经过两段编译,第一段是把.java文件转换成.class文件.第二段编译是把.class转换成机器指令的过程. ...

  5. 深入分析JVM逃逸分析对性能的影响

    逃逸分析(Escape Analysis) 逃逸分析的基本行为就是分析对象动态作用域:当一个对象在方法中被定义后,它可能被外部方法所引用,称为方法逃逸.甚至还有可能被外部线程访问到,譬如赋值给类变量或 ...

  6. 【Task】- JVM逃逸分析等待学习任务

    Tiered Compilation Compressed Oops Zero-Based Compressed Oops Escape Analysis NUMA Collector Enhance ...

  7. Java - 深入理解Java中的逃逸分析

    在Java的编译体系中,一个Java的源代码文件变成计算机可执行的机器指令的过程中,需要经过两段编译,第一段是把.java文件转换成.class文件.第二段编译是把.class转换成机器指令的过程. ...

  8. 面试官:请你详细说说Go的逃逸分析【文末送福利】

    ‍ ‍ 这是我在8月份整理分享的一系列后端工程师求职面试相关的文章,知识脉络图如下: JAVA/GO/PHP 面试常问的知识点 DB:MySql PgSql Cache: Redis MemCache ...

  9. JVM【带着问题去学习 01】什么是JVM+内存结构+堆内存+堆内存参数(逃逸分析)

    1.是什么 (1) 基本概念:可运行 Java 代码的非真实计算机 ,包括一套字节码指令集.一组寄存器.一个栈.一个垃圾回器,堆和一个存储方法域.它运行在操作系统之上,与硬件没有直接的交互. (2) ...

最新文章

  1. Python学习--not语句
  2. MySQL排错工具perror
  3. html 5笔记:理解与学习
  4. 田沄(1980-),男,博士,中国工程院—清华大学联合博士后科研工作站博士后,北京师范大学副教授....
  5. mysql root用户可以同时几个人连接_PHP安全:MySQL的使用安全
  6. java awt文件上传_springMVC实现前台带进度条文件上传的示例代码
  7. 大气辐射示意简单图_地理笔记 | N21 自然地理——大气的组成与垂直分层
  8. mt4 谐波_MT4指标AB=CD Dashboard — AB = CD 谐波模式仪表盘指示器
  9. 安卓bochs安装linux教程,Ubuntu环境下安装Bochs
  10. 浙大PAT 1051
  11. MySQL数据导入导出方法与工具介绍
  12. 源文件如何一步步到可执行程序【程序员必修课】
  13. php php_sapi cli,php_sapi_name() cli
  14. java公路车组装教程_自行车DIY入门教程,图文展示自行车组装全过程。(原创图文,转载请注明出处)...
  15. Uniapp——生成二维码
  16. 10.Quartz 常用配置
  17. Linux平台卸载MySQL总结
  18. 斯坦福大学开放课程:编程方法.02/第二课练习:karel 跳墙
  19. PRD产品需求文档概要
  20. 快速提升pv秘籍,如何提升网站pv

热门文章

  1. vim 环境写 markdown 的插件推荐
  2. 探秘采云间:全链路数据处理工具直击传统DW/BI痛点
  3. Java基础—集合2Set接口和Map接口
  4. Dynamic CRM 2013学习笔记(四十二)流程5 - 实时/同步工作流(Workflow)用法图解...
  5. 架构师2月刊发布:解读Android、高效运维、API设计方法论
  6. 源码安装 MariaDB
  7. 一步步教你实现富文本编辑器(第四部分)
  8. Android4.4 及以下TextView,Button等控件使用矢量图报错
  9. 编程小技巧(一)——系统性能优化之多次数据库访问处理
  10. [Cocos2d-x]Cocos2d-x 3.2 学习笔记