一、引言
Netty 作为业界知名的高性能网络通信框架,其内置的对象池机制凭借出色的设计,展现了强大的能力。出于对这一机制的好奇与探究欲,开启了这篇文章的创作。
在本文中,我将尝试抛开 Netty 的源码,仅依据对象池的核心功能需求,从零开始手写一个简易的对象池实现。这是一次对对象池工作原理的实践探索,通过在设计过程中可能遇到的关键问题,感受其中的设计考量与难点。
而在后面的文章里,把目光投向 Netty 的源码深处,对其对象池实现进行细致解析。通过这样 “实践先行、源码印证” 的方式,对比自己手写过程中遇到的困惑与挑战,从而更深刻地理解 Netty 对象池的精髓所在。也为大家在高性能系统开发中的内存管理实践提供一些有益的参考。
二、需求列表
-
有获取和回收两个功能(由使用者执行获取对象和回收对象)
-
并发控制(避免多线程操作的逻辑错误)
-
设置最大上限(最大并发数)
-
对象生命周期管理(长时间未被使用需要释放)
-
内存管理优化(使用堆外内存,避免JVM的创建和回收动作)
三、问题与解决思路
-
有获取和回收两个功能
通过 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;
}
-
并发控制
线程安全设计:内部通过锁或 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方法同理
-
设置最大上限
通过配置 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();
}
}
// 其他策略见后面详细代码
-
对象生命周期管理
空闲对象淘汰:对于长时间未被使用的对象,池会通过定时任务或惰性检查进行淘汰(如基于 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();
}
-
内存管理优化
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);
}
}