jvm 内存分配性能提升之——逃逸分析与tlab

柏拉图说过:思想永远是宇宙的统治者。只要思想不滑坡,办法总比困难多。

Java从最开始被诟病速度慢,到现在执行速度直追C语言。这些运行时优化是必不可少的。还记得我们之前讲的逃逸分析是怎么回事吗?

jvm 分配内存

当类已经被加载完毕了,那么会执行第二步,也就是分配内存。我们都知道new对象一般情况来说生成的对象都是会存放在堆当中(当存在栈上分配时,逃逸对象会优先分配在栈当中),那么存放肯定是需要内存空间去存放对象的。这里就涉及到两个问题。

问题1 如何分配内存?

分配内存的方式有两种。第一种是指针碰撞,第二种是空闲列表。

指针碰撞

指针碰撞的前提条件是堆中的内存是规整的,也就是说没有内存碎片的产生。因为对象实际上是以连续的内存空间去存放的。所以,当内存规整的时候,通过指针碰撞的方式就可以更加充分的利用内存。
堆中内存是绝对规整的,所有用过的内存都被放在了一边,没有用过的内存放在另外一边。中间通过一个指针来进行划分。当有新new的对象要在堆中划分内存时,这个指针会向空闲内存空间偏移一段可以存放下新对象的内存地址,然后再将新的对象存放到刚刚划分出来的新的内存空间当中。

空闲列表

空闲列表的方式是在内存不规整的情况下的一种内存的分配方式。

空闲列表是指,堆中可用空间跟已经使用的空间都相互交错,就没有办法通过指针碰撞这种方式来进行内存分配。这个时候虚拟机会维护一个列表去记录堆当中大大小小的可用内存空间,当新的对象需要进来分配内存空间的时候,会从空闲列表中找到一块能够存放进新对象的内存区域去存放对象,并且更新空闲列表的记录。

通过这两种方式,我们了解了JVM分配内存的机制。但是这里有一个问题,我们从一开始就在讨论规整的内存与不规整的内存的内存分配方式,但是大家有没有想过堆中的内存规整不规整这个又是由什么导致的呢?

其实这个跟使用的垃圾回收器有关。关于垃圾回收器的我会另外再开一篇文章来讲。

问题2 并发情况下如何去处理内存分配?

创建对象肯定是会发生并发情况的,当某个线程调用的方法在创建对象的时候,他并不知道这个时候会不会有其他线程在这个时候恰巧也在创建对象。这就会产生并发争抢内存的现象。
JVM针对这种现象也给出了相应的解决措施,一种是CAS,另外一种则是TLAB。

CAS(compare and swap)

通过CAS + 失败重试,保证以原子性的方式来对分配内容的动作进行同步处理。

TLAB(Thread Local Allocation Buffer)

TLAB翻译过来叫做本地线程分配缓冲区。是指把内存分配的执行按照线程划分到不同的空间之中进行,也就是说每个开启的线程都会在堆中事先分配一小块内存空间,用这一块空间来存放对象。也就避免的多个线程同时分配对象内存的资源争抢的问题。

JVM默认是开启了TLAB

可以通过 -XX:+/-UseTLAB来决定开启或者关闭,还可以通过-XX:TLABSize指定每个线程的缓冲区大小。

逃逸分析:单线程中new对象的栈上分配

如果一个对象的分配是在方法内部,并且没有多线程访问的情况下,那么这个对象其实可以看做是一个本地对象,这样的对象不管创建在哪里都只对本线程中的本方法可见,因此可以直接分配在栈空间中。

栈上分配的对象因为不用考虑同步,所以执行速度肯定会更加快速,这也是为什么JVM会引入栈上分配的原因。

逃逸分析不在公共空间分配这个对象,而是在私人的栈空间中分配。

逃逸分析还有一个作用就是lock coarsening。

同样的,单线程环境中,锁也是不需要的,也可以优化掉。

多线程环境中new对象之线程本地分配缓存区:TLAB

TLAB简介

小师妹:师兄,我觉得逃逸分析很好呀,栈上分配也不错。既然又这么厉害的两项技术了,为什么还要用到TLAB呢?

首先这是两个不同的概念,TLAB的全称是Thread-Local Allocation Buffers。Thread-Local大家都知道吧,就是线程的本地变量。而TLAB则是线程的本地分配空间。

逃逸分析和栈上分配只是争对于单线程环境来说的,如果在多线程环境中,不可避免的会有多个线程同时在堆空间中分配对象的情况。

这种情况下如何处理才能提升性能呢?

小师妹:哇,多个线程竞争共享资源,这不是一个典型的锁和同步的问题吗?

锁和同步是为了保证整个资源一次只能被一个线程访问,我们现在的情况是要在资源中为线程划分一定的区域。这种操作并不需要完全的同步,因为heap空间够大,我们可以在这个空间中划分出一块一块的小区域,为每个线程都分一块。这样不就解决了同步的问题了吗?这也可以称作空间换时间。

TLAB详解

小师妹,还记得heap分代技术中的一个中心两个基本点吗?哦,1个Eden Space和2个Suvivor Space吗?

Young Gen被划分为1个Eden Space和2个Suvivor Space。当对象刚刚被创建的时候,是放在Eden space。垃圾回收的时候,会扫描Eden Space和一个Suvivor Space。如果在垃圾回收的时候发现Eden Space中的对象仍然有效,则会将其复制到另外一个Suvivor Space。

就这样不断的扫描,最后经过多次扫描发现任然有效的对象会被放入Old Gen表示其生命周期比较长,可以减少垃圾回收时间。

因为TLAB关注的是新分配的对象,所以TLAB是被分配在Eden区间的,从图上可以看到TLAB是一个一个的连续空间。

然后将这些连续的空间分配个各个线程使用。

因为每一个线程都有自己的独立空间,所以这里不涉及到同步的概念。

jvm默认情况下TLAB是开启的,你可以通过:

-XX:-UseTLAB

来关闭它。

设置TLAB空间的大小

小师妹,F师兄,这个TLAB的大小是系统默认的吗?我们可以手动控制它的大小吗?

要解决这个问题,我们还得去看JVM的C++实现,也就是threadLocalAllocBuffer.cpp:

上面的代码可以看到,如果设置了TLAB(默认是0),那么TLAB的大小是定义的TLABSize除以HeapWordSize和max_size()中最小的那个。

HeapWordSize是heap中一个字的大小,我猜它=8。别问我为什么,其实我也是猜的,有人知道答案的话可以留言告诉我。
TLAB的大小可以通过:

-XX:TLABSize
来设置。

如果没有设置TLAB,那么TLAB的大小就是分配线程的平均值。

TLAB的最小值可以通过:

-XX:MinTLABSize
来设置。

默认情况下:

-XX:ResizeTLAB
resize开关是默认开启的,那么JVM可以对TLAB空间大小进行调整。

TLAB中大对象的分配

小师妹:F师兄,我想到了一个问题,既然TLAB是有大小的,如果一个线程中定义了一个非常大的对象,TLAB放不下了,该怎么办呢?

好问题,这种情况下又有两种可能性,我们假设现在的TLAB的大小是100K:

第一种可能性:

目前TLAB被使用了20K,还剩80K的大小,这时候我们创建了一个90K大小的对象,现在90K大小的对象放不进去TLAB,这时候需要直接在heap空间去分配这个对象,这种操作实际上是一种退化操作,官方叫做 slow allocation。

第二中个可能性:

目前TLAB被使用了90K,还剩10K大小,这时候我们创建了一个15K大小的对象。

这个时候就要考虑一下是否仍然进行slow allocation操作。

因为TLAB差不多已经用完了,为了保证后面new出来的对象仍然可以有一个TLAB可用,这时候JVM可以尝试将现在的TLAB Retire掉,然后分配一个新的TLAB空间,把15K的对象放进去。

JVM有个开关,叫做:

-XX:TLABWasteTargetPercent=N
这个开关的默认值是1。表示如果新分配的对象大小如果超出了设置的这个百分百,那么就会执行slow allocation。否则就会分配一个新的TLAB空间。

同时JVM还定义了一个开关:

-XX:TLABWasteIncrement=N
为了防止过多的slow allocation,JVM定义了这个开关(默认值是4),比如说第一次slow allocation的极限值是1%,那么下一次slow allocation的极限值就是%1+4%=5%。

TLAB空间中的浪费

小师妹:F师兄,如果新分配的TLAB空间,那么老的TLAB中没有使用的空间该怎么办呢?

这个叫做TLAB Waste。因为不会再在老的TLAB空间中分配对象了,所以剩余的空间就浪费了。

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

推荐阅读更多精彩内容