1、对象的创建流程
- 根据逃逸分析,判断是否可以栈上分配,不满足则进行堆上分配
- 判断是否为大对象,如果是大对象,在老年代进行内存分配,不满足则进行新生代分配,
- 首先尝试分配到TLAB(Thread Local Allocation Buffer),如果TLAB足够,则分配成功。
- 根据碰撞指针或空闲列表记录的可用地址,采用CAS的方式在新生代中分配内存,解决分配内存时并发问题
2、什么是逃逸分析
1、逃逸分析概念
通俗理解:如果一个对象在一个方法内定义,如果被方法外部的引用所指向,那认为它逃逸了。否者,这个对象,没有发生逃逸。
public void test(){
Object obj = new Object(); //未发生逃逸,只在该方法内,进行逃逸分析就是为这种对象做优化
}
2、逃逸分析有什么用?
逃逸分析好处就是:对不能逃逸的对象做优化:
1、栈上分配
在栈上分配空间给新创建的对象,其实目前Hotspot并没有实现真正意义上的栈上分配,实际上是标量替换。(广义上的栈上分配)
2、标量替换
标量替换:即时编译可以将对象打散,将对象替换为一个个很小的局部变量,就可以存放在方法栈帧的局部变量表中,也就实现了栈上分配
private static void alloc() {
Point point = new Point(1,2);
System.out.println("point.x" + point.x + ";point.y" + point.y);
}
//标量替换后:
private static void alloc() {
int x = 1;
int y = 2;
System.out.println("point.x = " + x + "; point.y=" + y);
}
- 标量 :不可再分,基本数据类型
- 聚合量: 可再分,引用数据类型
将对象替换为一个个局部变量后,就可以非常方便的在栈上进行分配了。为当前线程在虚拟机栈中分配一块空间,这块空间称为Java线程栈,里面存放的都是方法栈帧,一个栈帧代表的就是一个函数的调用,包含局部变量表、操作栈、动态连接、返回地址。
- 局部变量表:存放基本类型,引用类型在堆中的地址,或者是方法的返回地址等
- 操作栈 :存放计算过程中的中间结果,同时作为计算过程中变量临时的存储空间
- 动态连接: 指向了运行时常量池中方法的地址,当你要调用某一方法可以通过动态链接找到
- 方法出口:记录方法结束后,继续运行下一个栈帧对应的哪个方法哪行代码
3、锁消除
如果堆上的共享数据不可能逃逸出去被其它线程访问到,那么就可以把它们当成私有数据对待,也就可以将它们的锁进行消除
public class TestLockEliminate {
public static String getString(String s1, String s2) {
StringBuffer sb = new StringBuffer();
sb.append(s1);
sb.append(s2);
return sb.toString();
}
public static void main(String[] args) {
long tsStart = System.currentTimeMillis();
for (int i = 0; i < 1000000; i++) {
getString("TestLockEliminate ", "Suffix");
}
System.out.println("一共耗费:" + (System.currentTimeMillis() - tsStart) + " ms");
}
}
上述代码中的StringBuffer.append是一个同步操作,但是StringBuffer却是一个局部变量,并且方法也并没有把StringBuffer返回,所以不可能会有多线程去访问它。那么此时StringBuffer中的同步操作就是没有意义的。会进行锁消除
3、什么是TLAB
TLAB 是JVM在伊甸园区为每个线程划分出来的一块专用空间,如果需要分配内存,就在自己的空间上分配,这样就不存在竞争的情况,可以大大提高分配效率
虽然每个线程在初始化时都会去堆内存中申请一块 TLAB,并不是说这个 TLAB 区域的内存其他线程就完全无法访问了,其他线程的读取还是可以的,只不过无法在这个区域中分配内存而已。
当 TLAB 剩余空间不足时:
- 如果剩余空间大于refill_waste,就会放弃在TLAB上分配内存,直接在堆内存中分配。
- 如果剩余空间小于refill_waste,那么这个 TLAB 会被退回 Eden,重新申请一个新的TLAB。同时将旧TLAB中剩余可用的空间用一个 dummy object 填充满
为什么需要dummy object 填充?
主要为了提升GC扫描效率,由于 TLAB 仅线程内知道哪些被分配了,外部并不知道哪一部分被使用哪一部分没有,需要做额外的检查,那么会影响 GC 扫描效率。但是填充 dummy 也造成了空间的浪费,这种浪费不能太多,所以通过最大浪费空间限制来限制这种浪费。
4、什么是空闲列表和碰撞指针
- 碰撞指针:通过一个指针作为分界点,需要分配内存时,仅需把指针往空闲的一端移动与对象大小相等的距离,分配效率较高
- 空闲列表:通过额外的存储记录空闲的地址,将随机 IO 变为顺序 IO,但带来了额外的空间消耗。