对象一定分配在堆中吗?
不一定
在编译期间,JIT 编译器对代码做了很多优化,其中有一部分就是针对内存堆分配进行优化,其实也就是逃逸分析技术。
什么是逃逸分析?
逃逸分析是指分析指针动态范围的方法,它同编译器优化原理的指针分析和外形分析相关联。当变量(或者对象)在方法中分配后,其指针有可能被返回或者被全局引用,这样就会被其他方法或者线程所引用,这种现象称作指针(或者引用)的逃逸(Escape)。
通俗点讲,当一个对象被 new 出来之后,它可能被外部所调用,如果是作为参数传递到外部了,就称之为方法逃逸。
public class Main {
private static Object globalObj;
public static void main(string[] args) {
escapeMethod();
System.out.println(globalObj.toString());
}
public static void escapeMethod() {
Object localObj = new Object(); // 一个对象被创建
globalObj = localObj; // 对象的引用被传递到外部的全局变量
// 这里可以有其他代码逻辑
}
}
除此之外,如果对象还有可能被外部线程访问到,例如赋值给可以在其它线程中访问的实例变量,这种就被称为线程逃逸。
public class Main {
private static Object sharedObj;
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
sharedObj = new Object(); // 一个对象被创建并赋值给共享变量
}
});
thread.start();
thread.join(); // 等待线程执行结束
System.out.println(sharedObj.toString());
}
}
逃逸分析的好处:
- 栈上分配
如果对象不逃逸,则可以通过在栈上分配和销毁对象来避免频繁的堆内存分配和垃圾回收操作,进而提高程序的执行效率。
- 同步消除
线程同步本身是一个相对耗时的过程,如果逃逸分析能够确定一个变量不会逃逸出线程,无法被其他线程访问,那么这个变量的读写肯定就不会有竞争, 对这个变量实施的同步措施也就可以安全地消除掉。
- 标量替换
如果一个数据是基本数据类型,不可拆分,它就被称之为标量。
把一个 Java 对象拆散,将其用到的成员变量恢复为原始类型来访问,这个过程就称为标量替换。
假如逃逸分析能够证明一个对象不会被方法外部访问,并且这个对象可以被拆散,那么可以不创建对象,直接用创建若干个成员变量代替,可以让对象的成员变量在栈上分配和读写。