Android 对象池的原理和使用

3,227 阅读5分钟

「这是我参与11月更文挑战的第3天,活动详情查看:2021最后一次更文挑战

场景

在程序里面经常会遇到的一个问题是短时间内创建大量的对象,导致内存紧张,从而触发GC导致性能问题。对于这个问题,我们可以使用对象池技术来解决它。在使用之前我们要先了解Android的垃圾回收的机制。

Android中的垃圾回收机制

image.png

Android 里面是一个三级 Generation 的内存模型,最近分配的对象会存放在 Young Generation 区域,当这个对象在这个区域停留的时间达到一定程度,它会被移动到 Old Generation,最后到 Permanent Generation 区域。每一个级别的内存区域都有固定的大小,此后不断有新的对象被分配到此区域,当这些对象总的大小快达到这一级别内存区域的阀值时,会触发 GC 的操作,以便腾出空间来存放其他新的对象。每次 GC 发生的时候,所有的线程都是暂停状态的。GC 所占用的时间和它是哪一个 Generation 也有关系,Young Generation 的每次 GC 操作时间是最短的,Old Generation 其次,Permanent Generation 最长。

GC频繁触发的原因

  • 大量的对象被创建又在短时间内马上被释放

  • 瞬间产生大量的对象会严重占用 Young Generation 的内存区域,当达到阀值,剩余空间不够的时候,也会触发 GC。即使每次分配的对象占用了很少的内存,但是他们叠加在一起会增加 Heap 的压力,从而触发更多其他类型的 GC。这个操作有可能会影响到帧率,并使得用户感知到性能问题。

对象池

工作工程:

image.png

  • Pool 表示的是对象池,用于存储可重复利用的对象
  • 第二步操作就是取出对象。
  • 第三步操作是使用取出的对象来完成一些任务。这时候并不需要对象池再做什么,但是这也意味着该对象将被租借一段时间并且不能在被其他组件借出。
  • 第四步操作就是归还,组件归还借出的对象这样可以继续满足其他的租借请求。

Android中对象池源码解析

public final class Pools {

   
    public static interface Pool<T> {

        /**
         * @return 从对象池中取出对象
         */
        @UnsupportedAppUsage
        public T acquire();

        /**
         * 释放对象并放入对象池中
         * @return true true表示释放的对象成功放入对象池
         *
         * @throws IllegalStateException 如果对象已经存在,抛出异常
         */
        public boolean release(T instance);
    }

    private Pools() {
        /* do nothing - hiding constructor */
    }

    //对象池的非同步实现
    public static class SimplePool<T> implements Pool<T> {
        @UnsupportedAppUsage
        private final Object[] mPool;//存放对象的数组

        private int mPoolSize;//对象池内对象个数

        /**
         * Creates a new instance.
         *
         * @param maxPoolSize 对象池的最大容量
         *
         */
        @UnsupportedAppUsage
        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
        @SuppressWarnings("unchecked")
        @UnsupportedAppUsage
        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
        @UnsupportedAppUsage
        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;
        }
    }

    /**
     * 对象池的同步实现
     *
     * @param <T> The pooled type.
     */
    public static class SynchronizedPool<T> extends SimplePool<T> {
        private final Object mLock; //同步加锁对象

        /**
         * Creates a new instance.
         *
         * @param maxPoolSize The max pool size.
         * @param lock an optional custom object to synchronize on
         *
         * @throws IllegalArgumentException If the max pool size is less than zero.
         */
        public SynchronizedPool(int maxPoolSize, Object lock) {
            super(maxPoolSize);
            mLock = lock;
        }

        /** @see #SynchronizedPool(int, Object)  */
        @UnsupportedAppUsage
        public SynchronizedPool(int maxPoolSize) {
            this(maxPoolSize, new Object());
        }

        @Override
        @UnsupportedAppUsage
        public T acquire() {
            synchronized (mLock) {
                return super.acquire();
            }
        }

        @Override
        @UnsupportedAppUsage
        public boolean release(T element) {
            synchronized (mLock) {
                return super.release(element);
            }
        }
    }
}

Pool 接口类
acquire(): 从对象池请求对象的函数:
release(): 释放对象回对象池的函数

SimplePool类
原理:使用了 “懒加载” 的思想。当 SimplePool 初始化时,不会生成 N 个 T 类型的对象存放在对象池中。而是当每次外部调用 release() 时,才把释放的 T 类型对象存放在对象池中。要先放入,才能取出来。

对象池的优缺点

优点
  • 复用对象池中的对象,可以避免频繁创建和销毁堆中的对, 进而减少垃圾收集器的负担, 减少内存抖动;
  • 不必重复初始化对象状态, 对于比较耗时的构造函数来说非常合适;
缺点
  • Java的对象分配操作不比c语言的malloc调用慢, 对于轻中量级的对象, 分配/释放对象的开销可以忽略不计;

  • 并发环境中, 多个线程可能(同时)需要获取池中对象, 进而需要在堆数据结构上进行同步或者因为锁竞争而产生阻塞, 这种开销要比创建销毁对象的开销高数百倍;

  • 由于池中对象的数量有限, 势必成为一个可伸缩性瓶颈;

  • 很难正确的设定对象池的大小, 如果太小则起不到作用, 如果过大, 则占用内存资源高。

    优化:可以开启一个线程定期扫描分析, 将对象池压缩到一个合适的尺寸以节约内存,但为了获得不错的分析结果, 在扫描期间可能需要暂停复用以避免干扰,造成效率低下, 或者使用非常复杂的算法策略(增加维护难度);

  • 设计和使用对象池容易出错, 设计上需要注意状态同步, 这是个难点, 使用上可能存在忘记归还(就像c语言编程忘记free一样), 重复归还(可能需要做个循环判断一下是否池中存在此对象, 这也是个开销), 归还后仍旧使用对象(可能造成多个线程并发使用一个对象的情况)等问题。

源码中的Demo

由于对象池设计是要先放入,才能取出来。所以当没有放入对象时,调用 acquire(),返回都是 null,所以可以对 对象池进行以下封装,方便其使用:

public class MyPooledClass {

     private static final SynchronizedPool<MyPooledClass> sPool =
             new SynchronizedPool<MyPooledClass>(10);

     public static MyPooledClass obtain() {
        MyPooledClass instance = sPool.acquire();
         return (instance != null) ? instance : new MyPooledClass();
     }

     public void recycle() {
         // Clear state if needed.
          sPool.release(this);
     }

     . . .
 }

自己写的一个demo

public class Company {
    
    private String id;
    
    private String name;
    
    private static final Pools.SynchronizedPool<Company> mPool = new Pools.SynchronizedPool<>(10);
    //获取对象
    public static Company obtain(){
        Company company = mPool.acquire();
        if(company!=null){
            return company;
        }else{
            return new Company(); 
        }
    }
    //释放对象
    public void recycle(){
        mPool.release(this);
    }
    
}