专栏首页阿Q说代码Battle:你会TLAB,我会逃逸分析

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

TLAB

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

「参数设置」

  • -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对象发生了逃逸。

要想不发生逃逸,可以改造为:

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中才有实现,而且这项技术到如今也不是十分成熟。

其根本原因就是无法保证逃逸分析的性能提升一定能高于它的消耗,因为逃逸分析自身也需要进行一系列复杂的分析,是需要耗时的。

一个极端的例子,就是经过逃逸分析之后,发现所有对象都逃逸了,那这个逃逸分析的过程就白白浪费掉了。

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

本文分享自微信公众号 - 阿Q说代码(AQ_Shuo),作者:阿Q

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2021-06-23

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 小师妹学JVM之:逃逸分析和TLAB

    逃逸分析我们在JDK14中JVM的性能优化一文中已经讲过了,逃逸分析的结果就是JVM会在栈上分配对象,从而提升效率。

    程序那些事
  • JVM(2): 逃逸分析和内存分配

    我们都知道Java对象都是分配在在堆上的,在过往的认识中,一直是以这样的方式存在的,但是从Java7开始支持对象的栈分配和逃逸分析机制。

    yiduwangkai
  • 栈分配与TLAB

    在学习Java的过程中,一般认为new出来的对象都是被分配在堆上的,其实这个结论不完全正确,因为是大部分new出来的对象被分配在堆上,而不是全部。通过对Ja...

    干货满满张哈希
  • JVM 对象分配过程

    逃逸分析(Escape Analysis)简单来讲就是,Java Hotspot 虚拟机可以分析新创建对象的使用范围,并决定是否在 Java 堆上分配内存的一项...

    斯武丶风晴
  • JVM内存分配机制之栈上分配与TLAB的区别

    在java开发中,我们普遍认知中,new出的对象是直接分配到堆空间中,而实际情况并非如此,其实大家伙可以思考一下,无论方法的生命周期长与短,只要new的对象就存...

    黎明大大
  • JVM之堆

    一个进程对应一个jvm实例,同时包含多个线程,这些线==程共享方法区和堆==,每个==线程独有程序计数器、本地方法栈和虚拟机栈==。

    开源君
  • JVM技术总结之五——JVM逃逸分析

    一个对象(或变量)在方法中处理完毕返回时,返回结果可能会被其他对象引用,或者全局引用,这种现象即为逃逸。或者可以说,一个对象指针被多个线程或方法引用时,该对象指...

    剑影啸清寒
  • 面试官:Java中实例对象存储在哪?

    低级语言是计算机认识的语言、高级语言是程序员认识的语言。如何从高级语言转换成低级语言呢?这个过程其实就是编译。

    Java宝典
  • 【原创】JVM系列05 | TLAB上分配

    Java 程序会极其频繁的创建对象并为对象分配内存空间,一般情况下对象是分配在堆上的,堆又是全局共享的,所以会存在这样一个问题:多个线程同时在堆上申请空间,而堆...

    java进阶架构师
  • 一行代码引发的性能暴跌 10 倍

    上面的一个简单的代码是测试 Java 创建对象的性能,如果没有 System.out.println(user); 输出的时间是 300ms左右,如果加上性能是...

    付威
  • JVM虚拟机内存

    春哥大魔王
  • 【JVM从小白学成大佬】2.Java虚拟机运行时数据区

    这里我们先说句题外话,相信大家在面试中经常被问到介绍Java内存模型,我在面试别人时也会经常问这个问题。但是,往往都会令我比较尴尬,我还话音未落,面试者就会“背...

    猿人谷
  • 【JVM从小白学成大佬】2.Java虚拟机运行时数据区

    这里我们先说句题外话,相信大家在面试中经常被问到介绍Java内存模型,我在面试别人时也会经常问这个问题。但是,往往都会令我比较尴尬,我还话音未落,面试者就会“背...

    猿人谷
  • 【GC系列】JVM堆内存分代模型及常见的垃圾回收器

    现在大部分用到的垃圾回收器在逻辑上是分代的,除了G1之外的其他垃圾回收器在逻辑上和物理上都是分代的。

    行百里er
  • 在什么情况下,Java比C++慢很多?

    垃圾回收器。这是一把“双刃剑”。如果你的程序遵循“大部分对象都在年青代中消亡”模型,垃圾回收器是非常有利的(很少的碎片,更好的缓存局部性)。但是,如果程序不遵循...

    哲洛不闹
  • 10个经典又容易被人疏忽的JVM面试题

    「对象一定分配在堆中吗?」 不一定的,JVM通过「逃逸分析」,那些逃不出方法的对象会在栈上分配。

    捡田螺的小男孩
  • 10道饿了么JVM面试真题(两轮面试亲身经历)

    JVM系列知识,非常的重要,面试基本是必问的。大家面试的时候这方面的知识一定要提前做好储备。另外小编这里也整理了100多道的并发编程面试专题,想实战的朋友也可以...

    Java程序猿
  • 万万没想到,JVM内存结构的面试题可以问的这么难?

    在我的博客中,之前有很多文章介绍过JVM内存结构,相信很多看多我文章的朋友对这部分知识都有一定的了解了。

    Java技术江湖
  • 万万没想到,JVM内存结构的面试题可以问的这么难?

    在我的博客中,之前有很多文章介绍过JVM内存结构,相信很多看多我文章的朋友对这部分知识都有一定的了解了。

    java思维导图

扫码关注云+社区

领取腾讯云代金券