开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 12 天,点击查看活动详情
有这样一个问题:对象是在堆中被分配的吗?我想说至少目前是这样的。
现在版本的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控制,用完直接出栈即可,满足这个就需要该对象是满足未逃逸的,只有它是未逃逸的前提下,就可以将该对象进行标量替换成标准数据类型和方法,这样的结构用栈帧就足以表示,从而间接的实现了栈上分配,而这里分配并不是将对象在栈上分配了,而是通过标量替换的方式用另一种方式在栈上表示了对象。
第三步:程序变快了的原因,就是通过第二步的方式,在栈中用出栈的方式管理对象的回收问题。
最后:逃逸分析并不成熟。