Java 内存管理自动化的解决了两个问题:
1) 给对象分配内存;
2) 回收内存
新建对象主要分配在新生代的 Eden 区域,如果启动了本地线程分配缓冲,将按线程优先在 TLAB 上分配。少数情况也可能会直接分配在老年代中。分配的规则取决于当前使用的垃圾收集器组合,及其他与内存相关的设置参数。
以下是几条普遍的内存分配规则:
1.1 对象优先在 Eden 区分配
大多数情况下,对象在新生代 Eden 区分配,当该区域没有足够的空间时,虚拟机将会发起 Minor GC 进行垃圾收集。
1.2 大对象直接分配至老年代
这里的大对象是指需要大量连续内存空间的 Java 对象,如很长的字符串、数组等。
通过设置 -XX:PretenureSizeThread 参数,可将大于该值的对象直接在老年代分配内存。这样可以避免在 Eden 区及两个 Survivor 区之间发生大量的内存复制(基于复制算法的内存收集)。
参数设置格式如下:
-XX:PretenureSizeThread=3145728 // 3M,该参数不能直接设置为3M
1.3 长期存活的对象将进入老年代
Java 虚拟机给每个对象定义了一个对象年龄(Age)计数器,若对象在 Eden 区经过一次 Minor GC后仍然存活,并且被移到 Survivor 区,则该对象年龄置为1,此后,该对象在 Survivor 区每经过一次 Minor GC,年龄就加1,当增加到一定值时(年龄阈值,默认15 age,可通过 -XX:MaxTenuringThreshold 设置),则会被晋升到老年代中。
1.4 动态对象年龄判断
当然,虚拟机并不是永远要求对象的年龄必须达到阈值才能进入老年代区,若在 Survivor 空间中相同年龄所有对象所占空间大于 Survivor 空间的一半,年龄大于或等于该年龄的对象就可以直接先进入老年代区,无须等到 MaxTenuringThreshold 设置的阈值
1.5 空间分配担保
在发生 Minor GC 之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,大于的话,则说明 Minor GC 确保是安全的。
否则虚拟机会查看 HandlePromotionFailure 设置值是否允许担保失败。
若允许,虚拟机会检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,若大于,则尝试进行一次 Minor GC;若小于,或者 HandlePromotionFailure 设置不允许冒险,那这时也要改为进行一次 Full GC。
参考资料:
深入理解Java虚拟机:JVM高级特性与最佳实践