1.对象池
内存优化不仅要防止内存泄露,也要注意频繁GC卡顿、内存抖动以及不必要的内存开销造成的内存需求过大或者内存泄露。
比如,如果有大量临时对象的创建该如何处理呢?
首先要确定问题发生的原因,对象频繁创建一般有以下几种可能:
①在循环操作中创建对象
②在频繁被调用的方法中创建对象
例如:在onDraw中使用一些对象,由于onDraw方法会被程序频繁调用,所以不能在onDraw方法里面创建对象实例,可以考虑在onDraw方法外提前初始化这些对象。
能直接避免对象的频繁创建当然最好,但是有时候这些对象的初始化是不可避免的,那么就要考虑对象的复用,可以采用对象池来解决问题。对象池可以很好的复用对象,避免频繁创建和销毁。
对象池的目的就是减少频繁创建和销毁对象带来的成本,实现对象的缓存和复用。当创建对象的成本比较大,并且创建比较频繁时,就需要使用对象池了。
Android中使用对象池的应用有很多,比如:
①Handler处理的Message(在handler发送消息时,Message的初始化经常会用 Message.obtain()来实例化Message对象)
②线程池执行器ThreadPoolExecutor
③Glide
④控制TypedArray的Resources
虽然它们对线程池的实现方式不同(Message 使用链表、Glide使用Map),但是原理是一样的。即:初始化一个固定大小的池子,每次创建对象时先去池子中找有没有,如果有直接取出,没有就new出来使用后还到池子里。这样便可达到对象复用的目的。
以一个简单的获取StyledAttributions代码为例,展示一下对象池的应用:
final TypedArray ta = context.obtainStyledAttributes(mTabTextAppearance,R.styleable.TextAppearance);
try {
mTabTextSize = ta.getDimensionPixelSize( R.styleable.TextAppearance_android_textSize, 0);
mTabTextColors = ta.getColorStateList( R.styleable.TextAppearance_android_textColor);
} finally {
ta.recycle();
}
每次使用TypedArray时都会特别注意:TypedArray结束的时候,一定要调用它的recycle方法。而调用recycle方法的原因很简单:它使用了对象池。调用者通过obtain方法从对象池中获取对象,使用完毕后,就需要使用recycle方法返还给对象池。
2.对象池工作过程
Pool表示对象池,用于存储可重复利用的对象。
第②步操作就是取出对象。
第③步操作是使用取出的对象来完成一些任务。这时候并不需要对象池再做什么,但是这也意味着该对象将被租借一段时间并且不能再被其他组件借出。
第④步操作就是归还,组件归还借出的对象,这样可以继续满足其他的租借请求。
在一个多线程应用中,②、③、④操作都有可能发生并发操作。多线程的组件中分享对象导致了潜在的并发问题。也存在一种情况就是当所有对象都被借出时不能满足接下来的请求,对象池必须应对这些请求,不管是告诉组件已经没有对象可借还是允许组件等待直到有归还的对象。
注意:
①使用的时候,申请 (obtain) 和释放 (recycle) 成对出现,使用一个对象后一定要释放还给池子。
②池子的大小要根据实际情况合理指定。池子太大会使对象不释放时占用的内存很大;池子太小对象过多而且因为操作耗时而不能立即释放还给池子时候,池子满了,后续对象还是不能复用。所以,根据项目实际场景制定合理的大小是很必要的。
3.对象池的实现
(1)通过数组实现--Pools
在Android的v4包中有个类Pools,其中定义了一个对象池接口Pool,具体的实现有两个SimplePool和SynchronizedPool,一个是非线程安全的,另一个是线程安全的。
①Pool 接口类
public interface Pool<T> {
T acquire();
boolean release(T instance);
}
acquire(): 从对象池请求对象。注意:有可能会返回null。
release(): 释放对象并放入对象池。return true表示释放的对象成功放入对象池;return false表示对象没有成功地放到对象池中。如果参数对象 instance已经在对象池中,则会抛出IllegalStateException异常。
注意:如果只是一味地从对象池中获取对象(调用acquire()),而不向对象池中放入对象(调用release(T instance)),那么通过acquire()得到的对象,有可能为null,也有可能是new出来的,并没有达到缓存对象的目的,所以在使用完对象以后,要调用release(T instance)及时地将对象释放到对象池。
②SimplePool
这是Android官方对象池的简单实现,也是用得最多的实现。这是对象池的非同步实现。
原理:使用了 “懒加载” 的思想。当SimplePool初始化时,不会生成N个T类型的对象存放在对象池中。而是当每次外部调用release()时,才把释放的T类型对象存放在对象池中。要先放入,才能取出来。
public static class SimplePool<T> implements Pool<T> {
private final Object[] mPool; //对象池中真正用于存储对象的数组(使用数组实现对象池)
private int mPoolSize; //对象池内的对象个数
public SimplePool(int maxPoolSize) {
if (maxPoolSize <= 0) {
throw new IllegalArgumentException( "The max pool size must be > 0");
}
mPool = new Object[maxPoolSize]; //初始化对象数组
}
//从mPool数组中取出mPoolSize - 1位置上的对象
@Override
public T acquire() {
if (mPoolSize > 0) {
final int lastPooledIndex = mPoolSize - 1;
T instance = (T) mPool[lastPooledIndex];
mPool[lastPooledIndex] = null;
mPoolSize--;
return instance;
}
return null;
}
//回收的对象放入mPool数组的mPoolSize位置
@Override
public boolean release(T instance) {
if (isInPool(instance)) {
throw new IllegalStateException( "Already in the pool!");
}
if (mPoolSize < mPool.length) {
mPool[mPoolSize] = instance;
mPoolSize++;
return true;
}
return false;
}
//判断对象是否已存在于对象池中
private boolean isInPool(T instance) {
for (int i = 0; i < mPoolSize; i++) {
if (mPool[i] == instance) {
return true;
}
}
return false;
}
}
acquire()方法,首先判断此时对象池中有没有缓存的对象。如果有,则从mPool数组中取最后一个缓存对象并返回,同时将数组最后一个引用置为null,mPoolSize --;如果没有,则直接返回null。
release(T instance)方法,首先判断此对象是不是已经缓存在对象池中。如果已经在对象池中则直接抛出异常。如果缓存的对象个数没有超过mPool数组的长度,则将对象放到mPool数组的最后一位,并将mPoolSize++,return true;如果缓存的对象个数超过mPool数组的长度,则直接 return false,参数对象instance也不会放入到缓存数据mPool中。
③SynchronizedPool 对象池的同步实现
public static class SynchronizedPool<T> extends SimplePool<T> {
private final Object mLock = new Object(); //用于同步加锁的对象
public SynchronizedPool(int maxPoolSize) {
super(maxPoolSize);
}
@Override
public T acquire() {
synchronized (mLock) {
return super.acquire();
}
}
@Override
public boolean release(T element) {
synchronized (mLock) {
return super.release(element);
}
}
}
线程安全的实现类是非线程安全的实现类SimplePool的子类,只是在构造方法、acquire()和release(T element)方法中,通过synchronized加锁实现线程安全的。
(2)通过链表实现--Message
Message是通过链表的方式实现对象池的。
通过Message.obtain()方法可以从缓存(其实就是Message对象池)中获取Message对象。但是如果只是通过Message.obtain()得到Message对象,然后使用Message对象,用完就完了,这样的使用方式并没有达到缓存Message对象的作用。正确的做法是,在使用完Message对象以后,应该主动地调用message.recycle()方法回收Message对象,将Message对象回收进Message对象池中,这样以后才可以通过Message.obtain()继续复用这个Message对象。
Message 源码分析:
首先看一下Message类中和Message对象池相关的方法和变量。
①obtain()方法,它的作用是通过调用obtain()得到一个在Message对象池中的Message对象,避免通过new的方式新建Message对象。底层是通过一个单链表sPool对象实现的,这个sPool单链表和平时使用的单链表略有不同,不同之处在于sPool是在链表的头部插入和删除链表节点的,而不是在尾部。
②recycle()方法,它的作用是将Message对象回收进Message对象池,其实真正起作用的是recycleUnchecked()方法,在recycleUnchecked()开始会将Message中的变量复位,包括flags = FLAG_IN_USE以表明此对象现在正在使用中,然后同步地将此Message对象插入到sPool链表的头部,并将头节点指向此Message对象,最后将sPoolSize++。
③同步是通过sPoolSync变量和synchronized实现的。
注意:其中几个关键的变量-链表sPool、链表长度sPoolSize都是static的,表明在整个Android应用中共享此Message对象池,而且Message对象最大长度是 50 个。
public final class Message implements Parcelable {
......
Message next;
private static final Object sPoolSync = new Object();
private static Message sPool;
private static int sPoolSize = 0;
private static final int MAX_POOL_SIZE = 50;
......
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next; 头节点的下一个节点赋值给sPool
m.next = null; //将取出来的m与链表断开,此时上面的sPool变成新的头节点
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
return new Message();
}
public void recycle() {
if (isInUse()) {
if (gCheckRecycle) {
throw new IllegalStateException("This message cannot be recycled because it is still in use.");
}
return;
}
recycleUnchecked();
}
/*Recycles a Message that may be in-use.Used internally by the MessageQueue and Looper when disposing of queued Messages.*/
void recycleUnchecked() {
flags = FLAG_IN_USE;
what = 0;
arg1 = 0;
arg2 = 0;
obj = null;
replyTo = null;
sendingUid = -1;
when = 0;
target = null;
callback = null;
data = null;
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool; //sPool本来是头节点,现在让它指向下一个节点
sPool = this;//当前节点赋值给sPool,所以现在release掉的节点变成了头节点sPool
sPoolSize++;
}
}
}
}
(3) 通过Map实现BitmapPool--Glide
通过Map的方式实现对象池,这里用Glide中缓存Bitmap的BitmapPool说明。
Glide是用于加载图片的库,提到图片肯定绕不开Bitmap,而Bitmap又是吃内存的能手,所以使用BitmapPool缓存Bitmap对象,避免重复创建Bitmap过渡消耗内存就变得十分重要了。
先看一下Glide中的目录结构,如下图所示,用红色框起来的几个类都是和BitmapPool相关的类。
①BitmapPool接口定义
public interface BitmapPool {
long getMaxSize(); //返回当前BitmapPool的最大容量,单位是 byte 字节
void setSizeMultiplier(float sizeMultiplier); //可以通过此方法设置一个因子,此因子会乘以最开始设置的最大容量,将结果作为新的最大容量。计算完成以后,如果BitmapPool的当前实际容量比当前最大容量大,则会清除BitampPool中的对象,直到当前实际容量小于当前最大容量。可以通过此方法动态地、同步地调整BitmapPool最大容量
void put(Bitmap bitmap); //将此Bitmap对象添加到对象池中。如果符合条件,BitmapPool会修改并重用,否则会调用Bitmap.recycle()方法抛弃此Bitmap对象。调用此方法将此Bitmap对象添加到对象池中以后,此Bitmap对象就不能再被使用了
Bitmap get(int width, int height, Bitmap.Config config); //通过此方法可以得到一个width、height、config参数都符合条件的Bitmap,此Bitmap对象中只包含透明度的色值。如果 BitmapPool中没有符合此条件的Bitmap缓存对象,则会创建一个新的Bitmap对象并返回。通过此对象得到的Bitmap对象,会调用 bitmap.eraseColor(Color.TRANSPARENT) 方法清除bitmap中所有的像素,只保留透明度像素,这也是和getDirty(int width, int height, Bitmap.Config config)方法的主要区别
Bitmap getDirty(int width, int height, Bitmap.Config config);
void clearMemory(); //清除BitmapPool中的所有Bitmap缓存对象
void trimMemory(int level); //根据给定的level参数,不同程度地清除 BitmapPool 中的大小,比如:可以清除所有的Bitmap缓存对象,也可以清除一半的Bitmap缓存对象
}
②BitmapPool 的实现类
BitmapPool是个接口,定义了通用的方法,在 Glide中有两个BitmapPool的实现类:BitmapPoolAdapter和LruBitmapPool。
BitmapPoolAdapter类是一个空实现类,其中没有什么逻辑,对于每个添加进BitmapPoolAdapter缓存池的Bitmap都会抛弃,从BitmapPoolAdapter缓存池中取到的每个Bitmap都是新创建的。
这里详细分析一下LruBitmapPool,在介绍LruBitmapPool之前,首先介绍另外几个类:LruPoolStrategy、Key和KeyPool。
1)LruPoolStrategy
LruPoolStrategy是针对LruBitmapPool类定义的一个策略接口,其中包括一些存放、获取、移除 Bitmap的方法。
interface LruPoolStrategy {
void put(Bitmap bitmap); //存放Bitmap对象
Bitmap get(int width, int height, Bitmap.Config config); //根据width、height、config属性获取对应的Bitmap对象
Bitmap removeLast(); //移除最后一个Bitmap对象
String logBitmap(Bitmap bitmap); //获取Bitmap对象的用于打印的信息
String logBitmap(int width, int height, Bitmap.Config config);
int getSize(Bitmap bitmap); //得到一个Bitmap对象的大小
}
可以看到,LruPoolStrategy接口是没有权限修饰符的,即说明LruPoolStrategy只可以在Glide包内部使用。
在Glide内部LruPoolStrategy接口有三个实现类,分别是AttributeStrategy、SizeConfigStrategy和SizeStrategy类,这三个类是通过不同维度的条件缓存Bitmap的,具体如下:
①AttributeStrategy是通过Bitmap的width、height、config三个条件缓存Bitmap对象。
②SizeConfigStrategy是通过Bitmap的size、config两个条件缓存Bitmap对象。
③SizeStrategy是通过Bitmap的size条件缓存 Bitmap对象。
可能有人会有疑问了,在BitmapPool和LruPoolStrategy接口中定义的都是Bitmap get(int width, int height, Bitmap.Config config)方法,传入的都是 width、height、config 三个条件,为什么可以通过不同的条件缓存Bitmap对象呢?
这个时候就需要引入Key和KeyPool这两个类了。
2)Key & KeyPool
BitmapPool和LruPoolStrategy都只是接口定义,其底层的实现类其实都是使用了Map数据结构。Map是以键值对的形式在K和V之间形成一一映射的,V就是Bitmap对象,而K就可以是不同形式的了。在Glide中,具体的K是Key这样的一个类,而为了避免每次存放、获取Bitmap对象而新建Key对象,引入了KeyPool的概念。
Glide 中,LruPoolStrategy有三个不同的实现类AttributeStrategy、SizeConfigStrategy和SizeStrategy,他们是通过不同维度的条件缓存 Bitmap,这三个不同维度的条件具体就体现在它们各自内部的实现类Key上。
以AttributeStrategy为例,AttributeStrategy是通过Bitmap的 width、height、config三个条件缓存Bitmap对象,所以AttributeStrategy内部类Key的实现如下:
class AttributeStrategy implements LruPoolStrategy {
......
@VisibleForTesting
static class Key implements Poolable {
private final KeyPool pool;
private int width;
private int height;
private Bitmap.Config config;
public Key(KeyPool pool) {
this.pool = pool;
}
public void init(int width, int height, Bitmap.Config config) {
this.width = width;
this.height = height;
this.config = config;
}
@Override
public boolean equals(Object o) {
if (o instanceof Key) {
Key other = (Key) o;
return width == other.width && height == other.height && config == other.config;
}
return false;
}
@Override
public int hashCode() {
int result = width;
result = 31 * result + height;
result = 31 * result + (config != null ? config.hashCode() : 0);
return result;
}
@Override
public String toString() {
return getBitmapString(width, height, config);
}
@Override
public void offer() {
pool.offer(this);
}
}
在AttributeStrategy.Key类中包含width、height和 config三个属性,且通过init(int width, int height, Bitmap.Config config)方法初始化。
作为Map的 K,自然要重写equals(Object o)和hashCode()方法,且其中都用到了width、height、config三个属性。
在LruPoolStrategy另外两个类SizeConfigStrategy和SizeStrategy中Key也是类似的实现。
说完Key,接下来说一下KeyPool类,Glide 为了避免每次存放、获取 Bitmap 对象都新建Key对象,引入了KeyPool的概念。
因为不同的缓存策略对应不同的Key,不同的Key自然要对应不同的KeyPool了,有三种缓存策略,所以也有三种Key和KeyPool了。
先介绍三种KeyPool的基类BaseKeyPool类,如下所示:
abstract class BaseKeyPool {
private static final int MAX_SIZE = 20;
private final Queue keyPool = Util.createQueue(MAX_SIZE);
// 通过get()方法可以得到一个T对象,T对象有可能是从缓存队列中取的,也有可能是通过create()方法新建的
T get() {
T result = keyPool.poll();
if (result == null) {
result = create();
}
return result;
}
// 通过offer(T key)方法将T对象加入到缓存队列中,前提是缓存队列没有满
public void offer(T key) {
if (keyPool.size() < MAX_SIZE) {
keyPool.offer(key);
}
}
// 因为不同的缓存对象有不同形式的新建方式,所以create()方法需要是抽象的
abstract T create();
}
BaseKeyPool类是个泛型类,只要是实现了Poolable接口的类的对象,都可以通过BaseKeyPool对象池缓存。
在BaseKeyPool类中是通过一个队列Queue实现缓存的,对多可以缓存 20 个对象。
BaseKeyPool也是默认修饰符的,只可以在 Glide 包内部使用。
介绍完BaseKeyPool基类以后,接着介绍一下在AttributeStrategy中应用的KeyPool类,其定义如下:
class AttributeStrategy implements LruPoolStrategy {
......
@VisibleForTesting
static class KeyPool extends BaseKeyPool {
Key get(int width, int height, Bitmap.Config config) {
Key result = get();
result.init(width, height, config);
return result;
}
@Override
protected Key create() {
return new Key(this);
}
}
......
}
AttributeStrategy.KeyPool中缓存的Key都是AttributeStrategy.Key对象。
3)AttributeStrategy
class AttributeStrategy implements LruPoolStrategy {
private final KeyPool keyPool = new KeyPool();
private final GroupedLinkedMap groupedMap = new GroupedLinkedMap<>();
@Override
public void put(Bitmap bitmap) {
final Key key = keyPool.get( bitmap.getWidth(), bitmap.getHeight(), bitmap.getConfig());
groupedMap.put(key, bitmap);
}
@Override
public Bitmap get(int width, int height, Bitmap.Config config) {
final Key key = keyPool.get(width, height, config);
return groupedMap.get(key);
}
@Override
public Bitmap removeLast() {
return groupedMap.removeLast();
}
@Override
public String logBitmap(Bitmap bitmap) {
return getBitmapString(bitmap);
}
@Override
public String logBitmap(int width, int height, Bitmap.Config config) {
return getBitmapString(width, height, config);
}
@Override
public int getSize(Bitmap bitmap) {
return Util.getBitmapByteSize(bitmap);
}
......
@VisibleForTesting
static class KeyPool extends BaseKeyPool {
......
}
@VisibleForTesting
static class Key implements Poolable {
......
}
}
GroupedLinkedMap类似于LinkedHashMap,其实用HashMap、List和双向链表实现了 LRU 算法。
可以看到在put(Bitmap bitmap)&get(int width, int height, Bitmap.Config config)时,都是先从KeyPool中得到对应的Key以后,再去操作groupedMap的。
4)LruBitmapPool
介绍了LruBitmapPool相关的几个非常重要的概念:LruPoolStrategy、Key和KeyPool,以及LruPoolStrategy实现类AttributeStrategy,那么理解LruBitmapPool就更容易一些了。
两个public的构造方法最后都会调用默认修饰符的构造方法,传入的LruPoolStrategy strategy参数是通过getDefaultStrategy()方法获取的,可以看到在 API 19 及以上使用的是SizeConfigStrategy缓存策略,在 API 19 以下则使用的AttributeStrategy缓存策略
public class LruBitmapPool implements BitmapPool {
private final LruPoolStrategy strategy;
private final long initialMaxSize;
......
private long maxSize;
private long currentSize;
// 默认修饰符的构造方法,另外两个构造方法最后都会调用此构造方法
LruBitmapPool(long maxSize, LruPoolStrategy strategy, Set allowedConfigs) {
this.initialMaxSize = maxSize;
this.maxSize = maxSize;
this.strategy = strategy;
this.allowedConfigs = allowedConfigs;
this.tracker = new NullBitmapTracker();
}
public LruBitmapPool(long maxSize) {
this(maxSize, getDefaultStrategy(), getDefaultAllowedConfigs());
}
public LruBitmapPool(long maxSize, Set allowedConfigs) {
this(maxSize, getDefaultStrategy(), allowedConfigs);
}
@Override
public long getMaxSize() {
return maxSize;
}
@Override
public synchronized void setSizeMultiplier(float sizeMultiplier) {
maxSize = Math.round(initialMaxSize * sizeMultiplier);
evict();
}
@Override
public synchronized void put(Bitmap bitmap) {
if (bitmap == null) {
throw new NullPointerException("Bitmap must not be null");
}
if (bitmap.isRecycled()) {
throw new IllegalStateException("Cannot pool recycled bitmap");
}
if (!bitmap.isMutable() || strategy.getSize(bitmap) > maxSize || !allowedConfigs.contains(bitmap.getConfig())) {
bitmap.recycle();
return;
}
final int size = strategy.getSize(bitmap);
strategy.put(bitmap); // 关键,缓存进strategy对象池中
tracker.add(bitmap);
puts++;
currentSize += size;
dump();
evict();
}
private void evict() {
trimToSize(maxSize);
}
@Override
public Bitmap get(int width, int height, Bitmap.Config config) {
Bitmap result = getDirtyOrNull(width, height, config);
if (result != null) {
result.eraseColor(Color.TRANSPARENT); // 关键,将 Bitmap 对象的像素否复位为透明的
} else {
result = createBitmap(width, height, config); // 关键,若从缓存对象池中得到的 Bitmap 对象为空,则通过 createBitmap 方法创建新的 Bitmap 对象
}
return result;
}
@Override
public Bitmap getDirty(int width, int height, Bitmap.Config config) {
Bitmap result = getDirtyOrNull(width, height, config);
if (result == null) {
result = createBitmap(width, height, config); // 关键,创建新的 Bitmap 对象
}
return result;
}
private static Bitmap createBitmap(int width, int height, Bitmap.Config config) {
return Bitmap.createBitmap(width, height, config != null ? config : DEFAULT_CONFIG);
}
private synchronized Bitmap getDirtyOrNull(int width, int height, Bitmap.Config config) {
assertNotHardwareConfig(config);
final Bitmap result = strategy.get(width, height, config != null ? config : DEFAULT_CONFIG); // 关键,从 strategy 中获取缓存的 Bitmap 对象
if (result == null) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Missing bitmap=" + strategy.logBitmap(width, height, config));
}
misses++;
} else {
hits++;
currentSize -= strategy.getSize(result);
tracker.remove(result);
normalize(result);
}
dump();
return result;
}
private static LruPoolStrategy getDefaultStrategy() {
final LruPoolStrategy strategy;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
strategy = new SizeConfigStrategy();
} else {
strategy = new AttributeStrategy();
}
return strategy;
}
......
}
在put(Bitmap bitmap)方法中,首先会做必要的条件检查,然后会调用strategy.put(bitmap),将bitmap对象缓存到对象池中,最后修改相关的参数,如currentSize。
在get(int width, int height, Bitmap.Config config)和getDirty(int width, int height, Bitmap.Config config)方法中,首先都会调用getDirtyOrNull(int width, int height, Bitmap.Config config)方法从strategy.get(width, height, config != null ? config : DEFAULT_CONFIG)缓存对象池中获取 Bitmap,如果得到的 Bitmap 对象为空,则调用createBitmap(int width, int height, Bitmap.Config config)方法创建一个新的 Bitmap 对象并返回。
4.对象池的使用
优化程序过程中,如果看到内存抖动或者后台不断有GC在执行的时候可以考虑使用对象池来管理;另外对象一次初始化大量的实例,导致系统迟滞,并且伴有GC中断也可以考虑引入对象池管理。
public class TaskPool {
public String id;
public String name;
private static final SynchronizedPool sPool = new SynchronizedPool(10);
public static TaskPool obtain() {
TaskPool instance = sPool.acquire();
return (instance != null) ? instance : new TaskPool();
}
public void recycle() {
sPool.release(this);
}
}
调用TaskPool tpool = TaskPool.obtain();从对象池中获取,第一次对象池没有,会直接new一个,如果有会复用。
使用完毕务必要将对象归还到对象池tPool.recycle();
5.对象池的优缺点
优点:
①复用对象池中的对象,可以避免频繁创建和销毁堆中的对, 进而减少垃圾收集器的负担, 减少内存抖动;
②不必重复初始化对象状态, 对于比较耗时的constructor和finalize来说非常合适;
缺点:
①并发环境中, 多个线程可能同时需要获取池中对象, 进而需要在堆数据结构上进行同步或者因为锁竞争而产生阻塞, 这种开销要比创建销毁对象的开销高数百倍;
②由于池中对象的数量有限, 势必成为一个可伸缩性瓶颈;
③很难正确的设定对象池的大小, 如果太小则起不到作用, 如果过大, 则占用内存资源高, 可以开启一个线程定期扫描分析, 将对象池压缩到一个合适的尺寸以节约内存,但为了获得不错的分析结果, 在扫描期间可能需要暂停复用以避免干扰(造成效率低下), 或者使用非常复杂的算法策略(增加维护难度);
④设计和使用对象池容易出错, 设计上需要注意状态同步, 这是个难点, 使用上可能存在忘记归还, 重复归还(可能需要做个循环判断一下是否池中存在此对象, 这也是个开销), 归还后仍旧使用对象(可能造成多个线程并发使用一个对象的情况)等问题。