手写一个Netty对象池-第一节

57 阅读9分钟

一、引言

Netty 作为业界知名的高性能网络通信框架,其内置的对象池机制凭借出色的设计,展现了强大的能力。出于对这一机制的好奇与探究欲,开启了这篇文章的创作。

在本文中,我将尝试抛开 Netty 的源码,仅依据对象池的核心功能需求,从零开始手写一个简易的对象池实现。这是一次对对象池工作原理的实践探索,通过在设计过程中可能遇到的关键问题,感受其中的设计考量与难点。

而在后面的文章里,把目光投向 Netty 的源码深处,对其对象池实现进行细致解析。通过这样 “实践先行、源码印证” 的方式,对比自己手写过程中遇到的困惑与挑战,从而更深刻地理解 Netty 对象池的精髓所在。也为大家在高性能系统开发中的内存管理实践提供一些有益的参考。

二、需求列表

  1. 有获取和回收两个功能(由使用者执行获取对象和回收对象)

  2. 并发控制(避免多线程操作的逻辑错误)

  3. 设置最大上限(最大并发数)

  4. 对象生命周期管理(长时间未被使用需要释放)

  5. 内存管理优化(使用堆外内存,避免JVM的创建和回收动作)

三、问题与解决思路

  1. 有获取和回收两个功能

通过 acquire() 方法从池中获取对象,release() 方法将对象归还给池,避免频繁创建和销毁对象带来的性能开销,同时在回收的时候要把对象初始化。

1.1 对象存储

我们的MyRecycler类有两个方法,acquire() 方法从池中获取对象,release() 方法将对象归还给池,于是我们就得到了方法定义:

public class MyRecycler<T> {

    private final List<T> list = new LinkedList<>();

    public T acquire() {
        
    }
    
    public void release(T t) {
    
    }
}

public class RecycleTest {
    private static final MyRecycler<User> RECYCLER = new MyRecycler<>();
    
    public static void main(String[] args) {
        User user = RECYCLER.acquire();
        RECYCLER.release(user);
        User user1 = RECYCLER.acquire();
        System.out.printIn(user == user1);
    }
}

在 Java 池化技术中,用于保存对象的数据结构选择通常取决于池的特性(如是否需要排序、并发安全性、对象复用策略等)。这里我就使用简单的策略,使用LinkedList作为存储对象的集合。

1.2 传入某类对象的构造方法

在acquire方法里,如果对象池的对象都为空了,这时需要调用方法创建出一个对象,那么在对象池初始化的时候,就要把类的构造方法传给对象池。

这里可以把构造方法作为一个参数传给对象池,比如:

private final Supplier<T> objectConstruction;

public MyRecycler(Supplier<T> objectConstruction) {
    this.objectConstruction = objectConstruction;
}

// 创建对象池
// User类的定义忽略,User类的无参构造作为Supplier传给MyRecycler
private static final MyRecycler<User> RECYCLER = new MyRecycler<>(User::new);

或者使用匿名内部类

// 在MyRecycler里面定义一个newObject的抽象方法
private static final MyRecycler<User> RECYCLER = new MyRecycler<>() {
    @Override
    protected User newObject() {
        ......
    }
};
1.3 回收的时候要把对象初始化

release() 时执行重置时,要确保复用的对象状态干净,需要恢复对象到初始值

这里考虑有两种方法,一种是我们在类里定义一个把对象的里成员显示赋值方法,一种是用反射的方法给成员赋值(获取当前类的所有字段,设置私有字段可访问,把字段设置为null)

方法优点缺点
自定义显式赋值方法直接操作字段,性能好维护成本高,新增字段需要改代码,且只适用于当前类
反射动态方法维护成本低,自动适配字段变化,适用所有类性能较低

考虑Netty的对象池主要服务Netty框架内部的,并且性能要求较高,所以使用自定义显式赋值方法:

// 无参构造
private final Supplier<T> objectConstruction;
// 自定义显式赋值方法给T类型初始化
private final Consumer<T> initialize;

public MyRecycler(Supplier<T> objectConstruction, Consumer<T> initialize) {
    this.objectConstruction = objectConstruction;
    this.initialize = initialize;
}
  1. 并发控制

线程安全设计:内部通过锁或 CAS 操作保证多线程环境下的 acquire() 和 release() 操作安全,避免对象竞争导致的状态异常。最简单的方式是选择ReentrantLock锁来进行并发控制。

private final ReentrantLock lock = new ReentrantLock();

public T acquire() {
    try {
        lock.lock();
        // 下面是业务逻辑
    } catch (Exception e) {
        log.error("......", e);
        throw e;
    } finally {
        lock.unlock();
    }
}

// release方法同理
  1. 设置最大上限

通过配置 maxCapacity 参数限制池中对象的最大数量(如 SimpleObjectPool 可在构造时指定)。当池内对象达到上限时,新的 acquire() 请求可能会:

阻塞等待(直到有对象被释放);

直接创建新对象(超出上限的临时对象,归还时可能被销毁而非入池);

抛出异常(根据具体实现的策略)。

private Strategy<T> strategy;

public T acquire() {
    try {
        lock.lock();
        // 已使用的 >= 最大上限
        if (used >= maxCapacity) {
            strategy.execute();
        }
        
        used++;
        // 构建新对象或者在集合里面取已有对象
    } catch (Exception e) {
        return null;
    } finally {
        lock.unlock();
    }
}

private interface Strategy<T> {
    T execute();
}

private class ExceptionStrategy implements Strategy<T> {
    @Override
    public T execute() {
        throw new RuntimeException();
    }
}
// 其他策略见后面详细代码
  1. 对象生命周期管理

空闲对象淘汰:对于长时间未被使用的对象,池会通过定时任务或惰性检查进行淘汰(如基于 maxTime 配置),避免资源长期占用。

在对象池对象被创建的时候,可以生成一个线程,专门用于检测对象的生命周期,只需一直去遍历存放对象的集合,判断当前时间➖对象被放入到集合的时间,是否大于maxTime即可。注意这里从LinkedList容器移除元素的时候,边遍历边移除要使用安全的方式,或者像下面代码里这样先获取到一个新的地方expiredObjects存储,再移除对应LinkedList容器的元素。

private long maxTime = 100000;

private final Map<T, Long> freeTimeMap = new HashMap<>();

{
    new Thread(() -> {
        while (true) {
            try {
                // 加锁,遍历容器获取需要清理的对象
                List<T> expiredObjects = getExpiredObjects();
                
                // 加锁,批量清理
                if (!expiredObjects.isEmpty()) {
                    removeExpiredObjects(expiredObjects);
                }
                
                Thread.sleep(500);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                break;
            } catch (Exception e) {
                log.error("清理线程异常", e);
            }
        }
    }).start();
}
  1. 内存管理优化

Netty 大量使用堆外内存(Off-Heap Memory)而非 JVM 堆内存,是由其作为高性能高频高并发、低延迟的核心设计目标决定的。堆外内存可以减少垃圾回收和内存拷贝的影响,能够解决传统堆内存在网络通信场景中的多项性能瓶颈。

对于实现对象池,我的想法是使用Java NIO 的ByteBuffer,可创建堆外内存,使用更安全且无需手动释放。但是只能使用byte[]字节数组的方式进行存储和获取,还需要使用序列化的方式转成JVM内的内存,没法直接在堆外内存上修改对象的属性,这是由堆外内存的 “原始字节存储” 特性决定的。

若想在堆外内存中直接操作对象(而非字节),需要结合额外的技术手段,所以这个问题放在下一篇文章里解决,看看Netty的怎么使用堆外内存的。

四、详细代码

下面是我没有详细了解Netty对象池源码, 自己写出的简易堆内对象池的详细代码

import lombok.extern.slf4j.Slf4j;

import java.util.*;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Consumer;
import java.util.function.Supplier;

/**
* 堆内对象池
*  @param <T>
 */
@Slf4j
public class MyRecycler<T> {

    /**
* 对象池允许的最大对象数量限制
*/
private final int limit;

    /**
* 当前正在使用中的对象数量
*/
private int used = 0;

    /**
* 当对象池达到上限时的处理策略
*/
private final Strategy<T> strategy;

    /**
* 重入锁,用于保证对象池操作的线程安全性
*/
private final ReentrantLock lock = new ReentrantLock();

    /**
* 与重入锁配合使用的条件变量,用于线程间的等待/通知机制
*/
private final Condition condition = lock.newCondition();

    /**
* 对象的最大空闲时间(毫秒),超过此时间将被清理线程回收
*/
private final long maxTime;

    /**
* 存储可用对象的池,采用链表结构便于元素的添加和移除
*/
private final List<T> pool = new LinkedList<>();

    /**
* 对象工厂,用于创建新的T类型对象
*/
private final Supplier<T> objectFactory;

    /**
* 对象初始化器,用于在对象归还到池时重置其状态
*/
private final Consumer<T> initializer;

    /**
* 记录空闲对象的释放时间,键为对象实例,值为释放到池中的时间戳
*/
private final Map<T, Long> freeTimeMap = new HashMap<>();

    /**
* 清理线程,负责定期检查并回收超过最大空闲时间的对象
*/
private final Thread cleanupThread;

    /**
* 初始化对象池
*  @param  objectFactory T类的无参构造方法
*  @param  limit 对象池最大可用对象数
*  @param  strategyType 超时策略
*  @param  initializer T类对象的初始化方法
*  @param  timeLimit 对象最大空闲时间
*/
public MyRecycler(Supplier<T> objectFactory, int limit, StrategyType strategyType, 
                     Consumer<T> initializer, long timeLimit) {
        this.objectFactory = objectFactory;
        this.limit = limit;
        this.initializer = initializer;
        this.maxTime = timeLimit;
        this.strategy = createStrategy(strategyType);
        this.cleanupThread = createCleanupThread();
        this.cleanupThread.start();
    }

    /**
* 获取对象
*  @return  T类型对象
*/
public T acquire() {
        lock.lock();
        try {
            // 优先从池中获取对象
            if (!pool.isEmpty()) {
                used++;
                T obj = pool.remove(0);
                freeTimeMap.remove(obj);
                return obj;
            }
            
            // 检查是否超过限制
            if (used >= limit) {
                return strategy.execute();
            }
            
            // 创建新对象
            used++;
            return objectFactory.get();
        } finally {
            lock.unlock();
        }
    }

    /**
* 归还对象
*  @param  obj 对象
*/
public void release(T obj) {
        if (obj == null) return;
        
        lock.lock();
        try {
            used--;
            
            // 如果是超出限制创建的对象,直接丢弃
            if (used >= limit) {
                condition.signalAll();
                return;
            }
            
            // 重置对象状态并放回池中
            initializer.accept(obj);
            pool.add(obj);
            freeTimeMap.put(obj, System.currentTimeMillis());
            condition.signalAll();
        } finally {
            lock.unlock();
        }
    }

    /**
* 创建策略实例
*/
private Strategy<T> createStrategy(StrategyType strategyType) {
        switch (strategyType) {
            case WAIT:
                return new WaitStrategy();
            case NEW_OBJECT:
                return new NewObjectStrategy();
            case EXCEPTION:
                return new ExceptionStrategy();
            default:
                throw new IllegalArgumentException("未知的策略类型: " + strategyType);
        }
    }

    /**
* 创建清理线程
*/
private Thread createCleanupThread() {
        Thread thread = new Thread(() -> {
            while (true) {
                try {
                    // 获取需要清理的对象
                    List<T> expiredObjects = getExpiredObjects();
                    
                    // 批量清理
                    if (!expiredObjects.isEmpty()) {
                        removeExpiredObjects(expiredObjects);
                    }
                    
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    break;
                } catch (Exception e) {
                    log.error("清理线程异常", e);
                }
            }
        });

        // 设置成守护线程
        thread.setDaemon(true);
        return thread;
    }

    /**
* 获取过期对象列表
*/
private List<T> getExpiredObjects() {
        List<T> expired = new ArrayList<>();
        long now = System.currentTimeMillis();
        
        lock.lock();
        try {
            for (T obj : pool) {
                Long freeTime = freeTimeMap.get(obj);
                if (freeTime != null && (now - freeTime) >= maxTime) {
                    expired.add(obj);
                }
            }
        } finally {
            lock.unlock();
        }
        
        return expired;
    }

    /**
* 移除过期对象
*/
private void removeExpiredObjects(List<T> expiredObjects) {
        lock.lock();
        try {
            for (T obj : expiredObjects) {
                if (pool.remove(obj)) {
                    freeTimeMap.remove(obj);
                    log.debug("超时释放对象: {}", obj);
                }
            }
        } finally {
            lock.unlock();
        }
    }

    /**
* 策略接口
*/
private interface Strategy<T> {
        T execute();
    }

    /**
* 等待策略
*/
private class WaitStrategy implements Strategy<T> {
        @Override
        public T execute() {
            try {
                // 等待直到有对象可用
                while (pool.isEmpty() && used >= limit) {
                    condition.await();
                }
                
                // 重新检查池中是否有对象
                if (!pool.isEmpty()) {
                    used++;
                    T obj = pool.remove(0);
                    freeTimeMap.remove(obj);
                    return obj;
                }
                
                // 如果仍然没有对象,创建新对象
                used++;
                return objectFactory.get();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                throw new RuntimeException("等待被中断", e);
            }
        }
    }

    /**
* 新建对象策略
*/
private class NewObjectStrategy implements Strategy<T> {
        @Override
        public T execute() {
            used++;
            return objectFactory.get();
        }
    }

    /**
* 异常策略
*/
private class ExceptionStrategy implements Strategy<T> {
        @Override
        public T execute() {
            throw new RuntimeException("对象池已达到最大容量限制: " + limit);
        }
    }

    /**
* 策略类型枚举
*/
public enum StrategyType {
        WAIT, NEW_OBJECT, EXCEPTION
}
}

测试方法:

import lombok.Data;

public class MyRecyclerTest {

    private static final MyRecycler<User> RECYCLER = new MyRecycler<>(User::new, 2, MyRecycler.StrategyType.WAIT, user -> user.name = null, 1000);

    @Data
    public static class User{

        /**
* 姓名
*/
private String name;

        public User() {
            System.out.println("user对象被创建了");
        }
    }

    public static void main(String[] args) throws InterruptedException {
        User user0 = RECYCLER.acquire();
        user0.setName("张三");
        User user1 = RECYCLER.acquire();
        user1.setName("李四");
        System.out.println(user1 == user0);
        RECYCLER.release(user0);
        User user2 = RECYCLER.acquire();
        System.out.println(user0 == user2);

        new Thread(() -> {
            User user3 = RECYCLER.acquire();
            System.out.println("阻塞结束,创建成功, user3 = " + user3.toString());
        }).start();

        Thread.sleep(1000);
        RECYCLER.release(user1);
    }
}