JVM即使老年代已满也不会出现OOM问题。

huangapple go评论92阅读模式
英文:

JVM does not have OOM problem even if Old Gen is full

问题

我看到了关于JVM内存的有趣问题。这真的令人困惑。考虑这种情况:不断地将一个新对象放入HashMap中,而这个对象只有一个String字段。按理说,由于我没有重写hashcode()和equals()方法,应该会发生OOM(内存溢出)的,但实际结果却没有,JVM一直在进行垃圾回收。所有的对象都被HashMap强引用,为什么没有发生OOM呢?

下面是你提供的代码部分:

```java
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截图][1]

然后我删除了打印语句,结果发生了变化...

![删除println后][2]

Old Gen使用情况:

![Old Gen使用情况][3]

曲线在最后一刻下降,因为Eden Space(伊甸园空间)被清除了。然后看起来进程被阻塞住了(Old Gen已满)。另外,即使老年代已满,仍然没有产生OOM异常。

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


Note: I have translated the provided text into Chinese as per your request.

<details>
<summary>英文:</summary>

I saw this interesting question about JVM memory. It is really confusing. Consider this situation: keep putting a new object into a HashMap, while this object only has a String field. It is supposed to have a OOM because I didn&#39;t override hashcode() and equals(), but the result turns out to be not, JVM keeps doing GC. All objects are strong referenced by HashMap, why there&#39;s no 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 screenshot:
![jconsole screenshot][1]

Then I deleted that print command and the result is different...

![after deleting println][2]

Old Gen usage:
![Old Gen usage][3]

The curve goes down at the last moment because the Eden Space is cleared. Then it looks like the process is blocked (Old Gen is full). Also, even though the old gen is full, it still doesn&#39;t generate OOM exception. 

Why is these two situations different? Why there is no OOM exception?


  [1]: https://i.stack.imgur.com/1EbLV.png
  [2]: https://i.stack.imgur.com/6DwY3.png
  [3]: https://i.stack.imgur.com/4H2gH.png

</details>


# 答案1
**得分**: 2

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

因此,您的堆内存图显示出现了很多峰值,因为有大量可以进行垃圾回收的字符串。

将日志行更改为打印大小,您将看到 map.put() 调用逐渐消耗了所有可用内存,每次整数->字符串堆收集时的更改要少得多且更小:

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

一旦您按上述更改进行了更改,或者将 `System.out.println` 注释掉,运行速度将会提高,因为需要的垃圾回收会少得多,然后您将如预期般收到 `java.lang.OutOfMemoryError: Java heap space`。

<details>
<summary>英文:</summary>

For each iteration of your loop, `map.toString()` is allocating much bigger chunk of memory than `map.put(new FinalTest(&quot;a&quot;),&quot;v&quot;);` does, so toString hides the effect of map.put allocation.

Thus your heap memory chart is showing a lot of spikes because there are a large amount of String which are garbage collectable.

Change the log line to print the size, and you will see that the map.put() calls are gradually consuming all the available memory with much fewer and smaller changes each GC when the  integer-&gt;string heap is collected:

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

Once you have changed as above - or commented out `System.out.println` the speed of the run will increase because much less GC is needed and you will get your `java.lang.OutOfMemoryError: Java heap space` as expected.



</details>



huangapple
  • 本文由 发表于 2020年7月23日 13:44:34
  • 转载请务必保留本文链接:https://go.coder-hub.com/63047629.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定