栈分配、逃逸分析与TLAB -JVM( 三 )


public void f() {Object hollis = new Object();synchronized(hollis) {System.out.println(hollis);}}
代码中对这个对象进行加锁 , 但是对象的生命周期只在f()方法中 , 并不会被其他线程所访问到 , 所以在JIT编译阶段就会被优化掉 。优化成:
public void f() {Object hollis = new Object();System.out.println(hollis);}
所以 , 在使用的时候 , 如果JIT经过逃逸分析之后发现并无线程安全问题的话 , 就会做锁消除 。
标量替换
标量()是指一个无法再分解成更小的数据的数据 。Java中的原始数据类型就是标量 。相对的 , 那些还可以分解的数据叫做聚合量() , Java中的对象就是聚合量 , 因为他可以分解成其他聚合量和标量 。
在JIT阶段 , 如果经过逃逸分析 , 发现一个对象不会被外界访问的话 , 那么经过JIT优化 , 就会把这个对象拆解成若干个其中包含的若干个成员变量来代替 。这个过程就是标量替换 。
public static void main(String[] args) {alloc();}private static void alloc() {Point point = new Point(1,2);System.out.println("point.x="+point.x+"; point.y="+point.y);}class Point{private int x;private int y;}
以上代码中 , point对象并没有逃逸出alloc方法 , 并且point对象是可以拆解成标量的 。那么 , JIT就会不会直接创建Point对象 , 而是直接使用两个标量int x  , int y来替代Point对象 。
以上代码 , 经过标量替换后 , 就会变成:
private static void alloc() {int x = 1;int y = 2;System.out.println("point.x="+x+"; point.y="+y);}
可以看到 , Point这个聚合量经过逃逸分析后 , 发现他并没有逃逸 , 就被替换成两个聚合量了 。那么标量替换有什么好处呢?就是可以大大减少堆内存的占用 。因为一旦不需要创建对象了 , 那么就不再需要分配堆内存了 。
标量替换为栈上分配提供了很好的基础 。

栈分配、逃逸分析与TLAB -JVM

文章插图
TLAB
TLAB的全称是 Local , 即线程本地分配缓存区 , 这是一个线程专用的内存分配区域 , 在Eden区 。
由于对象一般会分配在堆上 , 而堆是全局共享的 。因此在同一时间 , 可能会有多个线程在堆上申请空间 。因此 , 每次对象分配都必须要进行同步(虚拟机采用CAS配上失败重试的方式保证更新操作的原子性) , 而在竞争激烈的场合分配的效率又会进一步下降 。JVM使用TLAB来避免多线程冲突 , 在给对象分配内存时 , 每个线程使用自己的TLAB , 这样可以避免线程同步 , 提高了对象分配的效率 。
对象内存分配
我们这里不考虑栈上分配 , 我们这里考虑的是无法栈上分配需要共享的对象
对于JVM 实现 , 所有的 GC 算法的实现都是一种对于堆内存的管理 , 也就是都实现了一种堆的抽象 , 它们都实现了接口。当分配一个对象堆内存空间时 , 在上首先都会检查是否启用了 TLAB , 如果启用了 , 则会尝试 TLAB 分配;如果当前线程的 TLAB 大小足够 , 那么从线程当前的 TLAB 中分配;如果不够 , 但是当前 TLAB 剩余空间小于最大浪费空间限制(这是一个动态的值 , 我们后面会详细分析) , 则从堆上(一般是 Eden 区) 重新申请一个新的 TLAB 进行分配 。否则 , 直接在 TLAB 外进行分配 。TLAB 外的分配策略 , 不同的 GC 算法不同 。例如G1: