nostra13/Android-Universal-Image-Loader · critical · IllegalStateException

{className}.sizeOf() is reporting inconsistent results!

Error message

{className}.sizeOf() is reporting inconsistent results!

What it means

LruMemoryCache.trimToSize throws IllegalStateException when its internal size counter goes negative or disagrees with map emptiness (map empty but size != 0). The counter is maintained by sizeOf(key, bitmap) during put/remove/evict; inconsistency means sizeOf returned different values for the same bitmap at different times. This class's sizeOf is fixed (bitmap.getRowBytes() * bitmap.getHeight()), so this error in stock UIL indicates concurrent corruption or a subclass with a non-deterministic sizeOf.

Source

Thrown at library/src/main/java/com/nostra13/universalimageloader/cache/memory/impl/LruMemoryCache.java:84

			}
		}

		trimToSize(maxSize);
		return true;
	}

	/**
	 * Remove the eldest entries until the total of remaining entries is at or below the requested size.
	 *
	 * @param maxSize the maximum size of the cache before returning. May be -1 to evict even 0-sized elements.
	 */
	private void trimToSize(int maxSize) {
		while (true) {
			String key;
			Bitmap value;
			synchronized (this) {
				if (size < 0 || (map.isEmpty() && size != 0)) {
					throw new IllegalStateException(getClass().getName() + ".sizeOf() is reporting inconsistent results!");
				}

				if (size <= maxSize || map.isEmpty()) {
					break;
				}

				Map.Entry<String, Bitmap> toEvict = map.entrySet().iterator().next();
				if (toEvict == null) {
					break;
				}
				key = toEvict.getKey();
				value = toEvict.getValue();
				map.remove(key);
				size -= sizeOf(key, value);
			}
		}
	}

View on GitHub (pinned to ba33ec64d0)

Solutions

  1. If overriding sizeOf(), make it deterministic and stable for the lifetime of the entry (compute once, never change)
  2. Do not recycle bitmaps while they are still cached; let the LRU evict them
  3. If it happens without subclassing, look for thread-unsafe external mutation of the map (e.g. via iterator from another thread) and route all access through the cache API

Example fix

// before
class MyCache extends LruMemoryCache {
    @Override
    protected int sizeOf(String key, Bitmap value) {
        return value.getAllocationByteCount() - compressed.get(key); // varies over time
    }
}

// after
class MyCache extends LruMemoryCache {
    @Override
    protected int sizeOf(String key, Bitmap value) {
        return value.getRowBytes() * value.getHeight(); // stable for the entry lifetime
    }
}
Defensive patterns

Strategy: validation

Validate before calling

// If you subclass LruMemoryCache, verify sizeOf stability:
// insert, read sizeOf twice, assert equal
Bitmap b = decode(uri);
int s1 = cache.sizeOf(uri, b);
int s2 = cache.sizeOf(uri, b);
assert s1 == s2 && s1 >= 0 : "sizeOf must be deterministic";

Try / catch

try {
    cache.put(uri, bitmap);
} catch (IllegalStateException e) {
    // accounting corrupted: clear and rebuild
    cache.clear();
    cache.put(uri, bitmap);
}

Prevention

When it happens

Trigger: Subclassing LruMemoryCache and overriding sizeOf() with a value that changes between insert and eviction (e.g. depends on mutable state, or returns the bitmap's current byte size after the bitmap was recycled); calling bitmap.recycle() on cached bitmaps so later sizeOf calls return different values; reflective/unsynchronized access from multiple threads despite the synchronized blocks.

Common situations: Custom memory caches extending LruMemoryCache with per-entry soft references or compressed sizes; an aggressive bitmap-recycling strategy recycling bitmaps still in the cache; bugs where getMemoryCache() is replaced while tasks are in flight.

Related errors


AI-assisted analysis of nostra13/Android-Universal-Image-Loader@ba33ec64d0 (2026-08-14). Data as JSON: /api/errors/98e2515390a78835. Report an issue: GitHub.