<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Netty默認(rèn)使用哪種內(nèi)存分配?

          共 7258字,需瀏覽 15分鐘

           ·

          2021-05-02 01:09

          一、引文

          對(duì)于 Java 程序來(lái)說(shuō),通過(guò)合理的內(nèi)存使用,減少 Full GC 的 STW 時(shí)間對(duì)于程序來(lái)說(shuō)可以獲得更好的性能。本文結(jié)合 Netty 來(lái)看如何對(duì) Java 內(nèi)存更合理的使用。

          二、內(nèi)存使用的目標(biāo)

          • 前提:盡可能的占用內(nèi)存更少

          • 預(yù)期:獲得更快的程序執(zhí)行速度

          于 Java 而言:減少 Full GC 的 STW 時(shí)間。

          三、內(nèi)存使用技巧

          1、減少對(duì)象本身的大小
          • 使用基本類(lèi)型而不是包裝類(lèi)型
            包裝類(lèi)型相比較基本類(lèi)型而言多了 object header ,會(huì)占用更多的內(nèi)存。

          • 使用 static 類(lèi)變量而不是實(shí)例變量
            一般如果類(lèi)是非單例的,會(huì)有多個(gè)實(shí)例,使用類(lèi)變量會(huì)節(jié)省更多的內(nèi)存。

            ? Netty 用于統(tǒng)計(jì)等待寫(xiě)的請(qǐng)求的字節(jié)數(shù)

            io.netty.channel.ChannelOutboundBuffer

          private static final AtomicLongFieldUpdater<ChannelOutboundBuffer> TOTAL_PENDING_SIZE_UPDATER =          AtomicLongFieldUpdater.newUpdater(ChannelOutboundBuffer.class, "totalPendingSize");
          @SuppressWarnings("UnusedDeclaration")private volatile long totalPendingSize;

          Netty 使用 static AtomicLongFieldUpdater 與 volatile long 結(jié)合的形式,減少本對(duì)象的內(nèi)存占用。其中 AtomicLongFieldUpdater 采用反射的形式原子的更新本類(lèi)中 volatile long 類(lèi)型的變量。

          2、對(duì)內(nèi)存分配預(yù)估
          • HashMap 在超過(guò)容量的 0.75 時(shí)會(huì)擴(kuò)容為 2 倍,對(duì)于可以預(yù)知容量的 HashMap 指定 size 避免庫(kù)容浪費(fèi)空間。

            ? Netty 根據(jù)接收到的數(shù)據(jù)動(dòng)態(tài)調(diào)整下一個(gè)要分配 Buffer 的大小

            io.netty.channel.AdaptiveRecvByteBufAllocator#record(int actualReadBytes)

          private void record(int actualReadBytes) {          // 嘗試是否可以減小分配的空間來(lái)滿足需求:當(dāng)前實(shí)際讀取的 size 是否小于或等于打算縮小的 size          if (actualReadBytes <= SIZE_TABLE[max(0, index - INDEX_DECREMENT)]) {              // 連續(xù)兩次減小都可以              if (decreaseNow) {                  // 減小                  index = max(index - INDEX_DECREMENT, minIndex);                  nextReceiveBufferSize = SIZE_TABLE[index];                  decreaseNow = false;              } else {                  decreaseNow = true;              }              // 判斷是否實(shí)際讀取的數(shù)量大于等于預(yù)估的,如果是則嘗試擴(kuò)容          } else if (actualReadBytes >= nextReceiveBufferSize) {              index = min(index + INDEX_INCREMENT, maxIndex);              nextReceiveBufferSize = SIZE_TABLE[index];              decreaseNow = false;          }      }
          3、零拷貝 - ( Zero-copy )
          • 使用邏輯組合,代替復(fù)制
            io.netty.buffer.CompositeByteBuf#addComponent

          • 使用包裝,代替實(shí)際復(fù)制

          byte[] bytes = data.getBytes();ByteBuf bytebuf = Unpooled.wrappedBuffer(bytes);

          使用 JDK 的 Zero-Copy 接口
          io.netty.channel.DefaultFileRegion#transferTo

          @Override  public long transferTo(WritableByteChannel target, long position) throws IOException {      long count = this.count - position;      if (count < 0 || position < 0) {          throw new IllegalArgumentException(                  "position out of range: " + position +                  " (expected: 0 - " + (this.count - 1) + ')');      }      if (count == 0) {          return 0L;      }      if (refCnt() == 0) {          throw new IllegalReferenceCountException(0);      }      // Call open to make sure fc is initialized. This is a no-oop if we called it before.      open();      // 包裝 FileChannel.transferTo 方法 Zero-Copy      long written = file.transferTo(this.position + position, count, target);      if (written > 0) {          transferred += written;      } else if (written == 0) {          // If the amount of written data is 0 we need to check if the requested count is bigger then the          // actual file itself as it may have been truncated on disk.          //          // See https://github.com/netty/netty/issues/8868          validate(this, position);      }      return written;  }
          4、堆外內(nèi)存

          堆內(nèi)內(nèi)存,把內(nèi)存對(duì)象分配在 Java 虛擬機(jī)的堆以外的內(nèi)存,又稱(chēng)直接內(nèi)存。

          5、內(nèi)存池

          內(nèi)存池就是在程序啟動(dòng)時(shí),預(yù)先向堆中申請(qǐng)一部分內(nèi)存,交給一個(gè)管理對(duì)象。在程序運(yùn)行中,需要時(shí)向管理對(duì)象“借”,不需要時(shí)“還”給管理對(duì)象。

          • 常用開(kāi)源實(shí)現(xiàn) Apache Commons pool

          • Netty 輕量級(jí)內(nèi)存池 io.netty.util.Recycler

          四、Netty 內(nèi)存使用源碼分析

          1、堆外內(nèi)存
          • 堆內(nèi)內(nèi)存 / 堆外內(nèi)存的切換方式

            • 指定參數(shù):io.netty.noPreferDirect = true / false

            • 默認(rèn)不使用堆內(nèi)內(nèi)存的,可以這樣指定使用堆內(nèi)內(nèi)存

          ServerBootstrap b = new ServerBootstrap();b.childOption(ChannelOption.ALLOCATOR, new PooledByteBufAllocator(false))
          • Netty 分配堆外內(nèi)存的本質(zhì)是調(diào)用 JDK 的ByteBuffer.allocateDirect(initialCapacity); 方法,再往下就是 JDK 的 Unsafe 了。

          2、內(nèi)存池
          • 內(nèi)存池 / 非內(nèi)存池 的切換方式

            • 指定參數(shù) :io.netty.allocator.type = unpooled / pooled

            • 啟動(dòng)類(lèi)中指定配置:

          ServerBootstrap b = new ServerBootstrap();b.childOption(ChannelOption.ALLOCATOR, UnpooledByteBufAllocator.DEFAULT);

          ServerBootstrap b = new ServerBootstrap();b.childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);

          那么 Netty 默認(rèn)使用什么類(lèi)型呢?
           我們查看 io.netty.channel.DefaultChannelConfig 類(lèi):

          private volatile ByteBufAllocator allocator = ByteBufAllocator.DEFAULT;

           繼續(xù)看 io.netty.buffer.ByteBufAllocator :

          ByteBufAllocator DEFAULT = ByteBufUtil.DEFAULT_ALLOCATOR;

           繼續(xù)看 io.netty.buffer.ByteBufUtil :

          static final ByteBufAllocator DEFAULT_ALLOCATOR;
          static { // 系統(tǒng)變量中取值,若為安卓平臺(tái)則使用 unpooled String allocType = SystemPropertyUtil.get( "io.netty.allocator.type", PlatformDependent.isAndroid() ? "unpooled" : "pooled"); allocType = allocType.toLowerCase(Locale.US).trim();
          ByteBufAllocator alloc; if ("unpooled".equals(allocType)) { alloc = UnpooledByteBufAllocator.DEFAULT; logger.debug("-Dio.netty.allocator.type: {}", allocType); } else if ("pooled".equals(allocType)) { alloc = PooledByteBufAllocator.DEFAULT; logger.debug("-Dio.netty.allocator.type: {}", allocType); } else { // 默認(rèn)為內(nèi)存池 alloc = PooledByteBufAllocator.DEFAULT; logger.debug("-Dio.netty.allocator.type: pooled (unknown: {})", allocType); }
          DEFAULT_ALLOCATOR = alloc;
          THREAD_LOCAL_BUFFER_SIZE = SystemPropertyUtil.getInt("io.netty.threadLocalDirectBufferSize", 0); logger.debug("-Dio.netty.threadLocalDirectBufferSize: {}", THREAD_LOCAL_BUFFER_SIZE);
          MAX_CHAR_BUFFER_SIZE = SystemPropertyUtil.getInt("io.netty.maxThreadLocalCharBufferSize", 16 * 1024); logger.debug("-Dio.netty.maxThreadLocalCharBufferSize: {}", MAX_CHAR_BUFFER_SIZE);  }
          • 總結(jié):默認(rèn)情況下,安卓平臺(tái)使用非池實(shí)現(xiàn),其他平臺(tái)使用內(nèi)存池實(shí)現(xiàn),在未指定 netty.allocator.type 參數(shù)時(shí),默認(rèn)內(nèi)存池實(shí)現(xiàn)

          • 具體的內(nèi)存池實(shí)現(xiàn) io.netty.buffer.PooledDirectByteBuf

            我們看一下 PooledDirectByteBuf#newInstance 方法:

          private static final ObjectPool<PooledDirectByteBuf> RECYCLER = ObjectPool.newPool(          new ObjectCreator<PooledDirectByteBuf>() {      @Override      public PooledDirectByteBuf newObject(Handle<PooledDirectByteBuf> handle) {          return new PooledDirectByteBuf(handle, 0);      }  });
          static PooledDirectByteBuf newInstance(int maxCapacity) { // 從池中獲取 PooledDirectByteBuf buf = RECYCLER.get(); buf.reuse(maxCapacity); return buf;  }

          這個(gè) RECYCLER 就是 Netty 的 Recycler 實(shí)現(xiàn),

          public final T get() {      if (maxCapacityPerThread == 0) {          // 表示沒(méi)有開(kāi)啟池化配置,new Object 返回          return newObject((Handle<T>) NOOP_HANDLE);      }      // ThreadLocal 獲取返回      Stack<T> stack = threadLocal.get();      DefaultHandle<T> handle = stack.pop();      if (handle == null) {          // 池中沒(méi)有對(duì)象時(shí)新建          handle = stack.newHandle();          handle.value = newObject(handle);      }      return (T) handle.value;  }

          上面的 get 方法時(shí)借,所謂有借有還再借不難,再看一下歸還的方法(Recycler 的內(nèi)部類(lèi) DefaultHandle ):

          @Overridepublic void recycle(Object object) {     if (object != value) {         throw new IllegalArgumentException("object does not belong to handle");       }
          Stack<?> stack = this.stack; if (lastRecycledId != recycleId || stack == null) { throw new IllegalStateException("recycled already"); } // 歸還回內(nèi)存池 stack.push(this);}
          瀏覽 84
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  韩国一级黄色毛片 | 日韩欧美中文字幕视频 | 日日噜狠狠色综合 | 在线看a黄色片……` | 骚逼免费观看 |