轩辕李的博客 轩辕李的博客
首页
  • Java
  • Spring
  • 其他语言
  • 工具
  • HTML&CSS
  • JavaScript
  • 分布式
  • 代码质量管理
  • 基础
  • 操作系统
  • 计算机网络
  • 编程范式
  • 安全
  • 中间件
  • 心得
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

轩辕李

勇猛精进,星辰大海
首页
  • Java
  • Spring
  • 其他语言
  • 工具
  • HTML&CSS
  • JavaScript
  • 分布式
  • 代码质量管理
  • 基础
  • 操作系统
  • 计算机网络
  • 编程范式
  • 安全
  • 中间件
  • 心得
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • Java

    • 核心

    • 并发

    • 经验

    • JVM

    • 企业应用

      • Freemarker模板实战指南
      • Servlet与JSP指南
      • Java日志系统
      • Java JSON处理
      • Java XML处理
      • Java对象池技术
        • 一、引言
          • 1、什么是对象池技术
          • 2、对象池技术的核心价值
        • 二、对象池的实现原理
          • 1、池子的初始化
          • 2、借用对象(Borrow)
          • 3、归还对象(Return)
          • 4、对象池的内部管理
        • 三、Java中的对象池技术
          • 1、Java对象池的分类
          • 2、Java对象池的经典应用
          • 2.1、线程池
          • 2.2、数据库连接池
          • 3、使用Apache Commons Pool2实现对象池
          • 3.1、核心组件介绍
          • 3.2、添加依赖
          • 3.3、创建对象工厂
          • 3.4、支持Key的对象池
          • 3.5、创建和配置对象池
          • 3.6、使用对象池
        • 四、对象池的局限性
          • 1、内存占用问题
          • 2、初始化成本
          • 3、线程安全的复杂性
          • 4、对象状态管理
          • 5、调试和监控的挑战
        • 五、总结
      • Java程序的单元测试(Junit)
      • Thymeleaf官方文档(中文版)
      • Mockito应用
      • Java中的空安全
  • Spring

  • 其他语言

  • 工具

  • 后端
  • Java
  • 企业应用
轩辕李
2023-07-16
目录

Java对象池技术

# 一、引言

# 1、什么是对象池技术

想象一下,你在餐厅吃饭时,服务员不会每次上菜都去买新盘子,而是用清洗干净的盘子重复使用。对象池技术就是这个道理——它是一种软件设计模式,预先创建一定数量的对象存放在"池子"里,需要时直接取用,用完后放回去继续循环使用。

这种做法的核心思想是对象重用。当程序需要对象时,不是每次都new一个新对象,而是从对象池中拿一个现成的,用完后清理状态再放回池中。这样就避免了频繁创建和销毁对象的开销,大大提升了系统性能。

除了性能提升,对象池还能很好地控制资源消耗。通过设置最大对象数量和最大空闲对象数量,可以防止系统资源被过度占用,起到"限流"的作用。

# 2、对象池技术的核心价值

使用对象池技术主要能带来这些好处:

性能提升:对象的创建和销毁往往涉及内存分配、构造函数执行、垃圾回收等开销。通过重用对象,可以显著减少这些成本,提高程序响应速度。

资源管控:通过限制池中对象的数量,可以避免程序在高并发场景下无限制地创建对象,防止内存溢出和系统崩溃。

内存优化:重复使用同一批对象,避免了大量相同对象的重复创建,有效节省了内存空间。

生命周期管理:对象池统一管理对象的初始化、激活、钝化和销毁过程,让对象的使用更加规范和可控。

在实际开发中,对象池技术广泛应用在数据库连接池、线程池、HTTP连接池等场景。可以说,哪里有高并发,哪里就离不开对象池技术。

# 二、对象池的实现原理

对象池的工作机制其实挺像银行的取号系统,我们来看看它是怎么运作的:

# 1、池子的初始化

系统启动时,对象池会预先创建一批对象放在容器里"待命",就像银行提前准备好一定数量的号码牌一样。这些对象都是干净的、可用的状态。

# 2、借用对象(Borrow)

当程序需要对象时,首先检查池子里有没有空闲的对象:

  • 如果有,直接拿一个出来用
  • 如果没有,就要看配置策略了:可能等待其他对象归还,也可能创建新对象,还可能直接抛异常

# 3、归还对象(Return)

用完对象后,不是直接扔掉,而是"还"给对象池。归还前,对象池会做一些清理工作:

  • 重置对象的状态
  • 清空可能残留的数据
  • 检查对象是否还能正常使用

# 4、对象池的内部管理

存储结构:通常用队列、栈或链表来管理对象,保证高效的存取操作。

线程安全:在多线程环境下,需要加锁或使用无锁算法,确保多个线程同时操作池子时不会出问题。

容量控制:设置最大对象数和最大空闲对象数,避免池子无限增长导致内存泄漏。

生命周期管理:定期检查对象的有效性,及时清理"坏掉"的对象,并在系统关闭时优雅地释放所有资源。

说白了,对象池就是通过"空间换时间"的策略,用一点额外的内存开销换取了大幅的性能提升。这种思路在高并发系统中特别有效。

# 三、Java中的对象池技术

# 1、Java对象池的分类

在Java生态中,我们有好几种方式来实现对象池,每种都有自己的特点:

手动实现对象池 这就像自己动手搭房子,完全按自己的需求来。优点是灵活性高,想怎么实现就怎么实现;缺点是要处理很多底层细节,比如线程安全、对象验证等,容易出Bug。

第三方库实现 用现成的轮子,比如Apache Commons Pool这样的成熟框架。这些库经过了大量项目的验证,功能齐全、性能可靠,配置选项也很丰富,是大多数项目的首选。

JDK标准库提供 Java自带了一些特定场景的对象池,最典型的就是线程池(ThreadPoolExecutor)。这些实现专门针对特定场景优化,使用起来简单直接。

# 2、Java对象池的经典应用

# 2.1、线程池

说到Java中的对象池,线程池绝对是最有名的那一个。

想想看,创建一个线程需要调用操作系统API,销毁线程也要回收系统资源,这些操作都很"重"。如果每次处理任务都新建线程,在高并发场景下系统很快就吃不消了。

线程池的做法是预先创建一批线程"待命",有任务来了就分配给空闲线程处理,处理完了线程继续等待下一个任务。这样既避免了频繁创建销毁线程的开销,又能控制并发线程数量。

具体的线程池使用方法,可以参考 Java并发-线程池ThreadPoolExecutor

# 2.2、数据库连接池

数据库连接池是另一个经典应用场景。

建立数据库连接涉及网络握手、身份验证、事务初始化等步骤,这个过程比较耗时。如果每次数据库操作都重新建连接,不仅慢,还可能因为连接数过多把数据库给压垮了。

连接池预先建立一批数据库连接,程序需要时就从池里借一个,用完再还回去。这样既保证了性能,又限制了数据库的连接数。

Java生态中有很多优秀的连接池实现:

  • HikariCP (opens new window):号称最快的连接池,Spring Boot默认选择
  • Druid (opens new window):阿里开源,监控功能强大
  • Tomcat JDBC Pool (opens new window):Tomcat内置的连接池

# 3、使用Apache Commons Pool2实现对象池

Apache Commons Pool2是目前最成熟的Java对象池框架,功能强大且经过了大量生产环境的验证。让我们来看看怎么用它搭建自己的对象池。

# 3.1、核心组件介绍

Commons Pool2的设计很清晰,主要包含这几个核心部分:

ObjectPool:对象池的操作接口,定义了borrowObject()(借用对象)和returnObject()(归还对象)等基本方法。

PooledObjectFactory:对象工厂接口,负责对象的创建、激活、钝化和销毁。这是你需要自己实现的部分。

PooledObject:池化对象包装器,封装了实际的对象并记录其状态信息。

GenericObjectPool:通用对象池实现,提供了丰富的配置选项,是最常用的对象池实现。

# 3.2、添加依赖

首先在项目中加入Commons Pool2的依赖:

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
    <version>2.11.1</version>
</dependency>

# 3.3、创建对象工厂

要使用对象池,首先得教它怎么创建对象。最简单的方法是继承BasePooledObjectFactory:

public interface PooledObjectFactory<T> {
    PooledObject<T> makeObject();          // 创建对象
    void activateObject(PooledObject<T> obj);    // 激活对象(从池中取出时调用)
    void passivateObject(PooledObject<T> obj);   // 钝化对象(归还到池中时调用)
    boolean validateObject(PooledObject<T> obj); // 验证对象是否可用
    void destroyObject(PooledObject<T> obj);     // 销毁对象
}

不过,直接实现接口有点麻烦,更简单的方法是继承BasePooledObjectFactory,只需要实现两个方法:

package test.test;

import org.apache.commons.pool2.BasePooledObjectFactory;
import org.apache.commons.pool2.PooledObject;
import org.apache.commons.pool2.impl.DefaultPooledObject;

public class StringBufferFactory extends BasePooledObjectFactory<StringBuffer> {

    @Override
    public StringBuffer create() throws Exception {
        // 创建实际的对象
        return new StringBuffer();
    }

    @Override
    public PooledObject<StringBuffer> wrap(StringBuffer obj) {
        // 用DefaultPooledObject包装对象
        return new DefaultPooledObject<>(obj);
    }
}

# 3.4、支持Key的对象池

有时候我们需要根据不同的key获取不同配置的对象,比如不同数据库的连接池。这时候可以使用BaseKeyedPooledObjectFactory:

public class DatabaseConnectionFactory extends BaseKeyedPooledObjectFactory<String, Connection> {
    
    @Override
    public Connection create(String key) throws Exception {
        // 根据key(比如数据库名)创建不同的连接
        return DriverManager.getConnection("jdbc:mysql://localhost/" + key);
    }

    @Override
    public PooledObject<Connection> wrap(Connection obj) {
        return new DefaultPooledObject<>(obj);
    }
}

这样就可以通过不同的key获取到对应的对象,而不需要为每种配置都创建单独的对象池。

# 3.5、创建和配置对象池

有了对象工厂,接下来就要创建实际的对象池了。Commons Pool2提供了几种现成的实现:

GenericObjectPool:最常用的通用对象池,支持丰富的配置选项。

GenericKeyedObjectPool:支持key的对象池,可以根据不同key管理不同类型的对象。

SoftReferenceObjectPool:基于软引用的对象池,当内存不足时池中的对象可以被GC回收。

最简单的用法:

// 创建一个基本的对象池
GenericObjectPool<StringBuffer> pool = new GenericObjectPool<>(new StringBufferFactory());

当然,我们通常需要做一些配置来满足实际需求:

// 创建配置对象
GenericObjectPoolConfig<StringBuffer> config = new GenericObjectPoolConfig<>();
config.setMaxTotal(20);        // 最大对象数
config.setMaxIdle(10);         // 最大空闲对象数  
config.setMinIdle(5);          // 最小空闲对象数
config.setTestOnBorrow(true);  // 借用时验证对象
config.setTestOnReturn(true);  // 归还时验证对象

// 创建配置好的对象池
GenericObjectPool<StringBuffer> pool = new GenericObjectPool<>(new StringBufferFactory(), config);

# 3.6、使用对象池

使用对象池的模式很固定,就是"借用-使用-归还"三步走:

StringBuffer buffer = null;
try {
    // 第一步:从池中借用对象
    buffer = pool.borrowObject();
    
    // 第二步:使用对象进行业务操作
    buffer.append("Hello World");
    System.out.println(buffer.toString());
    
} catch (Exception e) {
    // 处理异常
    throw new RuntimeException("操作失败: " + e.getMessage());
} finally {
    // 第三步:归还对象到池中(这一步很重要!)
    if (buffer != null) {
        try {
            pool.returnObject(buffer);
        } catch (Exception e) {
            // 归还失败通常可以忽略,但最好记录日志
            // logger.warn("归还对象到池中失败", e);
        }
    }
}

注意事项:

  • finally块中的归还操作绝对不能忘记,否则会导致对象泄漏
  • 即使业务逻辑出现异常,也要确保对象能正确归还
  • 可以考虑使用try-with-resources模式来简化代码

# 四、对象池的局限性

凡事都有两面性,对象池技术虽然优点很多,但也有一些需要注意的地方:

# 1、内存占用问题

对象池本质上是用空间换时间,需要预先创建并持有一批对象。如果配置不当,可能出现:

  • 池子太大:占用过多内存,在内存敏感的环境下可能成为负担
  • 池子太小:频繁创建新对象,达不到预期的性能提升效果

# 2、初始化成本

系统启动时需要预先创建对象,如果对象的创建比较"重"(比如需要初始化大量数据),可能会拖慢系统的启动速度。

# 3、线程安全的复杂性

多线程环境下,对象池必须保证线程安全,这需要额外的同步机制。虽然现有的框架已经处理了这些问题,但在自己实现对象池时需要格外小心。

# 4、对象状态管理

池中的对象被重复使用,必须确保每次归还时对象都是"干净"的状态。如果对象有复杂的状态或者存在依赖关系,状态重置可能会比较麻烦。

# 5、调试和监控的挑战

对象在池中循环使用,这给问题排查带来了一定困难。需要额外的监控手段来观察池的健康状况。

总的来说,对象池技术是一把双刃剑,用好了能大幅提升性能,用不好可能适得其反。关键是要根据实际场景合理配置和使用。

# 五、总结

对象池技术是Java开发中的一项重要优化手段,它通过对象重用有效解决了频繁创建销毁对象带来的性能问题。

核心思想很简单:预先创建一批对象放在池子里,需要时取用,用完归还,循环利用。

应用场景很广泛:从我们最熟悉的线程池、数据库连接池,到各种自定义的对象池,都体现了这种思想。

实现框架很成熟:Apache Commons Pool2提供了完整的解决方案,让我们可以专注于业务逻辑而不用操心底层细节。

注意合理使用:虽然对象池好处多多,但也要考虑内存占用、配置复杂度等因素,不能滥用。

掌握了对象池技术,你就多了一个性能优化的利器。在设计高并发、高性能系统时,合理运用对象池往往能起到事半功倍的效果。希望这篇文章能帮你更好地理解和应用这项技术!

祝你变得更强!

编辑 (opens new window)
#Java对象池
上次更新: 2025/08/16
Java XML处理
Java程序的单元测试(Junit)

← Java XML处理 Java程序的单元测试(Junit)→

最近更新
01
AI时代的编程心得
09-11
02
Claude Code与Codex的协同工作
09-01
03
Claude Code实战之供应商切换工具
08-18
更多文章>
Theme by Vdoing | Copyright © 2018-2025 京ICP备2021021832号-2 | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式