【问题标题】:JVM does not have OOM problem even if Old Gen is full即使 Old Gen 已满,JVM 也不存在 OOM 问题
【发布时间】:2020-07-23 05:44:34
【问题描述】:

我看到了这个关于 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 截图:

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

老一代用法:

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

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

【问题讨论】:

  • 你怎么知道老根已经满了?
  • @gudok 最后一张图片,右下角,Heap usage table的第一列表示使用了多少old gen space。

标签: java jvm


【解决方案1】:

对于循环的每次迭代,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。

【讨论】:

    猜你喜欢
    • 2011-08-21
    • 1970-01-01
    • 1970-01-01
    • 2019-04-18
    • 2021-07-11
    • 1970-01-01
    • 2023-03-23
    • 2012-09-29
    • 1970-01-01
    相关资源
    最近更新 更多