Netty源码实战(十) - 性能优化(下)

简介: Netty源码实战(十) - 性能优化(下)

1.2 轻量级对象池 Recycler

1.2.1 Recycler的使用

1.png

image.png


所以不使用 new 而是直接复用

1.pngNetty使用

image.png

1.2.2 Recycler的创建

image.png

image.png

image.png

image.png

image.png

下面回到Recycler的构造方法,看其入参.

image.png

image.png

image.png

image.png

image.png

image.png

image.png

1.2.3 回收对象到 Recycler

1.2.3.1 同线程回收


客户端开始调用

image.png

image.png

Recycler抽象类

image.png

将当前对象压栈

image.png

  • 如下,首先判断当前线程,thread 即为S tack 对象中保存的成员变量,若是创建该 stack 的线程,则直接压栈Stack 中,若不是再 pushlater.先分析 pushnow.

image.png

image.png

首先验证两个 id,由于默认初始值为0,所以通过判断.

image.png

接下来将其都赋值为第三个 id,该值在整个Recycler 中都是唯一确定值

image.png

紧接着判断当前 size是否已到 maxcapacity,若达到上限,直接丢弃该对象即直接 return;否则继续判断 drop 处理器

image.png

首先判断,若该对象之前未被回收过,继续判断;

至今已经回收了多少个对象,其中 rm 为7,即111(二进制表示),即每隔8个对象,就会来此判断一次,将其与7进行与运算后,若不为0,则返回 true,表示只回收八分之一的对象.


继续回到 pushnow 的流程,接下来判断 size 是否等于数组的容量.

因为 element 是一个数组,并不是一开始就创建maxcapacity 容量大小,若容量不够了,则进行两倍大小扩容,再将其加入数组.

1.2.3.2 异线程回收对象


本节食用指南

image.png

1.2.3.2.1 获取 WeakOrderQueue(以下简称WOQ)

由前面 pushnow 进入同线程回收, pushlater 即进入异线程回收过程.

image.png

image.png

先看看这么个东西是啥

image.png

其类型就很神奇了,首先是个FTL,即每个线程都有一个 map,map 的key=stack 表示对于不同线程,对不同 stack 来说对应于不同的WOQ.

那么它为何要定义成一个 map 结构呢,假设有3个线程T1/2/3;

T1创建的对象肯可能跑到T3中回收,T2中创建的对象也可能到了T3回收.

那么元素就是T1以及T2的WOQ.


image.png

假设当前在T2中,接下呢就通过get(this)拿到T1的WOQ,其中的 this 指的就是T1的 Stack.

然后若 queue==null,即表示T2从未回收过T1的对象,接下来开始判断

当前的即T2已经回收过的线程数 size,若不小于 mDQ,说明已经不能再回收其他线程的对象了!

给WOQ设置 dummy 标志,即对应下面的若下次看到一个线程标志了 dummy 直接return;什么也不做.


以上即为第一个过程,从FTL中拿一个Stack 对应的WOQ.

1.2.3.2.2 创建 WeakOrderQueue

若之前没拿到呢,那就直接创建一个WOQ吧!


接下来让我们看看一个线程创建WOQ是如何与待回收对象的Stack 进行绑定的.

image.png

其中的 this 即为 stack,是在T1中维护的,thread 即表示当前线程T2.

allocat就是为了给当前线程T2分配一个在T1中的Stack 对应的一个WOQ.

image.png

首先判断,T1中的 Stack还能否再分配LINK_capacity 个内存,若不能直接返回 null;

若可以,就直接 new 一个WOQ.

让我们具体看看其实现.

image.png

  • 此函数意义为:该 Stack 允许外部线程给它缓存多少个对象
    经过CAS操作设置该值为Stack 可为其他线程共享的回收对象的个数.

容量足够,则直接创建一个WOQ,下面来看看其数据结构.image.png

一个链表结构.将其 handle与Link 进行分离,极大地提升了性能,

因为不必判断当前T2能否回收T1的对象,而只需判断当前的L ink 中是否有空的,则可直接将 hande 塞进去.因为在前面一次性的判断过,从T1中是否能批量分配这么多对象(以减少很多操作的频率).

image.png

使用同步,将WOQ插到Stack 的头部.

image.png

1.2.3.2.3 将对象追加到 WeakOrderQueue


一开始呢,就是这么创建一个WOQ,默认有16个 handle

image.png

T2已经拿到queue,接着就是添加元素.

image.png

image.png

首先设置 上次回收 id.

image.png


该 id 为WOQ的 id,所以是以WOQ为基础的

image.png

然后拿到尾指针,获取Link 的长度,若已经等于 link_capacity,说明已经不可写了;

继续判断 想办法看看T1是否还能再分配一个Link来保存待回收的对象.

不允许,则直接丢弃;

允许,则直接创建Link并重新赋值 tail 节点.

image.png

创建完后,拿到其写指针,即 tail 的长度(0).所以 tail 节点也已经又有了足够的存储空间,将 handle 追加进去.再将该 handle 的 stack 指针重置为 null,因为已经不属于原来的 stack 了.

最后,写指针+1.

1.2.4 从 Recycler 获取对象

image.png

本节分析若当前 stack 为空

image.png

若当前线程T1去获取对象,若 stack 中有对象,则直接拿出.T1所拥有的对象即为T1拥有的 stack 中的对象,若发现其中为空,会尝试与 和T1的 stack 关联的WOQ中的 T1创建的,但是在其他线程中去回收的对象.那么,T1中对象不足,就需要在其他线程中去回收.


其中的 cusor 指针即当前所需要回收的对象


弹栈获取元素

image.png

若 size 为0,则从其他线程回收

image.png

若已经回收到则直接 return true.没有则重置两个指针,将 cusor 指向头结点,意味着准备从头开始回收.

image.png

接下来具体分析这段长代码

    boolean scavengeSome() {
            WeakOrderQueue prev;
            // 先拿到 cusor
            WeakOrderQueue cursor = this.cursor;
            // cusor 节点无对象
            if (cursor == null) {
                prev = null;
                // 指向头结点
                cursor = head;
                // 头结点依旧为空,已经没有与之关联的WOQ,直接返回 false.
                if (cursor == null) {
                    return false;
                }
            } else {
                prev = this.prev;
            }
            boolean success = false;
            // 此处 do/while 循环只为去寻找与 stack 关联的WOQ,看看到底能不能碰到一个对象.
            do {
              // transfer 即为了将WOQ中的对象传输到 stack 中.成功获取则结束循环!
                if (cursor.transfer(this)) {
                    success = true;
                    break;
                }
        // 没有回收成功,则看往 cusor 的下一个节点
                WeakOrderQueue next = cursor.next;
                // owner 为与当前WOQ关联的一个线程(对应图中的T4)
                // 为空,说明T4已经不存在!随后即,做一些善后清理工作
                if (cursor.owner.get() == null) {
                    // If the thread associated with the queue is gone, unlink it, after
                    // performing a volatile read to confirm there is no data left to collect.
                    // We never unlink the first queue, as we don't want to synchronize on updating the head.
                    // 判断节点中是否还有数据
                    if (cursor.hasFinalData()) {
                      // 就需要想办法将数据传输到 stack 中
                        for (;;) {
                            if (cursor.transfer(this)) {
                                success = true;
                            } else {
                                break;
                            }
                        }
                    }
                    // 处理完该节点后,即将其删除,通过传统的指针的删除方法
                    if (prev != null) {
                        prev.setNext(next);
                    }
                  //  T4还存活,继续看后继节点.
                } else {
                    prev = cursor;
                }
                cursor = next;
      // cusor 为空时,诶就结束循环啦!
            } while (cursor != null && !success);
            this.prev = prev;
            this.cursor = cursor;
            return success;
        }
  • 下面看传输方法
        // transfer as many items as we can from this queue to the stack, returning true if any were transferred
        @SuppressWarnings("rawtypes")
        boolean transfer(Stack<?> dst) {
            // 先找到头结点
            Link head = this.head;
            if (head == null) {
              // WOQ无数据,直接返回 false
                return false;
            }
      // 说明当前Link 的所有数据已被取走.
            if (head.readIndex == LINK_CAPACITY) {
                if (head.next == null) {
                    return false;
                }
                this.head = head = head.next;
            }
      // 从该索引开始取对象
            final int srcStart = head.readIndex;
            int srcEnd = head.get();
           // 当前Link 要传输到 Stack 的对象个数.
            final int srcSize = srcEnd - srcStart;
            if (srcSize == 0) {
                return false;
            }

image.png

      // dst 为当前Stack
            final int dstSize = dst.size;
            // 将对象都传输到Stack 所需容量.
            final int expectedCapacity = dstSize + srcSize;
       // 所需容量大于Stack 的大小,
            if (expectedCapacity > dst.elements.length) {
              // 因此进行扩容
                final int actualCapacity = dst.increaseCapacity(expectedCapacity);
                // 可传输的最后一个对象
                srcEnd = min(srcStart + actualCapacity - dstSize, srcEnd);
            }
            if (srcStart != srcEnd) {
                final DefaultHandle[] srcElems = head.elements;
                final DefaultHandle[] dstElems = dst.elements;
                int newDstSize = dstSize;
                for (int i = srcStart; i < srcEnd; i++) {
                    DefaultHandle element = srcElems[i];
                    //为0说明未被回收过
                    if (element.recycleId == 0) {
                        element.recycleId = element.lastRecycledId;
                    } else if (element.recycleId != element.lastRecycledId) {
                        throw new IllegalStateException("recycled already");
                    }
                    srcElems[i] = null;
                    if (dst.dropHandle(element)) {
                        // Drop the object.
                        continue;
                    }
                    element.stack = dst;
                    dstElems[newDstSize ++] = element;
                }
                if (srcEnd == LINK_CAPACITY && head.next != null) {
                    // Add capacity back as the Link is GCed.
                    reclaimSpace(LINK_CAPACITY);
                    this.head = head.next;
                }
                head.readIndex = srcEnd;
                if (dst.size == newDstSize) {
                    return false;
                }
                dst.size = newDstSize;
                return true;
            } else {
                // The destination stack is full already.
                return false;
            }
        }

1.3 小结

image.png

image.png

参考

Java读源码之Netty深入剖析




目录
相关文章
|
7月前
Netty实战: HTTP文件列表服务器
Netty实战: HTTP文件列表服务器
83 0
|
7月前
|
Java Unix Linux
【Netty技术专题】「原理分析系列」Netty强大特性之Native transports扩展开发实战
当涉及到网络通信和高性能的Java应用程序时,Netty是一个强大的框架。它提供了许多功能和组件,其中之一是JNI传输。JNI传输是Netty的一个特性,它为特定平台提供了高效的网络传输。 在本文中,我们将深入探讨Netty提供的特定平台的JNI传输功能,分析其优势和适用场景。我们将介绍每个特定平台的JNI传输,并讨论其性能、可靠性和可扩展性。通过了解这些特定平台的JNI传输,您将能够更好地选择和配置适合您应用程序需求的网络传输方式,以实现最佳的性能和可靠性。
150 7
【Netty技术专题】「原理分析系列」Netty强大特性之Native transports扩展开发实战
|
7月前
|
网络协议 Java 测试技术
阿里内部Netty实战小册,值得拥有
Netty 是一个高性能的 Java 网络通信框架,简化了网络编程并涵盖了最新的Web技术。它提供了一种抽象,降低了底层复杂性,使得更多开发者能接触网络编程。Netty 因其易用性、高效性和广泛的应用场景受到推崇,适合互联网行业从业者学习,有助于理解和开发基于Netty的系统。免费的《Netty实战小册》详细介绍了Netty的各个方面,包括概念、架构、编解码器、网络协议和实际案例,帮助读者深入理解和应用Netty。如需完整版小册,可点击链接获取。
阿里内部Netty实战小册,值得拥有
|
4月前
|
编解码 NoSQL Redis
(十一)Netty实战篇:基于Netty框架打造一款高性能的IM即时通讯程序
关于Netty网络框架的内容,前面已经讲了两个章节,但总归来说难以真正掌握,毕竟只是对其中一个个组件进行讲解,很难让诸位将其串起来形成一条线,所以本章中则会结合实战案例,对Netty进行更深层次的学习与掌握,实战案例也并不难,一个非常朴素的IM聊天程序。
109 3
|
4月前
|
前端开发 网络协议
Netty实战巅峰:从零构建高性能IM即时通讯系统,解锁并发通信新境界
【8月更文挑战第3天】Netty是一款高性能、异步事件驱动的网络框架,适用于开发高并发网络应用,如即时通讯(IM)系统。本文将指导你利用Netty从零构建高性能IM程序,介绍Netty基础及服务器/客户端设计。服务器端使用`ServerBootstrap`启动,客户端通过`Bootstrap`连接服务器。示例展示了简单的服务器启动过程。通过深入学习,可进一步实现用户认证等功能,打造出更完善的IM系统。
194 1
|
4月前
|
移动开发 网络协议 算法
(十)Netty进阶篇:漫谈网络粘包、半包问题、解码器与长连接、心跳机制实战
在前面关于《Netty入门篇》的文章中,咱们已经初步对Netty这个著名的网络框架有了认知,本章的目的则是承接上文,再对Netty中的一些进阶知识进行阐述,毕竟前面的内容中,仅阐述了一些Netty的核心组件,想要真正掌握Netty框架,对于它我们应该具备更为全面的认知。
253 2
|
7月前
|
NoSQL Redis
Netty实战:模拟Redis的客户端
Netty实战:模拟Redis的客户端
102 0
|
7月前
|
分布式计算 前端开发 网络协议
13W字!腾讯高工手写“Netty速成手册”,3天能走向实战
在java界,netty无疑是开发网络应用的拿手菜。你不需要太多关注复杂的nio模型和底层网络的细节,使用其丰富的接口,可以很容易的实现复杂的通讯功能。
|
7月前
|
监控 网络协议 调度
Netty Review - 深入探讨Netty的心跳检测机制:原理、实战、IdleStateHandler源码分析
Netty Review - 深入探讨Netty的心跳检测机制:原理、实战、IdleStateHandler源码分析
464 0
|
7月前
|
编解码 前端开发 网络协议
Netty Review - ObjectEncoder对象和ObjectDecoder对象解码器的使用与源码解读
Netty Review - ObjectEncoder对象和ObjectDecoder对象解码器的使用与源码解读
171 0