Netty默認(rèn)使用哪種內(nèi)存分配?
一、引文
對(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");("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 是否小于或等于打算縮小的 sizeif (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
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-Copylong 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/8868validate(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)則使用 unpooledString 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>() {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);}
