์ด๋ฏธ์ง ๋ก๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ์ง ์์ ์ํ์์ ๋ง์ ์ด๋ฏธ์ง๋ฅผ ์ฌ์ฉํ๊ฑฐ๋ ๊ณ ํด์๋ ์ด๋ฏธ์ง๋ฅผ ์ด๋ฏธ์ง๋ทฐ์ ๋ก๋ํด์ผํ๋ ๊ฒฝ์ฐ ๋ฉ๋ชจ๋ฆฌ ๋ถ์กฑ์ผ๋ก OOM์ด ๋ฐ์ํ๊ฒ ๋๋ค. ์์ฆ ๋์ค๋ ์ค๋งํธํฐ์ ๊ต์ฅํ ๋ง์ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ๊ฐ์ง๊ณ ํ์ด๋๋๋ฐ ์ ์ด์ ๋๋ ๋ชป ๋ฒํฐ์ง(?)
๋ผ๋ ์๊ฐ์ ํ๋ค๋ฉด, ์๋๋ก์ด๋๋ ์ฑ ๋ด์์ ์ฌ์ฉํ ์ ์๋ ํ ๋ฉ๋ชจ๋ฆฌ๊ฐ ์ ํด์ ธ์๊ธฐ ๋๋ฌธ์ด๋ค.
Android์ ๋ฉ๋ชจ๋ฆฌ ๋ชจ๋ธ์ ์ด์์ฒด์ ๋ฒ์ ์ ๋ฐ๋ผ ๋๊ฐ์ง๋ก ๋๋๊ฒ ๋๋ค.
Dalvik heap footprint + External Limit
์ ํฉ์ณ ํ๋ก์ธ์ค๋น ๋ฉ๋ชจ๋ฆฌ ํ๊ณ๋ฅผ ์ด๊ณผํ๋ฉด OOM์ด ๋ฐ์ํ๊ฒ ๋๋ค. java์ footprint๋ ํ ๋ฒ ์ฆ๊ฐํ๋ฉด ํฌ๊ธฐ๊ฐ ๋ค์ ๊ฐ์๋์ง ์๊ธฐ ๋๋ฌธ์, footprint๊ฐ ๊ณผ๋ํ๊ฒ ์ปค์ง์ง ์๊ฒ๋ ์ ๊ด๋ฆฌํด์ผํ๋ค.
Dalvik VM์ ์ฒ์์ ๋์์ ํ์ํ ๋งํผ๋ง ํ๋ก์ธ์ค์ Heap์ ํ ๋นํ๊ฒ ๋๊ณ , ํ๋ก์ธ์ค์ ํ ๋น๋ ๋ฉ๋ชจ๋ฆฌ๋ณด๋ค ๋ง์ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ํ์ํ๊ฒ๋ ๋๋ง๋ค Dalvit Footfrint๋ ์ฆ๊ฐํ๊ฒ๋๋ค. ํ์ง๋ง ์ฆ๊ฐ๋ Footfrint๋ ๊ฒฐ์ฝ ๊ฐ์ํ์ง ์๊ธฐ ๋๋ฌธ์ java ๊ฐ์ฒด๊ฐ ์ฌ์ฉ๊ฐ๋ฅํ ๋ฉ๋ชจ๋ฆฌ ๊ณต๊ฐ์ ์ฌ์ ๊ฐ ์์ด๋, External heap์ ํฌ๊ธฐ๊ฐ ์ฆ๊ฐ๋๋ฉด OOM์ด ๋ฐ์ํ ์ ์๋ค. ํ์ง๋ง ์ด๋ฐ ๋ฌธ์ ๋ Honeycomb ์ดํ๋ถํฐ๋ Dalvik heap ๊ณผ External ์์ญ์ด ํฉ์ณ์ก๊ธฐ ๋๋ฌธ์, ๊ณ ๋ คํ ํ์๊ฐ ์์ด์ก๋ค.
External ์์ญ์ ์ฌ์ฉํ๋ Honeycomb ๋ฏธ๋ง ๋ฒ์ ์์๋ ์ด๋ฏธ์ง๋ฅผ ๋ง์ด ์ฌ์ฉํ๊ณ ์๋ ํ๋ฉด์์ ํ๋ฉด์ ์ ํํ๋ ํ๋์ด ๋ฐ์ํ์ ๋๋ OOM์ด ๋ฐ์ํ๋ฉด์ ์ฑ์ด ์ค์ง๋ ๊ฒ์ด๋ค. ํ๋ฉด์ ์ ํํ๋ฉด ์ด์ ์กํฐ๋นํฐ ์ธ์คํด์ค์ ์๋ ์ด๋ฏธ์ง๋ทฐ๋ ํ ๋น๋์๋ ๋นํธ๋งต์ด ํจ๊ป ์๋ฉธ๋์ด ๋ฉ๋ชจ๋ฆฌ๊ฐ ํ์๋๊ณ ์๋ก์ด ์กํฐ๋นํฐ ์ธ์คํด์ค๋ฅผ ์์ฑํ ํ ๋ฐ, ์ด ๊ณผ์ ์์ ์ด์ ์กํฐ๋นํฐ ์ธ์คํด์ค์ ๋นํธ๋งต ๊ฐ์ฒด๊ฐ ํ์๋์ง ์์ ๋ฉ๋ชจ๋ฆฌ ๋์๊ฐ ๋ฐ์ํ๊ธฐ ๋๋ฌธ์ด๋ค.
๋นํธ๋งต ๊ฐ์ฒด์ ๋ํ ์ฐธ์กฐ๊ฐ ์๋๋ฐ๋ ์ ํ์๊ฐ ๋ ์ ์์๊น?๐ค Honeycomb ๋ฏธ๋ง ๋ฒ์ ์์๋ Java ๋นํธ๋งต ๊ฐ์ฒด๋ ์ค์ ๋นํธ๋งต ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ง๊ณ ์๋ ๊ณณ์ ๊ฐ๋ฆฌํค๋ ํฌ์ธํฐ์ผ ๋ฟ์ด๊ณ ์ค์ ๋ฐ์ดํฐ๋ External ์์ญ์ธ Native Heap ์์ญ์ ์ ์ฅ๋๊ธฐ ๋๋ฌธ์ด๋ค. Java ๋นํธ๋งต ๊ฐ์ฒด๋ ์ฐธ์กฐ๊ฐ ์์ ๋ GC์ ์ํด ํ์๋์ง๋ง Native Heap ์์ญ์ GC ์ํ์์ญ ๋ฐ์ด๊ธฐ ๋๋ฌธ์ ๋ฉ๋ชจ๋ฆฌ ์๋ฉธ ์์ ์ด ๋ค๋ฅด๋ค.
์ด๋ฌํ ๋ฌธ์ ์ ๋๋ฌธ์, Honeycomb ์ดํ ๋ฒ์ ์์๋ External ์์ญ์ด ์์ด์ง๋ฉด์ Dalvik heap ์์ญ์ ๋นํธ๋งต ๋ฉ๋ชจ๋ฆฌ๋ฅผ ์ฌ๋ฆด ์ ์๊ฒ ๋์๊ณ GC๋ ์ ๊ทผํ ์ ์๊ฒ ๋์๋ค.
๋ค์ ๋์์, ๋ง์ฝ ๊ณ ํด์๋ ์ด๋ฏธ์ง๋ฅผ ๋ก๋ํ ๋ OOM์ด ๋ฐ์ํ๋ ๊ฒฝ์ฐ BitmapFactory ๊ฐ์ฒด๋ฅผ ์ด์ฉํด ๋ค์ด์ํ๋ง, ๋์ฝ๋ฉ ๋ฐฉ์์ ์ ํํด ์ ์ ํ๊ฒ ๋ทฐ์ ๋ก๋ํ๋ฉด ๋๋ค. ํ์ง๋ง ๋ง์ ์ด๋ฏธ์ง๋ฅผ ์ฌ์ฉํ๊ฒ ๋๋ฉด์ OOM์ด ๋ฐ์ํ๋ค๋ฉด, ์ด๋ฏธ์ง ์บ์ฑ
์ ์ด์ฉํด๋ณด๋ ๊ฒ์ด ์ด๋จ๊น?
์ด๋ฏธ์ง๊ฐ ํ๋ฉด์์ ์ฌ๋ผ์ง๊ณ ๋ค์ ๊ตฌ์ฑํ ๋ ์ด๋ฏธ์ง๋ฅผ ๋งค๋ฒ ๋ก๋ํ๋ ๊ฒ์ ์ฑ๋ฅ์์ผ๋ก๋ ์ฌ์ฉ์ ๊ฒฝํ์ ์ข์ง ์๋ค. ์ด๋ด ๋ ๋ฉ๋ชจ๋ฆฌ์ ๋์คํฌ ์บ์๋ฅผ ์ด์ฉํ์ฌ ์ด๋์์ ๊ฐ ์ ์ฅ๋์ด์๋ ๋นํธ๋งต์ ๋ค์ ๊ฐ์ ธ์จ๋ค๋ฉด ๋ค์ ๋ก๋ํ๋ ์๊ฐ๋ ๋จ์ถ์ํฌ ์ ์์ผ๋ฉฐ ์ฑ๋ฅ ๊ฐ์ ๋ ๊ฐ๋ฅํ ๊ฒ์ด๋ค. ์ด ๋ ์บ์ฑ์ ์ํด Memory Cache
์ Disk Cache
์ฌ์ฉ์ ์ถ์ฒํ๋๋ฐ ๋๊ฐ์ง๊ฐ ์ด๋ค ์ฐจ์ด์ ์ด ์๋์ง ์์๋ณด์.
Memory Cache๋ ์ดํ๋ฆฌ์ผ์ด์ ๋ด์ ์กด์ฌํ๋ ๋ฉ๋ชจ๋ฆฌ์ ๋นํธ๋งต์ ์บ์ฑํ๊ณ , ํ์ํ ๋ ๋น ๋ฅด๊ฒ ์ ๊ทผ๊ฐ๋ฅํ๋ค. ํ์ง๋ง Memory Cache๋ ๊ณง ์ดํ๋ฆฌ์ผ์ด์ ์ฉ๋์ ํค์ฐ๋ ์ฃผ๋ฒ์ด ๋ ์ ์๊ธฐ ๋๋ฌธ์ ๋ง์ ์บ์ฑ์ ์๊ตฌํ๋ ๋นํธ๋งต์ ๊ฒฝ์ฐ์๋ Disk Cache์ ๋ฃ๋ ๊ฒ์ด ๋ ์ข์ ์ ์๋ค.
Memory Cache์ ๋ฃ๊ธฐ์ ๋ง์ ์บ์๋ฅผ ์๊ตฌํ๋ ๊ฒฝ์ฐ, ํน์ ์ฑ์ด ๋ฐฑ๊ทธ๋ผ์ด๋๋ก ์ ํ๋์ด๋ ์ ์ฌํ ์บ์๊ฐ ์ญ์ ๋์ง ์๊ธฐ๋ฅผ ๋ฐ๋๋ค๋ฉด Disk Cache๋ฅผ ์ด์ฉํ๋ ๊ฒ์ด ์ข๋ค. ํ์ง๋ง Disk๋ก๋ถํฐ ์บ์ฑ๋ ๋นํธ๋งต์ ๊ฐ์ ธ์ฌ ๋๋ Memory์์ ๋ก๋ํ๋ ๊ฒ๋ณด๋ค ์ค๋์๊ฐ์ด ๊ฑธ๋ฆฐ๋ค.
Memory Cache์ ์์๋ฅผ ์ํด ์๊ฐํ ๊ฒ์ BitmapPool
์ด๋ค. BitmapPool
์ ์๋ฆฌ๋ ์ฌ์ฉํ์ง ์๋ Bitmap์ ๋ฆฌ์คํธ์ ๋ฃ์ด๋๊ณ , ์ถํ์ ๋์ผํ ์ด๋ฏธ์ง๋ฅผ ๋ก๋ํ ๋ ๋ค์ ๋ฉ๋ชจ๋ฆฌ์ ์ ์ฌํ์ง ์๊ณ pool์ ์๋ ์ด๋ฏธ์ง๋ฅผ ๊ฐ์ ธ์ ์ฌ์ฌ์ฉํ๋ ๊ฒ์ด๋ค.
๋ณดํต BitmapPool์ ์ด์ฉํด ์ฌ์ฌ์ฉ Pool์ ๋ง๋ค๊ฒ ๋ ๋, LRU ์บ์ฑ ์๊ณ ๋ฆฌ์ฆ์ผ๋ก ๊ตฌํ๋ LinkedList (lruBItmapList)
์ Byte Size ์์ผ๋ก ์ ๋ ฌ๋ LinkedList(bitmapList)
๋ฅผ ์ฌ์ฉํ์ฌ ๊ตฌํํ๊ฒ ๋๋ค. ์ด ๋์ ๋ค์ด์๋ ๋นํธ๋งต์ ์์๋ง ๋ค๋ฅผ ๋ฟ, ๊ฐ์ ๋นํธ๋งต์ด ๋ด๊ธฐ๊ฒ๋๋ค.
private val lruBitmapList = LinkedList<Bitmap>()
private val bitmapList = LinkedList<Bitmap>()
LRU ์๊ณ ๋ฆฌ์ฆ์ ์ด์ฉํด ์ค๋ซ๋์ ์ฐธ์กฐ๋์ง์์ ๋นํธ๋งต ๊ฐ์ฒด๋ ๋งจ ๋ค๋ก ๋ฐ๋ฆฌ๊ฒ๋๊ณ , ๋งจ ๋ค์์๋ ๊ฐ์ฒด๋ฅผ ํ์
ํ๋ฉด์ BitmapPool์ ์ ์ง์ํค๋ ๊ฒ์ด๋ค. LRU ์๊ณ ๋ฆฌ์ฆ์ ์ด์ฉํ์ง ์๋๋ค๋ฉด ์ฒ์ BitmapPool์ด ๊ฐ๋ ์ฐฐ ๋๊น์ง๋ ๋ฌธ์ ์์ด ๋์ํ์ง๋ง, ๋นํธ๋งต์ ์ฌ์ฌ์ฉํ๋ ์์ ๋ถํฐ๋ ํน์ ๋นํธ๋งต๋ง ์ฌ์ฌ์ฉ๋ ์ ์์ผ๋ฉฐ, ์ฑ์ด ๋๋ ๋๊น์ง ๋ฉ๋ชจ๋ฆฌ๊ฐ ์ค์ด๋ค์ง ์๊ฒ๋๋ค. ์์ธํ ๋ด์ฉ์ ์ด ๋ธ๋ก๊ทธ๋ฅผ ์ฐธ๊ณ ํ๊ธธ ๋ฐ๋๋ค. :)
๋ํ์ ์ธ ์ด๋ฏธ์ง ๋ก๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ธ Glide
์์ ๊ตฌํํ LruBitmapPool Class ๋ด๋ถ๋ฅผ ๋ณด๋ฉฐ, LruBitmapList์ bitmapList๊ฐ ์ด๋ป๊ฒ ์ฐ์ด๊ณ ์๋์ง ์ดํด๋ณด์.
public class LruBitmapPool implements BitmapPool {
private static final String TAG = "LruBitmapPool";
private static final Bitmap.Config DEFAULT_CONFIG = Bitmap.Config.ARGB_8888;
private final LruPoolStrategy strategy;
private final Set<Bitmap.Config> allowedConfigs;
private final BitmapTracker tracker;
// Pool์ ๋ค์ด์ฌ์ ์๋ ์ต๋ ํฌ๊ธฐ
private long maxSize;
// ํ์ฌ pool์ ๋ค์ด๊ฐ bitmap byte ํฌ๊ธฐ
private long currentSize;
Glide๋ด LruBitmapPool Class ์์๋ strategy(LruPoolStrategy)๊ฐ ๊ณง LRU ๊ธฐ๋ฐ์ผ๋ก ๊ตฌํ๋ ๋ฆฌ์คํธ์ด๋ฉฐ, tracker(BitmapTracker)๊ฐ Bitmap size ์์ผ๋ก ์ ๋ ฌ๋ ๋ฆฌ์คํธ์ด๋ค.
@Override
public synchronized void put(Bitmap bitmap) {
if (bitmap == null) {
throw new NullPointerException("Bitmap must not be null");
}
// isRecycled: ์ฌํ์ฉ๋ bitmap์ธ์ง ์ฌ๋ถ
if (bitmap.isRecycled()) {
throw new IllegalStateException("Cannot pool recycled bitmap");
}
// isMutable: canvas๋ฅผ ์ป์ ์ ์๋ bitmap ์ธ์ง ์ฌ๋ถ.
if (!bitmap.isMutable()
|| strategy.getSize(bitmap) > maxSize
|| !allowedConfigs.contains(bitmap.getConfig())) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(
TAG,
"Reject bitmap from pool"
+ ", bitmap: "
+ strategy.logBitmap(bitmap)
+ ", is mutable: "
+ bitmap.isMutable()
+ ", is allowed config: "
+ allowedConfigs.contains(bitmap.getConfig()));
}
bitmap.recycle();
return;
}
// pool์ ๋ฃ์ผ๋ ค๋ bitmap์ ์ฌ์ด์ฆ๋ฅผ ์ป์ด์จ๋ค.
final int size = strategy.getSize(bitmap);
strategy.put(bitmap);
tracker.add(bitmap);
puts++;
currentSize += size;
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Put bitmap in pool=" + strategy.logBitmap(bitmap));
}
dump();
evict();
}
Bitmap์ Pool์ ๋ฃ์ ์ ์๋ ์กฐ๊ฑด์ ์ถฉ์กฑ์ํจ๋ค๋ฉด, strategy & tracker์ bitmap์ด ๋ค์ด๊ฐ๊ฒ๋๋ค.
fyi. Lru ๊ธฐ๋ฐ์ธ strategy ๋ ์ต๊ทผ์ ๋ค์ด์จ bitmap์ผ์๋ก ๋ฆฌ์คํธ์ ๋งจ ์์ผ๋ก ๋ฐฐ์น์์ผ์ผํ๋๋ฐ, ๊ทธ ๋ก์ง์ด
LruPoolStrategy
๊ตฌํ์ฒด ๋ด๋ถ์ ์กด์ฌํ๊ฒ ๋๋ค.
// Pool์ ์ต๋ ์ฌ์ฉ๋์ ๋๊ธฐ๋ฉด ํฌ๊ธฐ๋ฅผ ์ค์ฌ์ค๋ค.
private synchronized void trimToSize(long size) {
while (currentSize > size) {
//LRU List์์ ์ญ์ ํ๋ค.
final Bitmap removed = strategy.removeLast();
if (removed == null) {
if (Log.isLoggable(TAG, Log.WARN)) {
Log.w(TAG, "Size mismatch, resetting");
dumpUnchecked();
}
currentSize = 0;
return;
}
// BitmapList์์ ์ญ์ ํ๋ค.
tracker.remove(removed);
currentSize -= strategy.getSize(removed);
evictions++;
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Evicting bitmap=" + strategy.logBitmap(removed));
}
dump();
removed.recycle();
}
}
trimToSize()๋ฅผ ์ด์ฉํ๋ฉด pool์ด ์ต๋์ฌ์ฉ๋์ ๋์ด์ ๊ฒฝ์ฐ ์ฐธ์กฐ๋ฅผ ๊ฐ์ฅ ์ ๊ฒํ๊ณ ์๋ lastIndex ๋ถํฐ ๊ฐ์ฒด๋ฅผ ์ง์๊ฐ๋ฉฐ ํฌ๊ธฐ๋ฅผ ์ค์ฌ์ค ์ ์๋ค.
@Override
@Nullable
public Bitmap get(int width, int height, Bitmap.Config config) {
final int size = Util.getBitmapByteSize(width, height, config);
Key key = keyPool.get(size);
Integer possibleSize = sortedSizes.ceilingKey(size);
if (possibleSize != null && possibleSize != size && possibleSize <= size * MAX_SIZE_MULTIPLE) {
keyPool.offer(key);
key = keyPool.get(possibleSize);
}
final Bitmap result = groupedMap.get(key);
// ๋นํธ๋งต์ ์ฐพ์ ๊ฒฝ์ฐ, pool์์ ์ ๊ฑฐํ๋ค.
if (result != null) {
result.reconfigure(width, height, config);
decrementBitmapOfSize(possibleSize);
}
return result;
}
์ด๋ฏธ์ง๋ฅผ ๋ก๋ํ๊ธฐ ์ํด pool์์ ํ์ํ ๋นํธ๋งต์ ๊ฐ์ ธ์จ๋ค.
Glide
์ BitmapPool
๋ด๋ถ๋ง ์ ๊น ๋ณด์๋๋ฐ๋ ๋ถ๊ตฌํ๊ณ , ์ด๋ฏธ์ง ์ฒ๋ฆฌ์ ๋ํ ๋ง์ ํ๋ก์ธ์ค๊ฐ ๋ด๊ฒจ์ ธ์๋ค. ์ด๋ฏธ์ง๋ทฐ์ ์ด๋ฏธ์ง๋ฅผ ๋ฐ๋ก ๋ก๋ํ๋ ค๊ณ ํ๋ค๋ฉด, Out of memory, Slow Loading, Bitmap Caching, Memory Cache, Gabarge collector ์ด์ ๋ฑ๋ฑ... ๊ฐ๋ฐ์๊ฐ ๊ณ ๋ คํด์ผํ๋ ์์๊ฐ ๊ต์ฅํ ๋ง๊ธฐ์ ์ด๋ฅผ third party library์๊ฒ ์์ํ๋ ๊ฒ์ด๋ค. ๊ทธ๋ ๋ค๋ฉด ์์์ ์ค๋ช
ํ Glide๋ฅผ ํฌํจํด ์๋๋ก์ด๋์์ ์ฌ์ฉํ ์ ์๋ ์ด๋ฏธ์ง๋ก๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ข
๋ฅ๋ ๋ฌด์์ด ์์ผ๋ฉฐ, ๊ฐ๊ฐ ์ด๋ค ์ฐจ์ด์ ์ด ์๋์ง ๋ค์ ํฌ์คํธ์์ ์ดํด๋ณด์.