逃逸分析、栈上分配
一.逃逸分析
逃逸是指在某个方法之内创建的对象,除了在方法体之内被引用之外,还在方法体之外被其它变量引用到;这样带来的后果是在该方法执行完毕之后,该方法中创建的对象将无法被GC回收,由于其被其它变量引用。正常的方法调用中,方法体中创建的对象将在执行完毕之后,将回收其中创建的对象;故由于无法回收,即成为逃逸
通俗点解释:在方法内你new出来的对象只能在方法内随便折腾(业务逻辑处理),但是你不能逃逸出方法的这个一亩三分地而为外部方法所利用。再精简一点可以理解成:对象在我方法内活着,出方法则消亡
“逃逸分析技术”就是Java用来提升效率的技术手段之一,它通过动态的分析对象的作用域,来判断一个对象是否发生逃逸,如果没有逃逸,那么JVM可以做出以下优化:
锁消除
既然没有发生逃逸,位于线程私有的栈中天生线程安全,对数据的读写就无需加锁。
标量替换
如果可以,会对聚合量进行拆分,直接使用标量进行替换。(无法拆分,基本数据类型)
栈上分配
直接在方法栈中分配内存,方法出栈后就被销毁,减轻GC压力。
逃逸分析的场景包括:
- 全局变量赋值逃逸
- 方法返回值逃逸
- 实例引用发生逃逸
- 线程逃逸:赋值给类变量或可以在其他线程中访问的实例变量
逃逸分析开关:
-
-XX:+DoEscapeAnalysis 开启逃逸分析
-
-XX:-DoEscapeAnalysis 关闭逃逸分析
-
通过jmap -histo [pid]查看java堆上的对象分布情况
二.栈上分配
栈上分配主要是指在Java程序的执行过程中,在方法体中声明的变量以及创建的对象,将直接从该线程所使用的栈中分配空间。 一般而言,创建对象都是从堆中来分配的,这里是指在栈上来分配空间给新创建的对象。
三.标量与替换
1、标量和聚合量
标量(scalar replacement):就是不能再被分解的量。(例如八大基本类型 byte , short , int ,long ,char , float ,double , boolean)另外指向对象的引用也是标量。
聚合量(aggregate)就是还能继续被分解的量,例如对象能被分解成多个标量。
如果把一个Java对象拆散,将其成员变量恢复为分散的变量,这就叫做标量替换。拆散后的变量便可以被单独分析与优化,可以各自分别在活动记录(栈帧或寄存器)上分配空间;原本的对象就无需整体分配空间了。
2、替换过程
通过逃逸分析确定该对象不会被外部访问,并且对象可以被进一步分解时,JVM不会创建该对象,而会将该对象成员变量分解若干个被这个方法使用的成员变量所代替。这些代替的成员变量在栈帧或寄存器上分配空间。
-
-XX:+EliminateAllocations 可以开启标量替换
-
-XX:+PrintEliminateAllocations 查看标量替换情况(Server VM 非Product版本支持)
四.同步消除(又称为锁消除)
通过-XX:+EliminateLocks可以开启同步消除,进行测试执行的效率
同步消除是java虚拟机提供的一种优化技术。这里消除的其实是对象的同步锁,堆被线程共享,那么堆上面的对象也会被线程共享,大家一起读写,会出现同步的并发问题,所以对象也加了synchronized同步锁。但是栈是线程独享的,如果进行栈上分配,那么就根本不需要同步锁,提高了执行效率。这就是同步消除。