1. 堆
堆是JVM内存中最大的区域,我们创建的各种对象基本上都存在堆中,包括字符串常量池也是在堆中维护,并且各个线程都可以共享堆中的内容,而对于普通创建的对象在堆中的存储位置分为新生代和老年代,根据不同的情况将对象存入对应的区域。
2. 新生代、老年代是什么
我们先看看新生代和老年代的内存比例大小
我们可以看到,在堆中 新生代和老年代的内存大小比例为1:2,而在新生代中有分了三个部分,Eden去和两个幸存者区域(from、to),他们三块区域的内存大小比例为8:1:1。
3. 创建对象是堆如何存储
有了前面的堆中新生代老年代区域基础后,我们看看一个刚刚new出来的热乎对象是在堆中怎么保存的。
大部分情况下,对象会在 Eden
区生成,当 Eden
区装填满的时候,会触发 Young Garbage Collection
,即 YGC
垃圾回收的时候,在 Eden
区实现清除策略,没有被引用的对象则直接回收。
依然存活的对象会被移送到 Survivor
区。Survivor
区分为 from
和 to
两块内存区域。每次 YGC
的时候,它们将存活的对象复制到未使用的Survivor
空间(from
或 to
),然后将当前正在使用的空间完全清除,交换两块空间的使用状态。每次交换时,对象的年龄会加+1
。
如果 YGC
要移送的对象大于 Survivor
区容量的上限,则直接移交给老年代。一个对象也不可能永远呆在新生代,在 JVM
中 一个对象从新生代晋升到老年代的阈值默认值是 15
,可以在 Survivor
区交换 14 次之后,晋升至老年代。
堆区最容易出现的就是 OutOfMemoryError
错误,这种错误的表现形式会有以下两种:
OutOfMemoryError: GC Overhead Limit Exceeded
: 当JVM
花太多时间执行垃圾回收,并且只能回收很少的堆空间时,就会发生此错误。OutOfMemoryError: Java heap space
:假如在创建新的对象时, 堆内存中的空间不足以存放新创建的对象, 就会引发此错误。
个人感觉(猜的):新生代的幸存者区设置两个的意义就是为了记录这些对象的时候年龄不乱掉,因为一次次YGC时候,要将Eden区幸存下来对象和之前from区的对象都转移到to区,这时候就要判断要移入to中的这些对象那些是之前的幸存者那些是这次YGC产生的,然后给这些对象的年龄在之前基础上+1;如果只有一个幸存者区的话,就无法判断移入的这些对象那些是之前幸存的那些本次YDC幸存的了。