Direct Answer
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.
What Actually Drives Peak Memory
- Loaded asset data — bitmaps, raw byte arrays, strings — lives in Java heap or native memory (via
mmap). This is the dominant factor.
- Thread stacks — each worker thread adds ~1 MB (default stack size) plus object allocation overhead. A pool of 8 threads ≈ 8 MB stack overhead, which is usually negligible compared to texture data.
- Queued tasks — an unbounded work queue holding
Runnable objects (each referencing large assets) can cause OOM before thread stacks do.
- Unclosed
InputStream — every AssetManager.open() returns a native-backed stream; failing to close() leaks native buffers (visible as "Native" heap in profiler).
Answers to Your Three Questions
1. Does thread pool size affect peak memory when loading large textures?
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.
2. Recommended maximum thread count for devices with <1 GB RAM?
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.
3. Best profiling approach for memory spikes during concurrent asset loading?
- Android Studio Memory Profiler — record a session while loading; filter allocations by "Thread" to correlate heap growth with worker threads.
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 disk-read detection —
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder().detectDiskReads().penaltyLog().build()) surfaces accidental main-thread asset I/O.
Practical Patterns to Reduce Peak Memory
- Stream + downsample — use
BitmapFactory.decodeStream(in, null, opts) with inSampleSize to avoid full-resolution decode.
- Memory-map large assets —
AssetFileDescriptor fd = am.openFd(name); MappedByteBuffer mb = fd.createInputStream().getChannel().map(FileChannel.MapMode.READ_ONLY, 0, fd.getDeclaredLength()) keeps data off Java heap.
- Sequential loading with a semaphore —
Semaphore permits = new Semaphore(maxConcurrent); acquire before decode, release after bitmap is handed off (or recycled).
- Close streams in
finally — never rely on finalizers.
Verification Checklist
- Search your codebase for
setThreadPoolSize — it does not exist on AssetManager (verify in android.content.res.AssetManager source).
- Identify where parallel asset loading is actually introduced (custom executor, image library config).
- Run the profiler steps above on a low-RAM device (or emulator with
-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.