AssetManager Thread Pool Size: Memory Impact on Android
0 reputation · 17 Mar 2023, 13:13 UTC
0 reputation · 17 Mar 2023, 13:13 UTC
Goal: Determine how AssetManager.setThreadPoolSize influences peak memory consumption when loading large textures on Android devices.
Constraints: Android's limited heap, varying device RAM, and the fact that AssetManager loads assets asynchronously in a thread pool. The API documentation does not describe the relationship between thread count and memory usage, leaving developers uncertain whether increasing the pool size will lead to higher memory spikes or smoother loading.
Uncertainty: Does a larger thread pool cause multiple textures to be resident in memory simultaneously, thereby raising peak usage? Is there a documented safe threshold for thread count on low‑memory devices?
Questions:
1. Does setThreadPoolSize affect peak memory usage on Android when loading large textures?
2. Is there a recommended maximum thread count for devices with <1 GB RAM?
3. What profiling approach best captures memory spikes during concurrent asset loading?
32775 reputation · 17 Mar 2023, 16:56 UTC
AssetManager does not have a setThreadPoolSize method. All AssetManager operations (open(), list(), close()) are synchronous and run on the calling thread. Any parallelism — and any associated memory pressure — comes from your application code (custom ExecutorService, Glide, Coil, coroutines, RxJava, etc.), not from the framework.
mmap). This is the dominant factor.Runnable objects (each referencing large assets) can cause OOM before thread stacks do.InputStream — every AssetManager.open() returns a native-backed stream; failing to close() leaks native buffers (visible as "Native" heap in profiler).Only indirectly. If you create a thread pool and submit multiple texture-loading tasks concurrently, multiple decoded bitmaps can be resident simultaneously. The peak heap ≈ (number of concurrent decodes) × (bitmap size). The pool size itself is a minor contributor; the concurrency level you choose is the lever.
Bound the pool to Runtime.getRuntime().availableProcessors() (typically 4–8 on modern low-end devices). Do not use fixed sizes like 16 or 32. For texture loading specifically, consider 1–2 threads on constrained devices to limit simultaneous bitmap allocations. Use a bounded queue (new ThreadPoolExecutor(core, max, keepAlive, TimeUnit.SECONDS, new LinkedBlockingQueue<>(capacity))) so back-pressure naturally throttles submission.
adb shell dumpsys meminfo <package> — capture before/after snapshots; watch Native Heap and Graphics columns for texture memory.Debug.getNativeHeapAllocatedSize() — log periodically from a background thread to catch spikes between GC cycles.StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder().detectDiskReads().penaltyLog().build()) surfaces accidental main-thread asset I/O.BitmapFactory.decodeStream(in, null, opts) with inSampleSize to avoid full-resolution decode.AssetFileDescriptor fd = am.openFd(name); MappedByteBuffer mb = fd.createInputStream().getChannel().map(FileChannel.MapMode.READ_ONLY, 0, fd.getDeclaredLength()) keeps data off Java heap.Semaphore permits = new Semaphore(maxConcurrent); acquire before decode, release after bitmap is handed off (or recycled).finally — never rely on finalizers.setThreadPoolSize — it does not exist on AssetManager (verify in android.content.res.AssetManager source).-memory 512) to measure real peak.Assumption: you are using a custom thread pool or image-loading library that parallelizes AssetManager.open() calls. If you are not, peak memory is determined solely by how many bitmaps you hold simultaneously, not by any framework thread pool.
Use comments to ask for clarification. Post a solution as an answer.
5,140 reputation · 17 Mar 2023, 19:54 UTC
One clarification worth making explicit: setThreadPoolSize belongs to libGDX's AssetManager, which wraps an internal AsyncExecutor — not the Android SDK android.content.res.AssetManager, which is purely synchronous. The previous answer's premise applies to the wrong class.
With that corrected, the answer to question 1 is yes: pool size N means up to N textures can be decoded concurrently, so peak memory scales roughly with N × per-texture footprint. A 2048×2048 RGBA8888 texture is ~16 MB decoded, ~21 MB with mipmaps, so even 4 concurrent loads can add ~80 MB transiently — significant against typical ART heap limits.
For question 3, a practical approach: run a bulk-load scene with pool sizes 1, 2, and 4 while recording Debug.getNativeHeapAllocatedSize() and the Java heap in Android Studio Profiler, logging peaks around finishLoading(). The point where load time stops improving but peak memory keeps climbing is your ceiling — often 2 threads on low-RAM devices, though this needs verification on real hardware, not emulators.