摘要:為了防止內存抖動,所以可以使用對象池順利解決問題。獲取矩陣對象獲取某個矩陣的歸還矩陣對象注意事項如果對象池為空,則對象池自己會一個返回。如果對象池內有對象,則取一個已存在的返回。
目錄介紹
01.什么是對象池
02.glide哪里用到對象池
03.多條件key緩存bitmap
3.1 多條件key創建
3.2 key值的復用
04.glide對象池總結
05.學以致用對象池
5.1 使用場景
5.2 實現步驟
5.3 對象池使用
5.4 項目實踐分享
06.對象池的容量
好消息博客筆記大匯總【16年3月到至今】,包括Java基礎及深入知識點,Android技術博客,Python學習筆記等等,還包括平時開發中遇到的bug匯總,當然也在工作之余收集了大量的面試題,長期更新維護并且修正,持續完善……開源的文件是markdown格式的!同時也開源了生活博客,從12年起,積累共計N篇[近100萬字,陸續搬到網上],轉載請注明出處,謝謝!
鏈接地址:https://github.com/yangchong2...
如果覺得好,可以star一下,謝謝!當然也歡迎提出建議,萬事起于忽微,量變引起質變!
01.什么時對象池
對象池作用
在某些時候,我們需要頻繁使用一些臨時對象,如果每次使用的時候都申請新的資源,很有可能會引發頻繁的 gc 而影響應用的流暢性。這個時候如果對象有明確的生命周期,那么就可以通過定義一個對象池來高效的完成復用對象。
對象池使用場景
glide中對加載圖片時頻繁創建對象使用到了對象池。
02.glide使用對象池
glide頻繁請求圖片
比如Glide中,每個圖片請求任務,都需要用到類。若每次都需要重新new這些類,并不是很合適。而且在大量圖片請求時,頻繁創建和銷毀這些類,可能會導致內存抖動,影響性能。
Glide使用對象池的機制,對這種頻繁需要創建和銷毀的對象保存在一個對象池中。每次用到該對象時,就取對象池空閑的對象,并對它進行初始化操作,從而提高框架的性能。
03.多條件key緩存bitmap 3.1 多條件key創建
首先看一個簡單的緩存bitmap代碼,代碼如下所示
就簡單的通過 HashMap 緩存了Bitmap資源,只有在緩存不存在時才會執行加載這個耗時操作。但是上面的緩存條件十分簡單,是通過圖片的名字決定的,這很大程度上滿足不了實際的需求。可能會出現意想不到的問題……
private final Mapcache = new HashMap<>() private void setImage(ImageView iv, String name){ Bitmap b = cache.get(name); if(b == null){ b = loadBitmap(name); cache.put(name, b); } iv.setImageBitmap(b); }
多條件 Key
所以我們就需要定義一個Key對象來包含各種緩存的條件,例如我們除了圖片名字作為條件,還有圖片的寬度,高度也決定了是否是同一個資源,那么代碼將變成如下:
注意多條件key需要重寫equals和hashCode方法。equals注意是比較兩個對象是否相同,而hashCode主要作用是當數據量很大的時候,使用equals一一比較比較會大大降低效率。hashcode實際上是返回對象的存儲地址,如果這個位置上沒有元素,就把元素直接存儲在上面,如果這個位置上已經存在元素,這個時候才去調用equal方法與新元素進行比較就可以提高效率呢!
private final Map3.2 key值的復用cache = new HashMap<>(); private void setImage(ImageView iv, String name, int width, int height){ Key key = new Key(name, width, height); Bitmap b = cache.get(key); if(b == null){ b = loadBitmap(name, width, height); cache.put(key, b); } iv.setImageBitmap(b); } public class Key { private final String name; private final int width; private final int heifht; public Key(String name, int width, int heifht) { this.name = name; this.width = width; this.heifht = heifht; } public String getName() { return name; } public int getWidth() { return width; } public int getHeifht() { return heifht; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } Key key = (Key) o; if (width != key.width) { return false; } if (heifht != key.heifht) { return false; } return name != null ? name.equals(key.name) : key.name == null; } @Override public int hashCode() { int result = name != null ? name.hashCode() : 0; final int prime = 31; result = prime * result + width; result = prime * result + heifht; return result; } }
key值的復用是如何操作的
雖然可以支持多條件的緩存鍵值了,但是每次查找緩存前都需要創建一個新的 Key 對象,雖然這個 Key 對象很輕量,但是終歸覺得不優雅。gilde源碼中會提供一個 BitmapPool 來獲取 Bitmap 以避免 Bitmap 的頻繁申請。而 BitmapPool 中 get 方法的簽名是這樣的:
Bitmap 需要同時滿足三個條件(高度、寬度、顏色編碼)都相同時才能算是同一個 Bitmap,那么內部是如何進行查找的呢?需要知道的是,BitmapPool 只是一個接口,內部的默認實現是 LruBitmapPool
看LruBitmapPool中get方法
注意重點看這行代碼:final Bitmap result = strategy.get(width, height, config != null ? config : DEFAULT_CONFIG);
strategy 是 LruPoolStrategy 接口類型,查看其中一個繼承該接口類的 get 方法的實現
@Override @NonNull public Bitmap get(int width, int height, Bitmap.Config config) { Bitmap result = getDirtyOrNull(width, height, config); if (result != null) { // Bitmaps in the pool contain random data that in some cases must be cleared for an image // to be rendered correctly. we shouldn"t force all consumers to independently erase the // contents individually, so we do so here. See issue #131. result.eraseColor(Color.TRANSPARENT); } else { result = createBitmap(width, height, config); } return result; } @Nullable private synchronized Bitmap getDirtyOrNull( int width, int height, @Nullable Bitmap.Config config) { assertNotHardwareConfig(config); // 對于非公共配置類型,配置為NULL,這可能導致轉換以此處請求的配置方式天真地傳入NULL。 final Bitmap result = strategy.get(width, height, config != null ? config : DEFAULT_CONFIG); 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); } if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "Get bitmap=" + strategy.logBitmap(width, height, config)); } dump(); return result; }
然后看一下SizeConfigStrategy類中的get方法
看一下下面注釋的兩行重點代碼。同樣也需要一個專門的類型用來描述鍵,但是鍵result居然是也是從一個對象池keyPool中獲取的。
可以看到 Key 是一個可變對象,每次先獲取一個Key對象(可能是池中的,也可能是新創建的),然后把變量初始化。但是大家知道,HashMap 中的 Key 不應該是可變對象,因為如果 Key的 hashCode 發生變化將會導致查找失效,那么這里是如何做到 Key 是可變對象的同時保證能正確的作為 HashMap 中的鍵使用呢?
@Override @Nullable public Bitmap get(int width, int height, Bitmap.Config config) { int size = Util.getBitmapByteSize(width, height, config); Key bestKey = findBestKey(size, config); //第一處代碼 Bitmap result = groupedMap.get(bestKey); if (result != null) { decrementBitmapOfSize(bestKey.size, result); result.reconfigure(width, height, result.getConfig() != null ? result.getConfig() : Bitmap.Config.ARGB_8888); } return result; } private Key findBestKey(int size, Bitmap.Config config) { //第二處代碼 Key result = keyPool.get(size, config); for (Bitmap.Config possibleConfig : getInConfigs(config)) { NavigableMapsizesForPossibleConfig = getSizesForConfig(possibleConfig); Integer possibleSize = sizesForPossibleConfig.ceilingKey(size); if (possibleSize != null && possibleSize <= size * MAX_SIZE_MULTIPLE) { if (possibleSize != size || (possibleConfig == null ? config != null : !possibleConfig.equals(config))) { keyPool.offer(result); result = keyPool.get(possibleSize, possibleConfig); } break; } } return result; } @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); } }
然后看一下groupedMap的代碼
在查找時,如果沒有發現命中的值,那么就會創建新的值,并將其連同 Key 保存在 HashMap 中,不會對 Key 進行復用。而如果發現了命中的值,也就是說 HashMap 中已經有一個和當前 Key 相同的 Key 對象了,那么 Key 就可以通過 offer 方法回收到了 KeyPool 中,以待下一次查找時復用。
@Nullable public V get(K key) { LinkedEntry04.glide對象池總結entry = keyToEntry.get(key); if (entry == null) { entry = new LinkedEntry<>(key); keyToEntry.put(key, entry); } else { key.offer(); } makeHead(entry); return entry.removeLast(); }
優化點
對開銷較大的 Bitmap 進行了復用,就連為了復用Bitmap時重復申請的Key對象都進行了復用,盡可能的減少了對象的創建開銷,保證了應用的流暢性。
為何要多條件key
針對bitmap,加載圖片特別頻繁且多,不建議只是簡單通過一個name圖片名稱作為鍵,因為可能圖片名稱是一樣的,比如有時候接口返回同樣名稱的圖片有大圖,正常圖,縮略圖等,那樣可能會存儲重復或者碰撞。但是通過name,還有圖片寬高字段,就可以大大減小這種問題呢。
HashMap中鍵存儲問題
為了正確使用HashMap,選擇恰當的Key是非常重要的。Key在HashMap里是不可重復的。也就是說這個key對象的hashcode是不能改變的。那么多條件key是如何保證唯一了,如果要以可變對象作為key的話,那就必須要重寫hashcode和equals方法來達到這個目的,除此之外,別無他法。同時這個時候可以利用keyPool對key對象進行緩存。
那么有人會問,要是key值變化了,怎么辦?如果HashMap的Key的哈希值在存儲鍵值對后發生改變,Map可能再也查找不到這個Entry了。如果Key對象是可變的,那么Key的哈希值就可能改變。在HashMap中可變對象作為Key會造成數據丟失。這也就是為何key一般要用string或者int值的緣由呢。
05.學以致用對象池 5.1 使用場景在寫圖片縮放控件的時候,當雙手指滑動時,會頻繁操作讓圖片縮放和移動。這就會頻繁用到變化矩陣Matrix,還有RectF繪畫相關的工具類。為了防止內存抖動,所以可以使用對象池順利解決問題。
內存抖動是由于在短時間內有大量的對象被創建或者被回收的現象,內存抖動出現原因主要是頻繁(很重要)在循環里創建對象(導致大量對象在短時間內被創建,由于新對象是要占用內存空間的而且是頻繁,如果一次或者兩次在循環里創建對象對內存影響不大,不會造成嚴重內存抖動這樣可以接受也不可避免,頻繁的話就很內存抖動很嚴重),它伴隨著頻繁的GC。而我們知道GC太頻繁會大量占用ui線程和cpu資源,會導致app整體卡頓。
5.2 實現步驟
創建抽象ObjectsPool類,由于緩存的對象可能是不同的類型,這里使用泛型T。主要操作是從對象池請求對象的函數,還有釋放對象回對象池的函數。同時可以自己設置對象池的大小,可以使用隊列來實現存儲功能。
代碼如下:
/** ** @author yangchong * blog : https://github.com/yangchong211 * time : 2017/05/30 * desc : 對象池抽象類 * revise: 具體使用方法請看:https://github.com/yangchong211/YCGallery **/ public abstract class ObjectsPool{ /* * 防止頻繁new對象產生內存抖動. * 由于對象池最大長度限制,如果吞度量超過對象池容量,仍然會發生抖動. * 此時需要增大對象池容量,但是會占用更多內存. * 對象池容納的對象類型 */ /** * 對象池的最大容量 */ private int mSize; /** * 對象池隊列 */ private Queue mQueue; /** * 創建一個對象池 * * @param size 對象池最大容量 */ public ObjectsPool(int size) { mSize = size; mQueue = new LinkedList<>(); } /** * 獲取一個空閑的對象 * * 如果對象池為空,則對象池自己會new一個返回. * 如果對象池內有對象,則取一個已存在的返回. * take出來的對象用完要記得調用given歸還. * 如果不歸還,讓然會發生內存抖動,但不會引起泄漏. * * @return 可用的對象 * * @see #given(Object) */ public T take() { //如果池內為空就創建一個 if (mQueue.size() == 0) { return newInstance(); } else { //對象池里有就從頂端拿出來一個返回 return resetInstance(mQueue.poll()); } } /** * 歸還對象池內申請的對象 * 如果歸還的對象數量超過對象池容量,那么歸還的對象就會被丟棄 * * @param obj 歸還的對象 * * @see #take() */ public void given(T obj) { //如果對象池還有空位子就歸還對象 if (obj != null && mQueue.size() < mSize) { mQueue.offer(obj); } } /** * 實例化對象 * * @return 創建的對象 */ abstract protected T newInstance(); /** * 重置對象 * * 把對象數據清空到就像剛創建的一樣. * * @param obj 需要被重置的對象 * @return 被重置之后的對象 */ abstract protected T resetInstance(T obj); }
然后,可以定義一個矩陣對象池,需要實現上面的抽象方法。如下所示
public class MatrixPool extends ObjectsPool{ /**
*/ public MatrixPool(int size) { super(size); } @Override protected Matrix newInstance() { return new Matrix(); } @Override protected Matrix resetInstance(Matrix obj) { obj.reset(); return obj; } } ```5.3 對象池使用
至于使用,一般是獲取矩陣對象,還有歸還矩陣對象。
/**
*/ private static MatrixPool mMatrixPool = new MatrixPool(16); /** * 獲取矩陣對象 */ public static Matrix matrixTake() { return mMatrixPool.take(); } /** * 獲取某個矩陣的copy */ public static Matrix matrixTake(Matrix matrix) { Matrix result = mMatrixPool.take(); if (matrix != null) { result.set(matrix); } return result; } /** * 歸還矩陣對象 */ public static void matrixGiven(Matrix matrix) { mMatrixPool.given(matrix); } ```
注意事項
如果對象池為空,則對象池自己會new一個返回。如果對象池內有對象,則取一個已存在的返回。take出來的對象用完要記得調用given歸還,如果不歸還,仍然會發生內存抖動,但不會引起泄漏。
5.4 項目實踐分享
避免發生內存抖動的幾點建議:
盡量避免在循環體內創建對象,應該把對象創建移到循環體外。
注意自定義View的onDraw()方法會被頻繁調用,所以在這里面不應該頻繁的創建對象。
當需要大量使用Bitmap的時候,試著把它們緩存在數組中實現復用。
對于能夠復用的對象,同理可以使用對象池將它們緩存起來。
大多數對象的復用,最終實施的方案都是利用對象池技術,要么是在編寫代碼的時候顯式的在程序里面去創建對象池,然后處理好復用的實現邏輯,要么就是利用系統框架既有的某些復用特性達到減少對象的重復創建,從而減少內存的分配與回收。
圖片縮放案例:https://github.com/yangchong2...
06.對象池的容量
通常情況下,我們需要控制對象池的大小
如果對象池沒有限制,可能導致對象池持有過多的閑置對象,增加內存的占用
如果對象池閑置過小,沒有可用的對象時,會造成之前對象池無可用的對象時,再次請求出現的問題
對象池的大小選取應該結合具體的使用場景,結合數據(觸發池中無可用對象的頻率)分析來確定。
使用對象池也是要有一定代價的:短時間內生成了大量的對象占滿了池子,那么后續的對象是不能復用的。
其他介紹 01.關于博客匯總鏈接1.技術博客匯總
2.開源項目匯總
3.生活博客匯總
4.喜馬拉雅音頻匯總
5.其他匯總
02.關于我的博客我的個人站點:www.yczbj.org,www.ycbjie.cn
github:https://github.com/yangchong211
知乎:https://www.zhihu.com/people/...
簡書:http://www.jianshu.com/u/b7b2...
csdn:http://my.csdn.net/m0_37700275
喜馬拉雅聽書:http://www.ximalaya.com/zhubo...
開源中國:https://my.oschina.net/zbj161...
泡在網上的日子:http://www.jcodecraeer.com/me...
郵箱:yangchong211@163.com
阿里云博客:https://yq.aliyun.com/users/a... 239.headeruserinfo.3.dT4bcV
segmentfault頭條:https://segmentfault.com/u/xi...
掘金:https://juejin.im/user/593943...
03.參考博客https://www.jianshu.com/p/b74...
對象池優化綜合案例:https://github.com/yangchong2... 對象池優化縮放圖片案例:https://github.com/yangchong2...文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/77520.html
摘要:如果為負值,表示不運行檢測線程。默認為策略的類名,默認為這里就用到了上面提到的兩個參數對象池原理分析避免泄漏配置參數詳解,以及資源回收,從池中獲取資源,將資源返還給池邏輯解析 序 本文主要解析一下apache common pools下的GenericObjectPool的參數設置 GenericObjectPool commons-pool2-2.4.2-sources.jar!/o...
摘要:當一個進行需要處理阻塞操作時,它會將這個任務交給線程池來完成。線程池配置如果你確信引入線程池對性能提升有效,那么咱們可以繼續了解一些調優參數。這個錯誤表示這個線程池消費小于生產,所以可以增加隊列長度,如果調整無效,說明系統達到了瓶頸。 五年級英語水平,端午家庭作業。 前言 Nginx以異步、事件驅動的方式處理連接。傳統的方式是每個請求新起一個進程或線程,Nginx沒這樣做,它通過非...
摘要:錯誤使用單利在開發中單例經常需要持有對象,如果持有的對象生命周期與單例生命周期更短時,或導致無法被釋放回收,則有可能造成內存泄漏。如果集合是類型的話,那內存泄漏情況就會更為嚴重。 目錄介紹 1.OOM和崩潰優化 1.1 OOM優化 1.2 ANR優化 1.3 Crash優化 2.內存泄漏優化 2.0 動畫資源未釋放 2.1 錯誤使用單利 2.2 錯誤使用靜態變量 2.3 ...
摘要:會展示這個節點目前正在服務中的段的數量。線程池部分在內部維護了線程池。這些線程池相互協作完成任務,有必要的話相互間還會傳遞任務。每個線程池會列出已配置的線程數量,當前在處理任務的線程數量,以及在隊列中等待處理的任務單元數量。 showImg(https://segmentfault.com/img/remote/1460000011618283?w=1920&h=1080); 集群健康...
閱讀 2179·2021-11-24 09:39
閱讀 2792·2021-07-29 13:49
閱讀 2327·2019-08-29 14:15
閱讀 2240·2019-08-29 12:40
閱讀 3318·2019-08-26 13:42
閱讀 638·2019-08-26 12:13
閱讀 2072·2019-08-26 11:41
閱讀 3351·2019-08-23 18:32