StackOverflowError from Collections.unmodifiableMap get()?

大兔子大兔子 提交于 2021-02-08 06:37:40

问题


i was just handed the following stack trace:

2015-12-20 07:43:36.151 -0800 ERROR o.s.s.s.TaskUtils$LoggingErrorHandler [taskExecutor-6] Unexpected error occurred in scheduled task.
java.lang.StackOverflowError
    at java.util.Collections$UnmodifiableMap.get(Collections.java:1454) ~[?:1.8.0_65]
    at java.util.Collections$UnmodifiableMap.get(Collections.java:1454) ~[?:1.8.0_65]
    at java.util.Collections$UnmodifiableMap.get(Collections.java:1454) ~[?:1.8.0_65]
    at java.util.Collections$UnmodifiableMap.get(Collections.java:1454) ~[?:1.8.0_65]

looking at the source code this is the impl of get() (Collections.java:1454):

public V get(Object key)                 {return m.get(key);}

so this should only be possible if somehow this.m = this, but i cannot reproduce such a scenario.

how is this even possible?


回答1:


To elaborate the comment by Sotirios: The behavior can be reporoduced with something like this:

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

public class UnmodifiableMapStackOverflow
{
    public static void main(String[] args)
    {
        int depth = 20000; 
        test(depth);
    }

    private static void test(int depth)
    {
        Map<String, String> map = new HashMap<String, String>();
        map.put("X", "Y");
        for (int i =0; i<depth; i++)
        {
            map = Collections.unmodifiableMap(map);
        }
        String value = map.get("X");
        System.out.println("At "+depth+" got "+value);
    }
}

(The value that is required for the depth may depend on many, many factors - in doubt, you may have to increase it to observe the effect).

Of course, this code is blatantly and obviously wrong. The key point is that you might accidentally do something similar. A more complex scenario might be the following:

  • The map is stored in a field, using a setMap method
  • The map is returned in a getMap method. But becuase you should usually not return modifiable internal data structures, an unmodifiable view is returned.
  • This unmodifiable view is set again, causing one "layer" around the original mal during each call.

Like in this code:

import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;

public class UnmodifiableMapStackOverflowComplex
{
    public static void main(String[] args)
    {
        UnmodifiableMapStackOverflowComplex c = 
            new UnmodifiableMapStackOverflowComplex();

        Map<String, String> map = new LinkedHashMap<String, String>();
        map.put("X", "Y");
        c.setMap(map);

        for (int i=0; i<100000; i++)
        {
            Map<String, String> m = c.getMap();
            System.out.println("At "+i+": "+m.get("X"));
            c.setMap(m);
        }
    }

    private Map<String, String> map;
    Map<String, String> getMap()
    {
        // It's a good practice to only return unmodifiable VIEWS
        // on internal data structures:
        return Collections.unmodifiableMap(map);        
    }
    void setMap(Map<String, String> map)
    {
        this.map = map;
    }


}

Until now, this is just a guess, but the only possible reason that I can think of (unless you're doing some nasty reflection hacks somewhere).

In order to detect whether this is actually the case here, you might try to set a breakpoint at the method that eventually calls Map#get, and inspect the object in the debugger.



来源:https://stackoverflow.com/questions/34384953/stackoverflowerror-from-collections-unmodifiablemap-get

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!