JVM之逃逸分析以及TLAB

概述

对于逃逸分析和TLAB两种技术之间的关联一直没有理清楚,今天抽时间专门整理了一下这两门技术。
通过这篇文章,我们可以了解到什么:

  • 对象的分配流程
  • 对象在年轻代是采用什么算法进行内存的分配
  • 什么是逃逸分析技术
  • 逃逸分析和TLAB两门技术的结合,提高JVM虚拟机性能
  • 编译器和解释器的混合模式

TLAB

我们都知道对象是在堆区中为对象分配内存,并且堆区被多个线程共享(JVM采用共享内存模型),当多个线程同时进行 new 操作时,需要通过同步操作保证数据的正确。在JVM虚拟中先尝试通过CAS操作为对象分配内存,如果分配不成功才会尝试获得堆区的锁,进行对象的分配

  1. CAS分配
if (gen0->should_allocate(size, is_tlab)) {//对大小进行判断,比如是否超过eden区能分配的最大大小
      result = gen0->par_allocate(size, is_tlab);///while循环+指针碰撞+CAS分配
      if (result != NULL) {
        assert(gch->is_in_reserved(result), "result not in heap");
        return result;
      }
    }
  1. 对堆区进行加锁
      ............省略.............
      MutexLocker ml(Heap_lock);//锁
      if (PrintGC && Verbose) {
        gclog_or_tty->print_cr("TwoGenerationCollectorPolicy::mem_allocate_work:"
                      " attempting locked slow path allocation");
      }
      // Note that only large objects get a shot at being
      // allocated in later generations.
       //需要注意的是,只有大对象可以被分配在老年代。一般情况下都是false,所以first_only=true
      bool first_only = ! should_try_older_generation_allocation(size);
      //在年轻代分配
      result = gch->attempt_allocation(size, is_tlab, first_only);
      if (result != NULL) {
        assert(gch->is_in_reserved(result), "result not in heap");
        return result;
      }

通过同步操作虽然保证了内存分配的正确性,但是却限制了对象分配的效率,JVM通过TLAB来减少了同步的操作,TLAB全称是ThreadLocalAllocBuffer,这块内存是从堆空间中划分出来给线程管理。TLAB数据结构如下:

class ThreadLocalAllocBuffer: public CHeapObj<mtThread> {
  HeapWord* _start;                              // address of TLAB
  HeapWord* _top;                                // address after last allocation
  HeapWord* _pf_top;                             // allocation prefetch watermark
  HeapWord* _end;                                // allocation end (excluding alignment_reserve)
  size_t    _desired_size;                       // desired size   (including alignment_reserve)
  size_t    _refill_waste_limit;                 // hold onto tlab if free() is larger than this
  .....................省略......................
}

TLAB空间主要有3个指针:_start、_top、_end。_start指针表示TLAB空间的起始内存,_end指针表示TLAB空间的结束地址,通过_start和_end指针,表示线程管理的内存区域;
当进行对象的内存划分的时候,就会通过移动_top指针分配内存(TLAB,Eden,To,From 区主要采用指针碰撞来分配内存(pointer bumping)),在TLAB空间为对象分配内存需要遵循下面的原则:

  1. obj_size + tlab_top <= tlab_end,直接在TLAB空间分配对象
  2. obj_size + tlab_top >= tlab_end && tlab_free > tlab_refill_waste_limit,对象不在TLAB分配,在Eden区分配。(tlab_free:剩余的内存空间,tlab_refill_waste_limit:允许浪费的内存空间)
  3. obj_size + tlab_top >= tlab_end && tlab_free < _refill_waste_limit,重新分配一块TLAB空间,在新的TLAB中分配对象

按照上面的规则,如果对象先TLAB划分失败,那么对象会在Eden空间划分对象,首先尝试CAS方式,如果失败,通过同步加锁的方式进行分配,下面是 new 一个对象的流程图:


逃逸分析

逃逸分析技术是当虚拟机采用编译器执行代码的一种优化方案。
逃逸分析主要是分析对象的作用域,如果一个变量的作用域在方法体内,并且没有将该变量赋给方法体外的变量,那么这个对象就没有发生逃逸现象,为了提高性能,在栈上分配该对象的内存,当栈帧从Java虚拟机栈中弹出,就自动销毁这个对象。减小垃圾回收器压力。
编译器根据逃逸分析的结果,会对程序进行优化:(参考:《深入理解Java虚拟机》)

  1. 栈上分配:如果确定一个对象不会逃逸出方法之外,那么让对象在栈上分配内存,对象所占用的内存空间就可以随着栈帧的出栈而销毁
  2. 同步消除:如果逃逸分析能够确定一个变量不会逃逸出线程,无法被其他线程访问,那么这个变量的读写就不会有竞争,对这个变量的同步操作可以消除
  3. 标量替换:如果逃逸分析确认一个对象的内存结构不需要连续,并且可以拆散,那么创建他的成员变量来代替对象的创建。

接下来通过一个案例来测试采用逃逸分析和不采用逃逸分析得到得结果,参考代码如下:

public class EscapeAnalysis {
    private static class Foo {
        private int x;
        private static int counter;

        public Foo() {
            x = (++counter);
        }
    }

    public static void main(String[] args) {
        long start = System.nanoTime();
        for (int i = 0; i < 1000 * 1000 * 10; ++i) {
            Foo foo = new Foo();
        }
        long end = System.nanoTime();
        System.out.println("Time cost is " + (end - start));
    }
}
不开启逃逸分析

程序参数如下:

-server -verbose:gc -XX:-DoEscapeAnalysis  -XX:-UseTLAB  -Xmx20m -Xms20m -Xmn10m

程序打印的日志:

[GC (Allocation Failure)  8191K->792K(19456K), 0.0018773 secs]
[GC (Allocation Failure)  8984K->696K(19456K), 0.0010031 secs]
[GC (Allocation Failure)  8888K->784K(19456K), 0.0009671 secs]
[GC (Allocation Failure)  8976K->728K(19456K), 0.0009667 secs]
[GC (Allocation Failure)  8920K->712K(19456K), 0.0009643 secs]
[GC (Allocation Failure)  8904K->744K(19456K), 0.0007664 secs]
[GC (Allocation Failure)  8936K->632K(19456K), 0.0009849 secs]
[GC (Allocation Failure)  8824K->632K(18432K), 0.0005136 secs]
[GC (Allocation Failure)  7800K->632K(18944K), 0.0006147 secs]
[GC (Allocation Failure)  7800K->632K(18944K), 0.0003694 secs]
[GC (Allocation Failure)  7800K->632K(18944K), 0.0004741 secs]
[GC (Allocation Failure)  7800K->632K(18944K), 0.0003157 secs]
[GC (Allocation Failure)  7800K->632K(18944K), 0.0005349 secs]
[GC (Allocation Failure)  7800K->632K(18944K), 0.0003457 secs]
[GC (Allocation Failure)  7800K->632K(18944K), 0.0011141 secs]
[GC (Allocation Failure)  7800K->632K(19456K), 0.0007234 secs]
[GC (Allocation Failure)  8312K->632K(18944K), 0.0005562 secs]
[GC (Allocation Failure)  8312K->632K(19456K), 0.0004954 secs]
[GC (Allocation Failure)  8824K->632K(19456K), 0.0008478 secs]
[GC (Allocation Failure)  8824K->632K(19456K), 0.0004709 secs]
Time cost is 153997979
开启逃逸分析

程序参数如下:

-server -verbose:gc -XX:+DoEscapeAnalysis  -XX:-UseTLAB  -Xmx20m -Xms20m -Xmn10m

程序打印的日志:

Time cost is 6618854

根据打印出来的日志,明显的看出开启逃逸分析以后,性能提高了很多,而且也减少了GC的压力。

逃逸分析与TLAB结合

经过逃逸分析,发现对象发生了逃逸,那么对象就会在年轻代进行分配,分配的流程和我们之前的分析的对象分配流程一致。通过结合逃逸分析技术和TLAB,提高了对象分配的性能,减少了GC的压力。

扩展

在上一小节,当我们开启了逃逸分析,打印的日志就只有程序执行的时间,如果我们调小启动参数中年轻代的内存,会发现一个有趣的现象。
程序参数如下:

-server -verbose:gc -XX:+DoEscapeAnalysis  -XX:-UseTLAB  -Xmx20m -Xms20m -Xmn3m

程序打印的日志:

[GC (Allocation Failure)  2047K->652K(19968K), 0.0011765 secs]
Time cost is 7006804

从上面的日志中我们可以看到有GC日志,为啥会有GC呢??我们明明开启的逃逸分析,按理来说,应该会在栈上分配对象的啊。下面将回答这个问题

  1. 我使用的是JDK1.8,默认使用混合模式,你可以会问:什么是混合模式?
    在Hotspot中采用的是解释器和编译器并行的架构,所谓的混合模式就是解释器和编译器搭配使用,当程序启动初期,采用解释器执行(同时会记录相关的数据,比如函数的调用次数,循环语句执行次数),节省编译的时间。在使用解释器执行期间,记录的函数运行的数据,通过这些数据发现某些代码是热点代码,采用编译器对热点代码进行编译,以及优化(逃逸分析就是其中一种优化技术)

  2. 现在我们知道了什么是混合模式,但是我们怎么知道我们的JDK采用了混合模式呢?
    在windows系统中,我们通过cmd命令进行命令行窗口,执行命令

java -verion

执行结果:
java version "1.8.0_144"
Java(TM) SE Runtime Environment (build 1.8.0_144-b01)
Java HotSpot(TM) 64-Bit Server VM (build 25.144-b01, mixed mode)
//mixed mode 表示用的混合模式,interpreted mode 表示使用解释器, compiled mode 表示采用编译器
  1. 为什么会有GC日志?
    在程序启动初期,我们使用的解释器执行,使用解释器执行没有逃逸分析的技术,因此对象在年轻代进行分配,关于对象的分配和我们上面的分析流程一致,当年轻代空间不足,就会触发GC,关于对象的创建以及GC的触发可以参考我的文章://www.greatytc.com/p/941fe93d21c2
    使用解释器执行,积累的程序执行的相关数据,使用编译器对热点代码进行编译,并且采用逃逸分析技术进行优化。对象将在栈上分配,随着栈帧的出栈而消亡。

  2. 只使用编译器执行上面的代码会是什么效果?
    启动参数:

-server -Xcomp -verbose:gc -XX:+DoEscapeAnalysis  -XX:-UseTLAB  -Xmx20m -Xms20m -Xmn3m

程序打印的日志:

Time cost is 144426438

对比上面的日志,我们发现使用的时间多了两个数量级,而且没有GC日志,为什么呢?
没有GC日志是因为程序使用编译器来执行程序,并进行了逃逸分析的优化操作;时间多了两个数量级是因为编译器编译的过程缓慢,今天先来点开胃小菜,接下来将写其他的文章来讲解编译器和解释器的混合。

参考资料

http://blog.csdn.net/yangzl2008/article/details/43202969
http://blog.csdn.net/yangzl2008/article/details/43202969

结尾

就写到这里了,先逃了,如果发现文章的错误,请留言指出,大家一起进步,谢谢!!!!。

自我介绍

我是何勇,现在重庆猪八戒,多学学!!!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,743评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,296评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,285评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,485评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,581评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,821评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,960评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,719评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,186评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,516评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,650评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,329评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,936评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,757评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,991评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,370评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,527评论 2 349

推荐阅读更多精彩内容