大多数情况下,对象是要在堆上进行内存分配的。但是HotSpot虚拟机引入了JIT优化之后,会对对象进行逃逸分析,如果发现某一个对象并没有逃逸到方法外部,那么就可能通过标量替换来实现栈上分配,而避免堆上分配内存。
1.什么是HotSpot
首先我们得先了解一个概念,现在java的虚拟机默认使用的都是 oracle公司的hotspot虚拟机,在控制台输入 :java -version 就会打印出java版本以及虚拟机的信息。

不可否认,大部分的对象创建时都是分配到堆内存里面的,但是呢也有特例,以hotsport虚拟机为例,hotspot虚拟机在创建对象的时候会先判断你这个创建的对象符不符合栈上分配的条件,如果符合,那么这个对象就会分配到栈上;否则就分配到堆上;

2.逃逸分析
逃逸分析(Escape Analysis)是目前Java虚拟机中比较前沿的优化技术。这是一种可以有效减少Java 程序中同步负载和内存堆分配压力的跨函数全局数据流分析算法。通过逃逸分析,Hotspot编译器能够分析出一个新的对象的引用的使用范围从而决定是否要将这个对象分配到堆上。
逃逸分析的基本行为就是分析对象动态作用域:当一个对象在方法中被定义后,它可能被外部方法所引用,例如作为调用参数传递到其他地方中,称为方法逃逸。
例如:

sb是一个方法内部变量,上述代码中并没有将他直接返回,这样这个StringBuffer有不会被其他方法所改变,这样它的作用域就只是在方法内部。我们就可以说这个变量并没有逃逸到方法外部。
有了逃逸分析,我们可以判断出一个方法中的变量是否有可能被其他线程所访问或者改变,那么基于这个特性,JIT就可以做一些优化:

看下面实例:
public class HotSpotDemo {
public static void main(String[] args) {
long l = System.currentTimeMillis();
for(int i = 50000000; i> 0; i --){
// 调用方法后,里面的 obj 没有在别的地方引用,属于不可逃逸
createNewObject();
}
System.out.println("一共执行了"+(System.currentTimeMillis()- l) +"ms");
}
public static void createNewObject(){
Object obj = new Object();
}
}
运行结果:
一共执行了4ms
Process finished with exit code 0
由结果我们可以看到,jvm 并没有进行垃圾回收机制,并且五千万次的循环只用了4毫秒;那么接下来我们关掉逃逸分析;在启动的时候加入以下jvm参数。
-XX:-DoEscapeAnalysis
-XX:+PrintGCDetails
-XX:+HeapDumpOnOutOfMemoryError
以上的参数表示关闭逃逸分析功能,默认值是 -XX:+DoEscapeAnalysis , 很好理解吧,+表示启用,-表示禁用,代码还是原来的代码,加上参数后,在看看打印的结果:
[GC (Allocation Failure) [PSYoungGen: 261120K->808K(304640K)] 261120K->816K(1000960K), 0.0014260 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 261928K->856K(304640K)] 261936K->864K(1000960K), 0.0018832 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 261976K->776K(304640K)] 261984K->784K(1000960K), 0.0016967 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
一共执行了248ms
Heap
PSYoungGen total 304640K, used 18812K [0x000000066c300000, 0x0000000691600000, 0x00000007c0000000)
eden space 261120K, 6% used [0x000000066c300000,0x000000066d49d2d8,0x000000067c200000)
from space 43520K, 1% used [0x000000067c200000,0x000000067c2c2020,0x000000067ec80000)
to space 43520K, 0% used [0x000000068eb80000,0x000000068eb80000,0x0000000691600000)
ParOldGen total 696320K, used 8K [0x00000003c4800000, 0x00000003ef000000, 0x000000066c300000)
object space 696320K, 0% used [0x00000003c4800000,0x00000003c4802000,0x00000003ef000000)
Metaspace used 3281K, capacity 4500K, committed 4864K, reserved 1056768K
class space used 359K, capacity 388K, committed 512K, reserved 1048576K
结论:
大部分情况下Javaa的对象实例都是在Java堆当中分配的(这里是从实现的角度上来讲),但是随着即时编译技术(尤其是逃逸分析技术)的进步,诸如栈上分配、标量替换等优化手段也随之诞生,即堆上分配不再是绝对的了。