一、分配流程
分配流程图:
![](https://i-blog.csdnimg.cn/blog_migrate/87cc462cbae2676b961ae0a1e700a1bf.png)
并不是所有对象都分配在堆上,除了堆(绝⼤多数对象分配到堆上)以外,还有两个地⽅可以存放对象——栈和TLAB。
○ 如果开启栈上分配,JVM会先进行栈上分配;
○ 如果没有开启栈上分配或不符合条件,则会进行TLAB分配;
○ 如果TLAB分配不成功且不满足进入老年代的条件,则会在eden区分配;
○ 如果对象满足了直接进入老年代的条件,那就直接在老年代分配。
二、栈上分配
栈上分配是JVM提供的一项优化技术。
基本思想如下所示:
对于那些线程私有的对象(即:不可能被其他线程访问的对象),可以将它们打散分配在栈上,而不是分配在堆上。
分配在栈上的好处是可以在函数调用结束后自行销毁,而不需要垃圾回收器的介入,从而提高系统的性能。
对于大量的零散小对象,栈上分配提供了一种很好的对象分配优化策略,栈上分配速度快,并且可以有效避免GC带来的负面影响,但是由于和堆空间相比,栈空间较小,因此对于大对象无法也不适合在栈上分配。
栈上分配的技术基础,两者必须都开启:
逃逸分析:逃逸分析的目的是判断对象的作用域是否有可能逃逸出函数体。
标量替换:允许将对象打散分配在栈上。比如:若一个对象拥有两个字段,会将这两个字段视作局部变量进行分配。
注:只能在server模式下才能启用逃逸分析;
![](https://i-blog.csdnimg.cn/blog_migrate/072ee9e661e3541a5f52213e5a4cd603.png)
参数-XX:+DoEscapeAnalysis启用逃逸分析;
参数-XX:+EliminateAllocations开启标量替换(默认打开)。
三、TLAB
TLAB的全称是Thread Local Allocation Buffer,即线程本地分配缓存区,这是一个线程专用的内存分配区域。
由于对象一般会分配在堆上,而堆是全局共享的。因此在同一时间,可能会有多个线程在堆上申请空间。因此,每次对象分配都必须要进行同步(虚拟机采用CAS配上失败重试的方式保证更新操作的原子性),而在竞争激烈的场合分配的效率又会进一步下降。
JVM使用TLAB来避免多线程冲突,在给对象分配内存时,每个线程使用自己的TLAB,这样可以避免线程同步,提高了对象分配的效率。
TLAB本身占用Eden区空间,在开启TLAB的情况下,虚拟机会为每个Java线程分配一块TLAB空间。
参数**-XX:+UseTLAB**开启TLAB,默认是开启的。
TLAB空间的内存非常小,缺省情况下仅占有整个Eden空间的1% ,当然可以通过选项-XX:TLABWasteTargetPercent设置TLAB空间所占用Eden空间的百分比大小。
由于TLAB空间一般不会很大,因此大对象无法在TLAB上进行分配,总是会直接分配在堆上。TLAB空间由于比较小,因此很容易装满。
四、逃逸分析
对于线程私有的对象,可以分配在栈上,⽽不是分配在堆上。好处是⽅法执⾏完,对象⾃⾏销毁,不需要gc介⼊。可以提⾼性能。
⽽栈上分配的⼀个技术基础(如果关闭逃逸分析或关闭标量替换,那么⽆法将对象分配在栈上)就是逃逸分析。
逃逸分析的⽬的是判断对象的作⽤域是否有可能逃逸出函数体。如下所示:
Student student;
// 属于逃逸了
public void say1() {
student = new Student();
}
public void say2() {
Student student = new Student(); // 没有逃逸
}
【注意】
对于say2()⽅法中的new Student(),jvm就有可能将Student分配在栈上,⽽不是堆上。
对于⼤量的零散⼩对象,栈上分配提供了⼀种很好的对象分配优化策略。
对于⼤对象,⽆法也不适合在栈上分配。
五、标量替换
标量即不可被进一步分解的量,——JAVA的基本数据类型就是标量(如:int,long等基本数据类型以及reference类型等)
聚合量标量的对立就是可以被进一步分解的量。——JAVA中对象就是可以被进一步分解的聚合量。
替换过程
条件1> 通过逃逸分析确定该对象不会被外部访问。
条件2> 对象可以被进一步分解,即聚合量。JVM不会创建该对象,而会将该对象成员变量 分 解若干个被这个方法使用的成员变量所代替。这些代替的成员变量在 栈帧或 寄存器上分配空间。