首页 > 解决方案 > 即使 Old Gen 已满,JVM 也不存在 OOM 问题

问题描述

我看到了这个关于 JVM 内存的有趣问题。这真的很令人困惑。考虑这种情况:不断将一个新对象放入 HashMap 中,而这个对象只有一个 String 字段。应该是OOM,因为我没有覆盖hashcode()和equals(),结果结果不是,JVM一直在做GC。所有对象都被HashMap强引用,为什么没有OOM?

public class FinalTest {
    private final String key;
    FinalTest(String key){
        this.key = key;
    }
    public void getKey(){
        System.out.println( key);
    }
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException{
        Map<FinalTest,String> map = new HashMap<>();
        for(;;){
            map.put(new FinalTest("a"),"v");
            System.out.println(map.toString());
        }
    }
}

jconsole 截图: jconsole 截图

然后我删除了那个打印命令,结果不一样了......

删除 println 后

老一代用法: 老一代的使用

曲线在最后一刻下降,因为伊甸空间被清除了。然后看起来进程被阻止(老一代已满)。此外,即使老一代已满,它仍然不会产生 OOM 异常。

为什么这两种情况不同?为什么没有OOM异常?

标签: javajvm

解决方案


对于循环的每次迭代,map.toString()分配的内存比实际大得多map.put(new FinalTest("a"),"v");,因此 toString 隐藏了 map.put 分配的影响。

因此,您的堆内存图表显示了很多尖峰,因为有大量可垃圾回收的字符串。

更改日志行以打印大小,您将看到 map.put() 调用正在逐渐消耗所有可用内存,并且当收集整数->字符串堆时,每次 GC 的更改越来越少:

 System.out.println(map.size());

一旦您进行了上述更改 - 或注释掉了System.out.println运行速度将会提高,因为需要更少的 GC,并且您将获得java.lang.OutOfMemoryError: Java heap space预期的结果。


推荐阅读