Skip to content

Latest commit

 

History

History
51 lines (26 loc) · 4.77 KB

2017-12-08.Java 对象内存分配与回收策略.md

File metadata and controls

51 lines (26 loc) · 4.77 KB

一、对象优先在 Eden 分配

在大多数的情况下,对象在新生代 Eden 区中分配内存空间。当 Eden 区没有足够的空间进行分配时,这时虚拟机将执行一次 Minor GC,由于 Eden 区没有足够的内存空间大小分配对象,对象会先考虑在新生代中的 Survivor 区分配空间,如果 Survivor 区中也没有足够的空间大小为对象分配内存,那么就会通过分配担保策略将对象提前转移到老年代中去。

二、大对象直接进入老年代

大对象是指需要大量连续内存空间的对象,最典型的就是数组以及很长的字符串。大对象对虚拟机的内存分配来说是一个坏消息,但是更糟糕的是你创建了一个生命周期很短的大对象 (这点我们在写程序时要格外的注意),因为这样容易导致内存中还有不少的空间时就得提前进行垃圾收集以腾出足够大的连续内存空间来安置大对象。

虚拟机提供了一个 -XX:PretenureSizeThreshold 参数,令大于这个设置值的对象直接在老年代进行分配内存。

这样做可以可以避免在 Eden 区与两个 Survivor 区之间发生大量的内存复制 (新生代使用复制算法进行垃圾回收,如果你创建了一个大对象并且它的生命周期较长,在新生区发生垃圾回收是非常频繁的,这就意味着你创建的大对象在执行 GC 时就需要被复制很多次,这样做是非常不好的)。

三、长期存活的对象将进入老年代

虚拟机使用了分代收集的思想来管理内存,那么再进行垃圾回收时就必须能够识别哪些对象需要放在老年代,哪些对象需要放在新生代。除了大对象直接进入老年代外,虚拟机还给每个对象定义了一个对象年龄 (Age) 计数器。

它将作为新生区对象进入老年代的一个标志,如果对象在 Eden 区被分配内存并经过一次 Minor GC 后成功存活下来,并且能够在被 Survivor 区锁容纳,那么它的对象年龄就会加。

对象每“熬过”一次 Minor GC 那么它的年龄就会增加 1,这个年龄有个限制,就是当它的年龄增加到 15 岁时就会被晋升到老年代中去。这一点你可以设置这个年龄阈值,规定它的年龄超过这个阈值时就进入老年代,使用 -XX:MaxTenuringThreshold 进行设置。

四、动态对象年龄判定

当然虚拟机并不是全部要求对象的年龄必须到达 MaxTenuringThreshold 才被分配到老年代,为了更好的适应不同程序的内存情况,虚拟机还规定如果在 Survivor 中相同年龄的所有对象的大小的总和大于 Survivor 空间的一半,年龄大于或这等于该对象年龄的对象就可以直接进入到老年代。

五、空间分配担保

在执行 Minor GC 之前,虚拟机会先检查老年代中最大可用的连续空间是否大于新生代所有对象的总空间,如果这个条件成立,那么 Minor GC 就被视为是安全的。

如果条件不成立,则虚拟机会先查看 HandlePromotionFailure 设置值是否允许担保失败 (就像买了保险一样,如果你承担我执行后的风险我就执行,如果你不承担我就不执行)。

在允许的情况下,那么虚拟机会继续检查老年代中最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,那么就会尝试着进行一次 Minor GC ,尽管这次执行是有风险的,如果小于或者 HandlePromotionFailure 为不允许那么就不能执行 Minor GC 转为执行 Full GC。

这个风险指的是如果新生代中对象在执行一次 Minor GC 之后,另一块 Survivor 区无法为所有活下来的对象的拷贝分配内存,那么就会在老年代中进行内存分配,在极端的情况下,如果在老年代中也没有足够的连续的内存空间为这些对象分配内存,老年代的内存就会溢出。

六、什么时候执行 Minor GC 与 Full GC

Minor GC

当新生代中的 Eden 区分配满的时候触发。读了这篇博文你就会知道,随着 Minor GC 的执行,老年代的空间通常会随着 Minor GC 的执行内存占用空间增大。

Full GC

在将要执行 Minor GC 时如果老年代中最大可用的连续空间小新生代所有对象的总空间,这时会继续检查老年代中最大可用的连续空间是否大于历次晋升到老年代对象的平均大小。

如果大于,并且 HandlePromotionFailure 设置值为不允许,这时就执行 Full GC,或者小于,这时不管 HandlePromotionFailure 设置的值是允许还是不允许那么都会执行一次 Full GC 。

如果方法区中内存填满时也会执行 Full GC ;另外 System.gc() 默认也是触发 full GC。

参考

《深入理解 Java 虚拟机》周志明 著