面试官:说说Netty对象池的实现原理?

netty · 浏览次数 : 0

小编点评

本文主要介绍了Netty框架中对象池技术的重要性、实现原理以及在Netty中的应用场景。 首先,什么是对象池技术,它是一种用于重用对象的方法,旨在减少对象创建和销毁的开销。通过维护一个对象池,可以在需要时直接从池中获取对象,而不需要频繁创建新对象。这种技术的优点包括提高性能(减少对象的创建和销毁次数),减少内存碎片(避免频繁创建和销毁对象)以及避免频繁GC(减少垃圾回收的频率)。 在Netty中,对象池技术是通过Recycler类实现的。Recycler主要提供了三个方法:get()用于获取可重复使用的对象;recycle(T, Handle)用于回收一个对象;newObject(Handle)用于在对象池中没有可用对象时创建新的对象。 对象池技术在Netty中的应用主要体现在PooledHeapByteBuf和PooledDirectByteBuf这两个类中,它们分别用于管理堆内存和堆外内存中的ByteBuf对象。此外,ChannelOutboundBuffer.Entry也是一个重要的类,它用于包装Netty出站缓冲区中的消息。 Netty对象池技术的实现原理涉及到了四大核心组件:Stack(栈)、WeakOrderQueue(弱序队列)、Link(链表)和DefaultHandle(对象的包装类)。这些组件的协同工作使得Netty能够高效地重用对象,减少内存分配和垃圾收集的开销。 最后,文章还提到了线程如何获取对象的过程,包括直接从Stack获取对象、从WeakOrderQueue获取对象以及当Stack和WeakOrderQueue都为空时创建新对象的情况。 总的来说,Netty通过对象池技术有效地管理了内存资源,提升了网络通讯的效能。

正文

Netty 作为一个高性能的网络通讯框架,它内置了很多恰夺天工的设计,目的都是为了将网络通讯的性能做到极致,其中「对象池技术」也是实现这一目标的重要技术。

1.什么是对象池技术?

对象池技术是一种重用对象以减少对象创建和销毁带来的开销的方法。在对象池中,只有第一次访问时会创建对象,并将其维护在内存中,当再次需要使用对象时,会直接从对象池中获取对象,并在使用完毕后归还给对象池,而不是频繁地创建和销毁对象。

使用对象池技术的优点有以下几个:

  1. 提高性能:复用对象可以减少对象的创建和销毁次数,降低系统开销,提高系统性能和吞吐量。
  2. 减少内存碎片:对象池可以避免频繁地创建和销毁对象,减少内存碎片的产生,提高内存利用率。
  3. 避免频繁GC:减少了对象的创建和销毁,可以减少垃圾回收(GC)的频率,降低系统的负担,提高系统的稳定性。

2.对象池基本使用

Netty 对象池技术的核心实现类为 Recycler,Recycler 主要提供了以下 3 个方法:

  1. get():获取一个可重复使用的对象,如果对象池中有空闲对象,则返回其中一个;否则会创建一个新对象。
  2. recycle(T, Handle):回收一个对象,将对象放回对象池中以便下次复用。
  3. newObject(Handle):当对象池中没有可用对象时,此方法会被调用以创建新的对象实例。

接下来我们写一个 Recycler 对象池的使用 Demo,假设我们有一个 User 类,需要实现 User 对象的复用,具体实现代码如下:

public class UserRecyclerDemo {
    private static final Recycler<User> userRecycler = new Recycler<User>() {
        @Override
        protected User newObject(Handle<User> handle) {
            return new User(handle);
        }
    };

    static final class User {
        private String name;
        private Recycler.Handle<User> handle;
        public void setName(String name) {
            this.name = name;
        }
        public String getName() {
            return name;
        }
        public User(Recycler.Handle<User> handle) {
            this.handle = handle;
        }
        public void recycle() {
            handle.recycle(this);
        }
    }

    public static void main(String[] args) {
        User user1 = userRecycler.get(); 	// 1.从对象池获取 User 对象
        user1.setName("zhangsan"); 			// 2.设置 User 对象的属性
        user1.recycle(); 					// 3.回收对象到对象池
        User user2 = userRecycler.get(); 	// 4.从对象池获取对象
        System.out.println(user1 == user2);
        System.out.println(user2.getName());
    }
}

以上程序的执行结果如下:

true

zhangsan

从上述结果可以看出,当第一次调用 userRecycler.get() 时,因为对象池中尚未存在 user 对象,所以创建了 name 为“zhangsan”的对象。但第二次再调用 userRecycler.get() 时,因为对象池中已经存在了 user 对象,所以直接从对象池中取出了 user 对象,所以 user1==user2 时,得到的结果是 true。

3.对象池技术应用

在 Netty 中,使用 Recycler 对象池管理对象的常见类有以下几个:

  1. PooledHeapByteBuf:管理堆内存中的 ByteBuf 对象。
  2. PooledDirectByteBuf:管理堆外内存中的 ByteBuf 对象。
  3. ChannelOutboundBuffer.Entry:Netty 出站缓冲区(ChannelOutboundBuffer)中,每一个待发送的消息都包装在一个 Entry 对象中。

4.实现原理

要搞清楚 Netty 对象池技术的实现原理,就要搞清楚 Netty 对象池的核心组件,以及组件之间的关系。

Netty 对象池技术的实现依靠以下 4 大组件:

  1. Stack(栈):每个线程都关联一个 Stack(使用 FastThreadLocal 进行存储),用于存储和管理该线程回收的对象。Stack 中存储的是 DefaultHandle 对象,这些 DefaultHandle 对象包装了实际要重用的对象。Stack 是与线程绑定的,每个线程从自己的 Stack 中获取对象。
  2. WeakOrderQueue(弱序队列):当某个线程(非主线程)回收对象时,这些对象不会直接放入主线程的 Stack 中,而是放入 WeakOrderQueue 中。WeakOrderQueue 存储的是从其他线程回收的对象,这些对象被包装在 DefaultHandle 中。WeakOrderQueue 与 Stack 关联,但属于非主线程。当主线程的 Stack 为空时,会尝试从 WeakOrderQueue 中获取对象。
  3. Link(链表):WeakOrderQueue 中的存储单元,用于存储回收的对象。Link 中存储的是 DefaultHandle 对象数组,这些数组包含从其他线程回收的对象。
  4. DefaultHandle:对象的包装类,在 Recycler 中缓存的对象都会包装成 DefaultHandle 类。DefaultHandle 中存储了实际要重用的对象,以及与之相关的元数据。

简单来说,这 4 个组件的关系是,(每个)线程为了保证线程安全和高效性操作,所以会把使用的对象放到 Stack 栈中,且每个线程都有自己的 Stack 栈。当线程中的对象不再被使用时(也就是被回收时),并不会将回收对象直接放到 Stack 中(因为当前线程已经不再使用了),此时会将对象存放到 WeakOrderQueue 队列中,因为 WeakOrderQueue 队列相当于“线程共享的区域”,这样其他线程就可以方便的从 WeakOrderQueue 中获取对象进行重用了。而 WeakOrderQueue 中的存储单元是 Link 链表,它存储的是对象池中的包装对象 DefaultHandle,这就是这四大核心组件之间的关系。

5.线程如何获取对象?

在 Netty 中,获取对象池中对象的流程如下:

  1. 判断 Stack:线程首先会尝试从自己的 Stack 中获取对象。如果 Stack 中有对象,则直接弹出(pop)并返回。
  2. Stack 为空:如果 Stack 为空,线程会检查 WeakOrderQueue。如果 WeakOrderQueue 中有对象,则按照一定的规则(如“1/7规则”,每 7 个移动 1 个)将部分对象转移到 Stack 中,然后从 Stack 中弹出并返回。
  3. 创建新对象:如果 Stack 和 WeakOrderQueue 都为空,线程会调用 newObject() 方法创建一个新的对象,并包装成 DefaultHandle 后放入 Stack 中,然后返回该对象。

通过这样的设计,Netty 的 Recycler 对象池技术能够高效地重用对象,减少内存分配和垃圾收集的开销,提升性能。

课后思考

Netty 是如何利用池化技术管理内存的?讲讲它的具体实现?

本文已收录到我的面试小站 www.javacn.site,其中包含的内容有:Redis、JVM、并发、并发、MySQL、Spring、Spring MVC、Spring Boot、Spring Cloud、MyBatis、设计模式、消息队列等模块。

与面试官:说说Netty对象池的实现原理?相似的内容:

面试官:说说Netty对象池的实现原理?

Netty 作为一个高性能的网络通讯框架,它内置了很多恰夺天工的设计,目的都是为了将网络通讯的性能做到极致,其中「对象池技术」也是实现这一目标的重要技术。 1.什么是对象池技术? 对象池技术是一种重用对象以减少对象创建和销毁带来的开销的方法。在对象池中,只有第一次访问时会创建对象,并将其维护在内存中

面试官:说说Netty的核心组件?

Netty 核心组件是指 Netty 在执行过程中所涉及到的重要概念,这些核心组件共同组成了 Netty 框架,使 Netty 框架能够正常的运行。 Netty 核心组件包含以下内容: 启动器 Bootstrap/ServerBootstrap 事件循环器 EventLoopGroup/EventL

美团面试:说说Netty的零拷贝技术?

零拷贝技术(Zero-Copy)是一个大家耳熟能详的技术名词了,它主要用于提升 IO(Input & Output)的传输性能。 那么问题来了,为什么零拷贝技术能提升 IO 性能? 1.零拷贝技术和性能 在传统的 IO 操作中,当我们需要读取并传输数据时,我们需要在用户态(用户空间)和内核态(内核空

抖音面试:说说延迟任务的调度算法?

Netty 框架是以性能著称的框架,因此在它的框架中使用了大量提升性能的机制,例如 Netty 用于实现延迟队列的时间轮调度算法就是一个典型的例子。使用时间轮调度算法可以实现海量任务新增和取消任务的时间度为 O(1),那么什么是时间轮调度算法呢?接下来我们一起来看。 1.延迟任务实现 在 Netty

面试官:在原生input上面使用v-model和组件上面使用有什么区别?

前言 还是上一篇面试官:来说说vue3是怎么处理内置的v-for、v-model等指令? 文章的那个粉丝,面试官接着问了他另外一个v-model的问题。 面试官:vue3的v-model都用过吧,来讲讲。 粉丝:v-model其实就是一个语法糖,在编译时v-model会被编译成:modelValue

终于搞懂了!原来 Vue 3 的 generate 是这样生成 render 函数的

前言 在之前的 面试官:来说说vue3是怎么处理内置的v-for、v-model等指令? 文章中讲了transform阶段处理完v-for、v-model等指令后,会生成一棵javascript AST抽象语法树。这篇文章我们来接着讲generate阶段是如何根据这棵javascript AST抽象

关于面试被面试官暴怼:“几年研究生白读” 的前因后果

中午一个网友来信说自己和面试官干起来了,看完他的描述真是苦笑不得,这年头是怎么了,最近互联网CS消息满天飞,怎么连面试官都SB起来了呢? 大概是这样的:这位网友面试时被问及了Serializable接口的底层实现原理,因为这是一个标识性的空接口,大部分同学在学习时都秉持着会用就行(说实话,Build

面试官让列举Spring的事务会失效的场景,我说了8个

今天,我们就一起梳理下有哪些场景会导致Spring事务失效。

面试官:字节流可以处理一切文件为什么还需要字符流呢?

一、写在开头 在计算机领域中百分之九十以上的程序拥有着和外部设备交互的功能,这就是我们常说的IO(Input/Output:输入/输出),所谓输入就是外部数据导入计算机内存中的过程,输出则是将内存或者说程序中的数据导入到外部存储中,如数据库、文件以及其他本地磁盘等。 二、什么是IO流 这种输入输出往

面试官:Java中缓冲流真的性能很好吗?我看未必

一、写在开头 上一篇文章中,我们介绍了Java IO流中的4个基类:InputStream、OutputStream、Reader、Writer,那么这一篇中,我们将以四个基类所衍生出来,应对不同场景的数据流进行学习。 二、衍生数据流分类 我们上面说了java.io包中有40多个类,都从InputS