JVM 技术专题 - 逃逸分析介绍
摘要
Java 中算是新颖而前言的优化技术,从 JDK1.6 才开始引入该技术。
逃逸分析
辅助优化
首先我们要认识到,逃逸分析并不是直接优化的技术,而是作为其他优化的依据。
分析对象动态作用域
方法逃逸
对象在方法中被定义,但却被方法以外的其他代码使用。如传参到其他方法中等可能导致此情况发生。
线程逃逸
一个对象由某个线程在方法中被定义,但却被其他线程访问。如类变量、公用的或有 get、set 方法的实例变量等
逃逸分析过程
数据流敏感的若干复杂分析,以确定程序各分支执行对目标对象影响,耗时较长。
JVM 配置
逃逸分析具体配置项如下:
开启逃逸分析(JDK8 中,逃逸分析默认开启。)
-XX:+DoEscapeAnalysis
关闭逃逸分析
-XX:-DoEscapeAnalysis
逃逸分析结果展示
-XX:+PrintEscapeAnalysis
对象优化
如果对象不会发生前述方法逃逸和线程逃逸情况(即完全不可能被别的方法和线程访问到的对象),JVM 可做以下优化:
栈上分配
普通对象在堆中分配,各线程共享。但有 GC 消耗。
当确定对象不会发生方法逃逸时,可在线程栈上分配对象。此时对象生命周期和方法相同,随栈帧出栈时即可销毁,不需要 GC 了。
同步消除
线程同步有性能消耗
锁消除:当确定对象不会发生线程逃逸时,可消除该对象不必要的同步操作(永不会竞争)。具体来说,JVM 在编译器运行时会扫描代码,当检查到那些不可能存在共享区竞争,但却有互斥同步的代码,直接将这样的多此一举的同步消除
锁粗化:JVM 针对那些反复在一段代码中对同一对象加锁的情况,将同步锁放在最外层包住这里面的多次同步锁,同时取消内部的同步锁
JVM 配置
(JDK8 中,同步消除默认开启。)
-XX:+EliminateLocks
标量替换
标量
标量指无法分解的数据,如 java 中的基本数据类型及引用类型
聚合量
可以分解的,成为聚合量,如对象
如果一个可拆分对象不会发生逃逸,那在程序执行时并不创建他,而是根据情况在线程栈上只创建用到的成员标量
原始对象标量替换后,往往可以只创建所需标量,节约了空间和时间。
此外,JVM 可将这类成员变量放在栈上,乃至移动到高速寄存器中进行读写,大大提升读写效率。
还可以进一步优化。
截止 JDK8,栈上直接分配对象并未实现,而是将对象标量替换后在栈上分配。
JVM 配置
开启标量替换(JDK8 中,逃逸分析默认开启。)
-XX:+EliminateAllocations
查看标量替换详情
-XX:+PrintEliminateAllocations
配置命令
关闭逃逸分析
java -Xmx1G -XX:-DoEscapeAnalysis -XX:+PrintGCDetails -cp /Users/chengc/cc/work/projects/javaDemos/src/main/java/ demos.jvm.escape.EscapeDemo
elapsed time = 22
$ jmap -histo 18938
num #instances #bytes class name
----------------------------------------------
1: 1000000 24000000 demos.jvm.escape.EscapeDemo$Person
关闭标量分配
java -Xmx1G -XX:-EliminateAllocations -XX:+PrintGCDetails -cp /Users/chengc/cc/work/projects/javaDemos/src/main/java/ demos.jvm.escape.EscapeDemo
逃逸分析和标量替换
java -Xmx1G -XX:+PrintGCDetails -cp /Users/chengc/cc/work/projects/javaDemos/src/main/java/ demos.jvm.escape.EscapeDemo
因为此时,JVM 逃逸分析
EscapeDemo
只会在escapeTest
方法中执行,不会发生方法逃逸和线程逃逸,所以可以对部分聚合量EscapeDemo
进行标量替换,将拆分后的标量在栈上分配,减少直接在堆上分配的对象数量。
评论