Battle:你会TLAB,我会逃逸分析

“噔噔噔…”传来一阵敲门声,把我从好梦中惊醒了。java

朦胧间听到有人在说话“阿Q,在家不?”面试

“来了来了”,推门一看,原来是“赵信”兄弟。并发

赵信:自称常山赵子龙,一把三爪长枪耍的虎虎生风,见人上去就是一枪,人送外号“菊花信”。app


 

TLAB













  • 尽管不是全部的对象实例都可以在TLAB中成功分配内存(由于它的空间比较小),但JVM明确是将TLAB做为内存分配的首选;
  • 一旦对象在TLAB空间分配内存失败时,JVM就会尝试着经过使用加锁机制确保数据操做的原子性,从而直接在Eden空间中分配内存。
     

参数设置ide

  • -XX:UseTLAB:设置是否开启TLAB空间;
  • -XX:TLABWasteTargetPercent:设置TLAB空间所占Eden空间的百分比大小,默认仅占1%;


堆是分配对象的惟一选择吗?





  1. 若是通过逃逸分析(Escape Analysis)后发现,一个对象并无逃逸出方法,那么就可能被优化为栈上分配。这样就无需在堆上分配内存,也无须进行垃圾回收了。这也是最多见的堆外存储技术。
  2. 基于OpenJDK深度定制的TaoBaoVM,它创新的GCIH(GCinvisible heap)实现了堆外分配。将生命周期较长的Java对象从堆中移至堆外,而且GC不能管理GCIH内部的Java对象,以此达到下降GC的回收频率和提高GC的回收效率的目的。




举例一性能

public void method(){
   
    User user = new User();
    ...
    user = null;
}

user对象在方法内部声明,且在内部置为null,未被方法外的方法所引用,咱们就说user对象没有发生逃逸。学习

能够分配到栈上,并随着方法的结束,栈空间也随之移除。测试

举例二优化

public static StringBuffer createStringBuffer(String s1,String s2){
   
    StringBuffer sb = new StringBuffer();
    sb.append(s1);
    sb.append(s2);
    return sb;
}

虽然sb对象在方法内部被定义,可是它又做为方法的返回对象,可被其它方法调用,咱们就说sb对象发生了逃逸。this

要想不发生逃逸,能够改造为:

public static String createStringBuffer(String s1,String s2){
   
    StringBuffer sb = new StringBuffer();
    sb.append(s1);
    sb.append(s2);
    return sb.toString();
}



JDK 6u23版本以后,HotSpot中默认开启了逃逸分析。

  • -XX:DoEscapeAnalysis:显式开启逃逸分析
  • -XX:+PrintEscapeAnalysis:查看逃逸分析的筛选结果



栈上分配


/** * 栈上分配测试 * -Xmx1G -Xms1G -XX:-DoEscapeAnalysis -XX:+PrintGCDetails */
public class StackAllocation {
   
    public static void main(String[] args) {
   
        long start = System.currentTimeMillis();

        for (int i = 0; i < 10000000; i++) {
   
            alloc();
        }
       
        long end = System.currentTimeMillis();
        System.out.println("花费的时间为: " + (end - start) + " ms");
        //为了方便查看堆内存中对象个数,线程sleep
        try {
   
            Thread.sleep(1000000);
        } catch (InterruptedException e1) {
   
            e1.printStackTrace();
        }
    }

    private static void alloc() {
   
        //未发生逃逸
        User user = new User();
    }

    static class User {
   

    }
}

逃逸分析默认开启,也能够手动开启:-XX:+DoEscapeAnalysis

关闭逃逸分析


同步省略


咱们都知道线程同步的代价是至关高的,同步的后果就是下降了并发性和性能。

JVM为了提升性能,在动态编译同步块的时候,JIT编译器能够借助逃逸分析来判断同步块所使用的锁对象是否只可以被一个线程访问。

若是符合条件,那么JIT编译器在编译这个同步块的时候就会取消对这部分代码的同步。这个取消同步的过程就叫同步省略,也叫锁消除。

举例

public class SynchronizedTest {
   
    public void method() {
   
        Object code = new Object();
        synchronized(code) {
   
            System.out.println(code);
        }
    }
    /** *代码中对code这个对象进行加锁, *可是code对象的生命周期只在method方法中 *并不会被其余线程所访问控制, *因此在 JIT 编译阶段就会被优化掉。 */
    
    //优化为
    public void method2() {
   
        Object code = new Object();
        System.out.println(code);
    }
}

在解释执行时这里仍然会有锁,可是通过服务端编译器的即时编译以后,这段代码就会忽略全部的同步措施而直接执行。

标量替换

  • 标量:不可被进一步分解的量,如JAVA的基本数据类型就是标量;
  • 聚合量:能够被进一步分解的量, 在JAVA中对象就是能够被进一步分解的聚合量。

聚合量能够分解成其它标量和聚合量。

标量替换,又名分离对象,即在JIT阶段,若是通过逃逸分析,发现一个对象不会被外界访问的话,那么通过JIT优化,就会把这个对象拆解成若干个其中包含的成员变量来替代。

举例

public class ScalarTest {
   
    public static void main(String[] args) {
   
        alloc();   
    }
    public static void alloc(){
   
        Point point = new Point(1,2);
    }
}
class Point{
   
    private int x;
    private int y;
    public Point(int x,int y){
   
        this.x = x;
        this.y = y;
    }
}
//转化以后变为
public static void alloc(){
   
    int x = 1;
    int y = 2;
}
//Point这个聚合量通过逃逸分析后,发现他并无逃逸,就被替换成两个标量了。

标量替换默认开启,你也能够经过参数手动设置-XX:+EliminateAllocations,开启以后容许将对象打散分配到栈上,GC减小,执行速度提高。

常见的发生逃逸的场景


举例

public class EscapeAnalysis {
   

    public EscapeAnalysis obj;
    
     /* 为成员属性赋值,发生逃逸 */
    public void setObj(){
   
        this.obj = new EscapeAnalysis();
    }
    //思考:若是当前的obj引用声明为static的?仍然会发生逃逸。

    /* 方法返回EscapeAnalysis对象,发生逃逸 */
    public EscapeAnalysis getInstance(){
   
        return obj == null? new EscapeAnalysis() : obj;
    }
   
   
    /* 引用成员变量的值,发生逃逸 */
    public void useEscapeAnalysis1(){
   
        EscapeAnalysis e = getInstance();
        //getInstance().xxx()一样会发生逃逸
    }
    
     /* 对象的做用域仅在当前方法中有效,没有发生逃逸 */
    public void useEscapeAnalysis(){
   
        EscapeAnalysis e = new EscapeAnalysis();
    }
}

逃逸分析并不成熟




1999年就已经发表了关于逃逸分析的论文,但JDK1.6中才有实现,并且这项技术到现在也不是十分红熟。

其根本缘由就是没法保证逃逸分析的性能提高必定能高于它的消耗,由于逃逸分析自身也须要进行一系列复杂的分析,是须要耗时的。

一个极端的例子,就是通过逃逸分析以后,发现全部对象都逃逸了,那这个逃逸分析的过程就白白浪费掉了。

细心的小伙伴也应该能发现,咱们在抽样器中的截图其实就是在堆中分配的对象。

以上就是今天的全部内容了,若是你有不一样的意见或者更好的idea,欢迎联系阿Q,添加阿Q能够加入技术交流群参与讨论呦!

后台留言领取java干货资料:学习笔记与大厂面试题

相关文章
相关标签/搜索