为什么会有对象池
在实际的应用工程当中,存在一些被频繁使用的、创建或者销毁比较耗时、持有的资源也比较昂贵的一些对象。比如:数据库连接对象、线程对象。所以如果能够通过一种方式,把这类对象统一管理,让这类对象可以被循环利用的话,就可以减少很多系统开销(内存、CPU、IO等),极大的提升应用性能。
Apache Commons Pool
Apache Commons Pool就是一个对象池的框架,他提供了一整套用于实现对象池化的API,以及若干种各具特色的对象池实现。Apache Commons Pool是很多连接池实现的基础,比如DBCP连接池、Jedis连接池等。
Apache Commons Pool有个两个大版本,commons-pool和commons-pool2。commons-pool2是对commons-pool的重构,里面大部分核心逻辑实现都是完全重写的。我们所有的源码分析都是基于commons-pool2。
在commons-pool2中,对象池的核心接口叫做ObjectPool,他定义了对象池的应该实现的行为。
- addObject方法:往池中添加一个对象。池子里的所有对象都是通过这个方法进来的。
- borrowObject方法:从池中借走到一个对象。借走不等于删除。对象一直都属于池子,只是状态的变化。
- returnObject方法:把对象归还给对象池。归还不等于添加。对象一直都属于池子,只是状态的变化。
- invalidateObject:销毁一个对象。这个方法才会将对象从池子中删除,当然这其中最重要的就是释放对象本身持有的各种资源。
- getNumIdle:返回对象池中有多少对象是空闲的,也就是能够被借走的对象的数量。
- getNumActive:返回对象池中有对象对象是活跃的,也就是已经被借走的,在使用中的对象的数量。
- clear:清理对象池。注意是清理不是清空,改方法要求的是,清理所有空闲对象,释放相关资源。
- close:关闭对象池。这个方法可以达到清空的效果,清理所有对象以及相关资源。
在commons-pool2中,ObjectPool的核心实现类是GenericObjectPool。
在前面的文章中我已经解析过addObject、borrowObject、returnObject、invalidateObject方法的实现,对应链接地址:
- addObject方法解析:https://blog.youkuaiyun.com/weixin_42340670/article/details/107749058
- borrowObject方法解析:https://blog.youkuaiyun.com/weixin_42340670/article/details/106876879
- returnObject方法解析:https://blog.youkuaiyun.com/weixin_42340670/article/details/107851894
- invalidateObject方法解析:对象池:commons-pool2源码解析:GenericObjectPool的invalidateObject方法解析_老艮头的博客-优快云博客
通过以往的源码解析,本文来总结下GenericObjectPool常用参数的作用。
GenericObjectPool常用参数详解
参数 | 类型(默认值) | 描述 |
lifo | boolean(true) | 空闲队列是否维持后进先出,默认为true。 如果为false,就维持先进先出特性。 |
fairness | boolean(false) | 如果多个调用线程在等待获取对象,那么他们之间是否应该先到先得(公平),默认为false,也就是不维持先到先得的特性。 |
maxWaitMillis | long(-1) | 对象池没有可用空闲对象的时候,调用方最长等待多长时间(毫秒)。默认值 -1,表示无限等待。 如果配置为1000,表示最长等待1秒。超过1秒,给调用方返回超时异常。 |
maxTotal | int(8) | 对象池的最大容量。池里最多存放多少个对象 默认8。 |
maxIdle | int(8) | 对象池中最多可以有多少个空闲对象。 当对象被归还的时候,如果池中空闲对象数量已经达到该值,那么被归还的对象直接被销毁,不需要再进入空闲对象列表。 默认8。 |
minIdle | int(0) | 对象池中最少需要有几个空闲对象。 高负载的应用系统中,维持一定量的空闲对象可以减少对象频繁销毁、创建。减轻应用压力。 默认值为0,该值的有效范围n应该满足: 0 < n <= maxIdle 当异步的回收器线程回收完对象、或者调用方线程归还对象、以及销毁对象等操作后,都会重新检查并确保对象池中至少有minIdle个空闲对象。 |
timeBetweenEvictionRunsMillis | long(-1) | 回收器线程多久执行一次空闲对象回收(轮询间隔时间,单位毫秒)。 默认-1,意味着不启动回收器线程。 只有设置一个大于0的数值,才会启动回收器线程,同时下面要介绍的三个参数才会生效。否则,即便下面三个参数都指定了有效值,那么也不会产生实际效果。 如果配置为60000,意味着1分钟执行一次空闲回收。 |
numTestsPerEvictionRun | int(3) | 根据该值x可以推导出一个数值n,标识回收过程需要检查多少个空闲对象。 如果x>=0,那么n=x。 如果x<0,那么n=(空闲对象数量/x的绝对值)向上取整,假设空闲对象一共有10个,该值配置为-3,那么就意味着这次回收需要检查4个空闲对象。 |
softMinEvictableIdleTimeMillis | long(-1) | 软回收时间阈值 一个对象如果空闲时间超过了该值(毫秒),并且空闲对象的数量已经大于了minIdle时,就可以被回收器回收。 只有大于0的值,才被认为是一个有效值。默认值-1相当于Long.MAX_VALUE。 如果设置为120000,意味着如果一个对象空闲时间超过了2分钟,并且空闲对象数量大于minIdle,那么这个对象可以被回收。 |
minEvictableIdleTimeMillis | long(1000L * 60L * 30L) | 硬回收时间阈值 一个对象如果空闲时间超过了该值,不care空闲对象数量,即使空闲对象的数量已经小于minIdle了,一样也会被回收器回收。 默认值是30分钟。只有大于0的值,才被认为是一个有效值。小于0的值相当于Long.MAX_VALUE。 如果设置为180000,意味着如果一个对象空闲时间超过了3分钟,就可以被回收。 如果配置了minEvictableIdleTimeMillis,那么softMinEvictableIdleTimeMillis一定要小于minEvictableIdleTimeMillis才有意义,否则softMinEvictableIdleTimeMillis就相当于不生效。 |
testOnCreate | boolean(false) | 当往对象池里新加入一个新对象的时候,是否校验该新对象的有效性。 默认false,也就是不校验。 这个一般不建议设置为true,新建的对象,在很短的时间内失效的可能性很小。 |
testOnBorrow | boolean(false) | 当从对象池里借走一个对象的时候,是否校验该对象的有效性。 默认false,也就是不校验。 如果负载不是很高的系统,回收器轮询间隔又比较长的,该值可以设置为true,来保证获取到的连接一定是有效的。 |
testOnReturn | boolean(false) | 当往对象池里归还一个对象的时候,是否校验该对象的有效性。 默认值为false。 这个一般不建议设置为true,归还的对象校验有效性的意义不是很大,还会降低性能。重点保证获取到的对象是有效的就行。参见:testOnBorrow |
testWhileIdle | boolean(false) | 当回收器在扫描空闲对象时,是否校验对象的有效性。 如果某个对象空闲时间还没达到规定的阈值,如果testWhileIdle配置为true,那么就会检查该对象是否还有效,如果该对象的资源已经失效(例如:连接断开),那么他就可以被回收。 这个强烈建议设置为true。(要与回收器轮询间隔时间、检测的对象数量配合好)。 例如:在某个高负载的系统里,对象频繁被借出、被归还。 1、testOnBorrow、testOnReturn都设置为false,提升性能; 2、timeBetweenEvictionRunsMillis设置为60000,一分钟进行一次空闲对象的回收检查。 3、numTestsPerEvictionRun设置为-1,检查所有空闲对象。 4、minEvictableIdleTimeMillis设置为180000,空闲超过3分钟的可以被回收。 5、testWhileIdle设置为true,不管空闲时间是否超时,每个空闲对象都检查下有效性,无效的一样被回收。 通过异步的回收器来尽可能的保证空闲对象的有效性,减少同步调用时有效性检查导致的响应延迟、以及有效性检查对底层带来的访问压力。 |