Skip to content

Instantly share code, notes, and snippets.

What would you like to do?

ArrayMap ClassCastException Deep Analyze


I'm a developer of 企鹅FM. Our app constantly report ClassCastException on ArrayMap, some stack trace looks like this.

Intent i = new Intent(ACTION);
i.putExtra(KEY, VALUE); // crashes here

Just incredible.


  1. A buggy program uses ArrayMap without proper synchronization causing data stored in ArrayMap being corrupted.
  2. ArrayMap has a Memory Pool, which stores that corrupted data, causing anyone obtaining data from that pool crashes!

So the very cause of the issue is a program using ArrayMap (or Intent, Bundle, etc... which is backed by ArrayMap) with thread issue(s).

And the bad design of the memory pool in ArrayMap causing anyone uses it may crash. (propagated error) Besides, it is hard to find out who is using ArrayMap improperly only according to the crash stack trace.

Short Analyze

ArrayMap is designed to be memory efficient. It has a memory pool caches short Object[] array. When a new array is obtained, ArrayMap first tries to obtain one from the memory pool. And when an array is abandoned AM tries to put it in the memory pool as long as the pool is not full (10 at most).

And the reason is multi-thread manipulation of the same ArrayMap.

Assume we have an ArrayMap, say am.

  • am recycle an array to the memory pool.
  • just before the recycle is complete, another thread use am set some value to array[0].

then the array in the memory pool is corrupted!

Anyone obtains an array from the memory pool will crash!

Deep Analyze

DataStructure of ArrayMap

public final class ArrayMap<K, V> {
    // memory pool, array reference is guard by ArrayMap.class
    // but the content of the array is not guarded!
    static Object[] mBaseCache;
    static int mBaseCacheSize;

    int[] mHashes;
    // holds data in arrayMap, not guarded by anything in ArrayMap
    // code 0, more info in later this article
    Object[] mArray;

    // write operation
     public void put(K key, V value) {
        mArray[index] = value;

recycle array to memory pool

// mArray is passed in as array
private static void freeArrays(final int[] hashes, final Object[] array, final int size) {
    if (hashes.length == BASE_SIZE) {
        synchronized (android.util.ArrayMap.class) {
            if (mBaseCacheSize < CACHE_SIZE) {
                // code 1, more info in later this article
                array[0] = mBaseCache;
                array[1] = hashes;
                for (int i = (size << 1) - 1; i >= 2; i--) {
                    array[i] = null;
                // code 2, more info in later this article
                mBaseCache = array;

code 0

The parameter array is always the mArray field of an ArrayMap. This is the array used to keep data, in ArrayMap, similar to HashMap. Each time this array needs to grow, it first called freeArray to check if the old array can be put in the memory pool.

code 1

mBaseCache stores as much as 10 arrays, arranged as a Singly Linked List. the array[0] is a reference/pointer to the next element, and array[1] is some payload (which we can ignore).

code 2

Make current array as the head of Link List, and assume this array will not be touched until it been polled from the memory pool.

poll from the memory pool

 private void allocArrays(final int size) {
    if (size == BASE_SIZE) {
        synchronized (android.util.ArrayMap.class) {
            if (mBaseCache != null) {
                final Object[] array = mBaseCache;
                mArray = array;
                mBaseCache = (Object[]) array[0]; // code 3
                mHashes = (int[]) array[1];
                array[0] = array[1] = null;

code 3

This is where most crash happens! I've seen all kind classes cast to Object[] when crash.

In terms of Singly Linked List as described above, the array[0] is a reference/pointer to next element, so what this code does is:

  1. poll one element from the linked list
  2. make it head be the next element

crash scenerio

1. CORRUPT the Memory Pool

We explain the conclusion again with some code.

We still assume an ArrayMap is ben used in multi-thread without synchronization.

  1. "Thread one" is running at freeArrays, putting an array, say am.mArray, to the memory pool, and has run after "code 1" and just before "code 2".
  2. At the same time, "thread two" operates 'am', putting something to am.mArray[0]

So the array in memory pool is corrupted! The first element is supposed to be next element of the Singly Linked List, but it's now something else been put int thread-2.

2. pool data from the memory pool

After the corruption happened, and no one will realize that.

At some time in the future, an ArrayMap (maybe still am) happily polled an array from the memory pool. Then it will crash as we analyzed in code3.

How to fix this issue

There is no good way to avoid corruption, without making the WHOLE ArrayMap thread safe.

The best way I preferred is to avoid any kind of memory pool since JVM and GC is the best memory pool!


How to reproduce this crash

Just following the conclusion, we came up with this demo. It will crash with a ClassCastException after a short time.

    private void arrayMapClassCastExceptionDemo() {
        final ArrayMap<String, String> criminal = new ArrayMap<>();
        final ArrayMap<String, String> victim = new ArrayMap<>();

        new Thread(() -> {
            while (true) {
                try {
                    if (criminal.size() >= 8) {
                        // trigger freeArrays
                } catch (IndexOutOfBoundsException ingnore) {
                    // multi thread issue
        }, "criminal-thread-0").start();

        new Thread(() -> {
            // may corrupt ArrayMap
            while (true) {
                try {
                    // may crash here
                    criminal.put("" + System.currentTimeMillis(), "");
                } catch (ArrayIndexOutOfBoundsException e) {
                    // ignore
        }, "criminal-thread-1").start();

        new Thread(() -> {
            while (true) {
                // poll array from memory pool
                victim.put("x", ""); // may crash here
        }, "victim").start();

This comment has been minimized.

Copy link

@cutler cutler commented May 4, 2017

can you speek in chinese? ok? brother!


This comment has been minimized.

Copy link

@ryecrow ryecrow commented May 12, 2017

Such an analysis is beneficial to developers around the world, not only to Chinese developers like us. So the usage of English is a better option as it is more widely used than Chinese.


This comment has been minimized.

Copy link

@dkmeteor dkmeteor commented May 19, 2017

Great work.


This comment has been minimized.

Copy link

@dbof10 dbof10 commented May 10, 2018

Thank you


This comment has been minimized.

Copy link

@JeffMony JeffMony commented May 18, 2018



This comment has been minimized.

Copy link

@longerian longerian commented Jan 4, 2019

在 support 25.3.0上没有复现。
在 support 26.1.0 上 crash,但crash 不是这个,而是:

01-04 17:50:28.698 24754 25079 E AndroidRuntime: java.util.ConcurrentModificationException
01-04 17:50:28.698 24754 25079 E AndroidRuntime:   at
01-04 17:50:28.698 24754 25079 E AndroidRuntime:   at
01-04 17:50:28.698 24754 25079 E AndroidRuntime:   at
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment