jvm 優(yōu)化篇-(5)-線程局部緩存TLAB 指針碰撞、Eden區(qū)分配 -XX:+UseTLAB -XX:+PrintTLAB -XX:TLABWasteTargetPercent

告訴大家技術不枯燥

TLAB(Thread Local Allocation Buffer)

????線程本地分配緩存,這是一個線程獨享的內(nèi)存分配區(qū)域。

特點:
  • TLAB解決了:直接在線程共享堆上安全分配帶來的線程同步性能消耗問題(解決了指針碰撞)。
  • TLAB內(nèi)存空間位于Eden區(qū)。
  • 默認TLAB大小為占用Eden Space的1%。
開啟TLAB的參數(shù):

-XX:+UseTLAB
-XX:+TLABSize
-XX:TLABRefillWasteFraction
-XX:TLABWasteTargetPercent
-XX:+PrintTLAB <<<<<傳送門

TLAB的數(shù)據(jù)結構:
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
  .....................省略......................
}
  • _start 指TLAB連續(xù)內(nèi)存起始地址。
  • _top 指TLAB當前分配到的地址。
  • _end 指TLAB連續(xù)內(nèi)存截止地址。
  • _desired_size 是指TLAB的內(nèi)存大小。
  • _refill_waste_limit 是指最大的浪費空間。默認值為64b,jdk1.8<<<<<傳送門
    • eg:假設為_refill_waste_limit=5KB:
      ????????1、假如當前TLAB已經(jīng)分配96KB,還剩下4KB可分配,但是現(xiàn)在new了一個對象需要6KB的空間,顯然TLAB的內(nèi)存不夠了,4kb<5kb這時只浪費4KB的空間,在_refill_waste_limit 之內(nèi),這時可以申請一個新的TLAB空間,原先的TLAB交給Eden管理。
      ????????2、假如當前TLAB已經(jīng)分配90KB,還剩下10KB,現(xiàn)在new了一個對象需要11KB,顯然TLAB的內(nèi)存不夠了,這時就不能簡單的拋棄當前TLAB,這11KB會被安排到Eden區(qū)進行申請。
分配規(guī)則:

????????1、obj_size + tlab_top <= tlab_end,直接在TLAB空間分配對象。
????????2、obj_size + tlab_top >= tlab_end && tlab_free > tlab_refill_waste_limit,對象不在TLAB分配,在Eden區(qū)分配。(tlab_free:剩余的內(nèi)存空間,tlab_refill_waste_limit:允許浪費的內(nèi)存空間)<----總結:tlab剩余可用空間>tlab可浪費空間,當前線程不能丟棄當前TLAB,本次申請交由Eden區(qū)分配空間。
????????3、????????obj_size + tlab_top >= tlab_end && tlab_free < _refill_waste_limit,重新分配一塊TLAB空間,在新的TLAB中分配對象。<----總結:tlab剩余可用空間<tlab可浪費空間,在當前允許可浪費空間內(nèi),重新申請一個新TLAB空間,原TLAB交給Eden

清單:/src/share/vm/memory/ThreadLocalAllocationBuffer.inline.hpp
功能:TLAB內(nèi)存分配

inline HeapWord* ThreadLocalAllocBuffer::allocate(size_t size) {
  invariants();
  // 獲取當前top
  HeapWord* obj = top();
  if (pointer_delta(end(), obj) >= size) {
    // successful thread-local allocation
#ifdef ASSERT
    // Skip mangling the space corresponding to the object header to
    // ensure that the returned space is not considered parsable by
    // any concurrent GC thread.
    size_t hdr_size = oopDesc::header_size();
    Copy::fill_to_words(obj + hdr_size, size - hdr_size, badHeapWordVal);
#endif // ASSERT
    // This addition is safe because we know that top is
    // at least size below end, so the add can't wrap.
    // 重置top
    set_top(obj + size);

    invariants();
    return obj;
  }
  return NULL;
}

??????實際上虛擬機內(nèi)部會維護一個叫作refill_waste的值,當請求對象大于refill_waste時,會選擇在堆中分配,若小于該值,則會廢棄當前TLAB,新建TLAB來分配對象。這個閾值可以使用TLABRefillWasteFraction來調(diào)整,它表示TLAB中允許產(chǎn)生這種浪費的比例。默認值為64,即表示使用約為1/64的TLAB空間作為refill_waste。默認情況下,TLAB和refill_waste都會在運行時不斷調(diào)整的,使系統(tǒng)的運行狀態(tài)達到最優(yōu)。如果想要禁用自動調(diào)整TLAB的大小,可以使用-XX:-ResizeTLAB禁用ResizeTLAB,并使用-XX:TLABSize手工指定一個TLAB的大小。

指針碰撞&Eden區(qū)分配
指針碰撞
               // 指針碰撞分配
              HeapWord* compare_to = *Universe::heap()->top_addr();
              HeapWord* new_top = compare_to + obj_size;
              if (new_top <= *Universe::heap()->end_addr()) {
                if (Atomic::cmpxchg_ptr(new_top, Universe::heap()->top_addr(), compare_to) != compare_to) {
                  goto retry;
                }
                result = (oop) compare_to;
              }
            }

Eden區(qū)指針碰撞,需要模擬多線程并發(fā)申請內(nèi)存空間。且需要關閉逃逸分析 -XX:-DoEscapeAnalysis -XX:+UseTLAB


/**
 * @author biudefu
 * @since 2019/8/19  下午11:25
-Xmx100m -Xms100m -XX:-DoEscapeAnalysis -XX:+UseTLAB 
-XX:TLABWasteTargetPercent=1 -XX:+PrintCommandLineFlags  -XX:+PrintGCDetails
 */
public class AllocationTLABSomeThread {

    private static final int threadNum = 100;
    private static CountDownLatch latch = new CountDownLatch(threadNum);
    private static final int n = 50000000 / threadNum;

    private static void alloc() {
        byte[] b = new byte[100];
    }

    public static void main(String[] args) {
        long start = System.currentTimeMillis();
        for (int i = 0; i < threadNum; i++) {
            new Thread(() -> {
                for (int j = 0; j < n; j++) {
                    alloc();
                }
                latch.countDown();
            }).start();
        }
        try {
            latch.await();
        } catch (InterruptedException e) {
            System.out.println("hello world");
        }
        long end = System.currentTimeMillis();
        System.out.println(end - start);
    }

}
運行結果:
-XX:-DoEscapeAnalysis -XX:InitialHeapSize=104857600 -XX:MaxHeapSize=104857600 -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:TLABWasteTargetPercent=1 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseParallelGC -XX:+UseTLAB 
[GC (Allocation Failure) [PSYoungGen: 25600K->960K(29696K)] 25600K->968K(98304K), 0.0019559 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 26560K->960K(29696K)] 26568K->968K(98304K), 0.0022243 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 26560K->768K(29696K)] 26568K->776K(98304K), 0.0022446 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
........
[GC (Allocation Failure) [PSYoungGen: 32768K->0K(33280K)] 34193K->1425K(101888K), 0.0014598 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 32768K->0K(33280K)] 34193K->1425K(101888K), 0.0015168 secs] [Times: user=0.00 sys=0.01, real=0.00 secs] 
823
Heap
 PSYoungGen      total 33280K, used 3655K [0x00000007bdf00000, 0x00000007c0000000, 0x00000007c0000000)
  eden space 32768K, 11% used [0x00000007bdf00000,0x00000007be291c48,0x00000007bff00000)
  from space 512K, 0% used [0x00000007bff80000,0x00000007bff80000,0x00000007c0000000)
  to   space 512K, 0% used [0x00000007bff00000,0x00000007bff00000,0x00000007bff80000)
 ParOldGen       total 68608K, used 1425K [0x00000007b9c00000, 0x00000007bdf00000, 0x00000007bdf00000)
  object space 68608K, 2% used [0x00000007b9c00000,0x00000007b9d64798,0x00000007bdf00000)
 Metaspace       used 4255K, capacity 4718K, committed 4992K, reserved 1056768K
  class space    used 477K, capacity 533K, committed 640K, reserved 1048576K

關閉逃逸和TLAB分配 -XX:-DoEscapeAnalysis -XX:-UseTLAB 運行結果:

-XX:-DoEscapeAnalysis -XX:InitialHeapSize=104857600 -XX:MaxHeapSize=104857600 -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:TLABWasteTargetPercent=1 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseParallelGC -XX:-UseTLAB 
[GC (Allocation Failure) [PSYoungGen: 25599K->976K(29696K)] 25599K->984K(98304K), 0.0023516 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 26575K->880K(29696K)] 26583K->888K(98304K), 0.0015459 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 26480K->832K(29696K)] 26488K->840K(98304K), 0.0006776 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
.......
[GC (Allocation Failure) [PSYoungGen: 32767K->0K(33280K)] 34053K->1285K(101888K), 0.0004838 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 32767K->0K(33280K)] 34053K->1285K(101888K), 0.0005389 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
5388
Heap
 PSYoungGen      total 33280K, used 21392K [0x00000007bdf00000, 0x00000007c0000000, 0x00000007c0000000)
  eden space 32768K, 65% used [0x00000007bdf00000,0x00000007bf3e4230,0x00000007bff00000)
  from space 512K, 0% used [0x00000007bff00000,0x00000007bff00000,0x00000007bff80000)
  to   space 512K, 0% used [0x00000007bff80000,0x00000007bff80000,0x00000007c0000000)
 ParOldGen       total 68608K, used 1285K [0x00000007b9c00000, 0x00000007bdf00000, 0x00000007bdf00000)
  object space 68608K, 1% used [0x00000007b9c00000,0x00000007b9d41788,0x00000007bdf00000)
 Metaspace       used 4248K, capacity 4718K, committed 4992K, reserved 1056768K
  class space    used 478K, capacity 533K, committed 640K, reserved 1048576K

經(jīng)過對比,相差7倍左右。二者內(nèi)存回收??,從YoungGC次數(shù)和耗時上沒有太大變化:應為都是Eden區(qū)分配。

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容