Collections.unmodifiedMap get()中的StackOverflow错误



i刚刚收到以下堆栈跟踪:

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]

查看源代码,这是get()的impl(Collections.java:1454):

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

所以这应该只有在某种程度上是可能的。m=this,但我不能重现这样的场景。

这怎么可能呢?

为了详细说明Sotirios的评论:行为可以用这样的东西来重新产生:

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);
    }
}

depth所需的值可能取决于许多因素——毫无疑问,您可能必须增加它才能观察效果)。

当然,这个代码显然是错误的。关键是你可能会不小心做一些类似的事情。一个更复杂的场景可能如下:

  • 使用setMap方法将地图存储在字段中
  • 映射以getMap方法返回。但是,由于通常不应返回可修改的内部数据结构,因此会返回不可修改的视图
  • 这个不可修改的视图被再次设置,在每次调用过程中会在原始mal周围产生一个"层"

类似于此代码:

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;
    }

}

到目前为止,这只是一个猜测,但这是我能想到的唯一可能的原因(除非你在某个地方做一些令人讨厌的反思黑客)。

为了检测这里是否真的是这样,您可以尝试在最终调用Map#get的方法上设置一个断点,并在调试器中检查对象。

最新更新