美文网首页jvm调优
jvm 优化篇-(5)-线程局部缓存TLAB 指针碰撞、Eden

jvm 优化篇-(5)-线程局部缓存TLAB 指针碰撞、Eden

作者: tianlang136520 | 来源:发表于2019-08-20 07:31 被阅读0次
    告诉大家技术不枯燥

    TLAB(Thread Local Allocation Buffer)

        线程本地分配缓存,这是一个线程独享的内存分配区域。

    特点:
    • TLAB解决了:直接在线程共享堆上安全分配带来的线程同步性能消耗问题(解决了指针碰撞)。
    • TLAB内存空间位于Eden区。
    • 默认TLAB大小为占用Eden Space的1%。
    开启TLAB的参数:

    -XX:+UseTLAB
    -XX:+TLABSize
    -XX:TLABRefillWasteFraction
    -XX:TLABWasteTargetPercent
    -XX:+PrintTLAB <<<<<传送门

    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
      .....................省略......................
    }
    
    • _start 指TLAB连续内存起始地址。
    • _top 指TLAB当前分配到的地址。
    • _end 指TLAB连续内存截止地址。
    • _desired_size 是指TLAB的内存大小。
    • _refill_waste_limit 是指最大的浪费空间。默认值为64b,jdk1.8<<<<<传送门
      • eg:假设为_refill_waste_limit=5KB:
                1、假如当前TLAB已经分配96KB,还剩下4KB可分配,但是现在new了一个对象需要6KB的空间,显然TLAB的内存不够了,4kb<5kb这时只浪费4KB的空间,在_refill_waste_limit 之内,这时可以申请一个新的TLAB空间,原先的TLAB交给Eden管理。
                2、假如当前TLAB已经分配90KB,还剩下10KB,现在new了一个对象需要11KB,显然TLAB的内存不够了,这时就不能简单的抛弃当前TLAB,这11KB会被安排到Eden区进行申请。
    分配规则:

            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:允许浪费的内存空间)<----总结:tlab剩余可用空间>tlab可浪费空间,当前线程不能丢弃当前TLAB,本次申请交由Eden区分配空间
            3、        obj_size + tlab_top >= tlab_end && tlab_free < _refill_waste_limit,重新分配一块TLAB空间,在新的TLAB中分配对象。<----总结:tlab剩余可用空间<tlab可浪费空间,在当前允许可浪费空间内,重新申请一个新TLAB空间,原TLAB交给Eden

    清单:/src/share/vm/memory/ThreadLocalAllocationBuffer.inline.hpp
    功能:TLAB内存分配
    
    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;
    }
    

          实际上虚拟机内部会维护一个叫作refill_waste的值,当请求对象大于refill_waste时,会选择在堆中分配,若小于该值,则会废弃当前TLAB,新建TLAB来分配对象。这个阈值可以使用TLABRefillWasteFraction来调整,它表示TLAB中允许产生这种浪费的比例。默认值为64,即表示使用约为1/64的TLAB空间作为refill_waste。默认情况下,TLAB和refill_waste都会在运行时不断调整的,使系统的运行状态达到最优。如果想要禁用自动调整TLAB的大小,可以使用-XX:-ResizeTLAB禁用ResizeTLAB,并使用-XX:TLABSize手工指定一个TLAB的大小。

    指针碰撞&Eden区分配
    指针碰撞
                   // 指针碰撞分配
                  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区指针碰撞,需要模拟多线程并发申请内存空间。且需要关闭逃逸分析 -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
    

    经过对比,相差7倍左右。二者内存回收♻️,从YoungGC次数和耗时上没有太大变化:应为都是Eden区分配。

    相关文章

      网友评论

        本文标题:jvm 优化篇-(5)-线程局部缓存TLAB 指针碰撞、Eden

        本文链接:https://www.haomeiwen.com/subject/lozejctx.html