有这样一个问题:对象是在堆中被分配的吗?我想说至少目前是这样的。
现在版本的JVM本身就已经默认开启
了逃逸分析
来优化我们的代码,这种优化的思想有一种错觉是对象在栈上分配了,但其实并不是。实际上对象还是在堆中被分配的,优化的内容只是针对那些未发生逃逸的对象
,将对象通过标量替换
的手段进行优化了,也就是说将未发生逃逸的对象
拆分成了基础数据类型
和方法
,在栈帧使用栈帧即可管理,方法结束后栈帧出栈,“对象”被释放,减少了GC
发生的次数,优化了代码。
为了巩固自己的印象,上文提到了这些名词,将在文中逐一介绍:
- 逃逸分析
- 什么叫未发生逃逸的对象
- 如何开启和关闭逃逸分析
- 标量替换是什么意思
示例代码
类名为StackAllocation
。方法名:alloc
,调用之后就new
一个StackAllocation
对象。main
方法循环10000000次调用alloc
方法,并且计算花费的时间。
public class StackAllocation {
public static void main(String[] args) {
long start = System.currentTimeMillis();
for (int i = 0;i < 10000000; i++){
alloc();
}
long end = System.currentTimeMillis();
System.out.println("花费的时间是:" + (end- start) + "毫秒");
try {
TimeUnit.SECONDS.sleep(1000);
}catch (InterruptedException e){
e.printStackTrace();
}
}
public static void alloc(){
StackAllocation stackAllocation = new StackAllocation();
}
}
运行该代码时,添加JVM指令:-XX:-DoEscapeAnalysis
(该命令是关闭逃逸分析),执行结果如下:
花费的时间是:75毫秒
那么我们开启逃逸分析:
花费的时间是:4毫秒
逃逸分析
逃逸分析的作用是用来减少Java程序中同步负载和内存堆分配的压力,它的基本行为就是分析对象作用域。
未发生逃逸的对象
举个例子:当一个对象在A方法
中被定义后,如果该对象仅在A方法
中使用,则表示该对象是未发生逃逸的;
public void alloc(){
Cheng c = new Cheng(); // 该对象c只在alloc方法中使用,未发生逃逸
}
发生逃逸的对象
而以下三种行为是会发生逃逸的:
- 为类成员赋值,我们常说的
getInstance
动作
public class EscapeAnalysis{
private Cheng c;
public void getInstance{
this.c == null ? new Cheng():this.c; // 对象作用域已经出了这个方法
}
}
return
语句
public Cheng getCheng(){
return new Cheng(); // 对象作用域已经出了这个方法
}
- 调用
return
语句或者getInstance
拿到已经发生逃逸的对象
public void run(){
Cheng c = getInstance();
c.run();
}
如何开启和关闭逃逸分析
开启:-XX:+DoEscapeAnalysis
(默认就是开启的)
关闭:-XX:-DoEscapeAnalysis
标量替换是什么意思
标量
是指一个无法再分解的数据,基本数据类型满足这个特征。
而类是由成员变量以及方法构成的,所以一般类成员是可以进行拆分,拆分成标量
。这个拆有个前提就是满足未逃逸
。
例子:
private static void alloc() {
Point point = new Point(1,2);
}
class Point {
private int x;
private int y;
}
point
满足未逃逸
,它将会优化成标量
。
private static void alloc() {
int x = 1;
int y = 2;
}
这种方法
+变量
的形式,用栈结构即可。
总结
如何理解逃逸分析呢?
第一步:我们要知道JVM放置对象和数组的地方称为堆
,堆
是GC
垃圾回收的主要区域,如果频繁的GC
操作是会影响程序性能的,所以目的是要减缓GC
操作,这才有了逃逸分析。
第二步:了解逃逸分析做了什么事,它的设想是能不能栈上分配对象
,如果可以对象的回收不由GC
控制,用完直接出栈即可,满足这个就需要该对象是满足未逃逸的
,只有它是未逃逸的前提下,就可以将该对象进行标量替换
成标准数据类型和方法,这样的结构用栈帧就足以表示,从而间接的实现了栈上分配
,而这里分配并不是将对象在栈上分配了,而是通过标量替换
的方式用另一种方式在栈上表示了对象。
第三步:程序变快了的原因,就是通过第二步的方式,在栈中用出栈的方式管理对象的回收问题。
最后:逃逸分析并不成熟。