上篇文章我们讲解到了什么是垃圾回收机制,如何判断哪些对象要被回收以及垃圾收集算法,而且我们也留了疑问:“为啥‘复制’算法那么浪费空间,而新生代还是选择了它”,今天这篇文章,我们来讲解。
对象的内存分配,往大方向讲,就是在堆上分配,对象主要分配在新生代的Eden区上,如果启动了本地线程分配缓冲,将按线程优先在TLAB上分配。少数情况下也可能直接分配在老年代中,分配的规则不是百分百固定的,其细节取决于当前使用的是哪一种垃圾回收器组合,还有虚拟机中与内存相关的参数的设置。
当使用参数 -XX:SurvivorRatio=8 决定了新生代中Eden区与一个Survivor区的空间比例是8:1,这里要注意,是 一个 Survivor区的比例,而不是总的Survivor区的比例,而且新生代总用空间是Eden区+1个Survivor区的总容量
大对象指大量连续内存空间的Java对象,比如那种很长的字符串以及数组
在JVM中提供了—XX:PretenureSizeThreshold,令大于这个设置值的对象直接在老年代分配,这样做的目的是避免Eden区及两个Survivor区之前发生大量的内存复制。
虚拟机会给每一个对象定义了一个对象年龄(Age)计数器。如果对象在Eden出生并经过第一次Minor GC后仍然存活,并且能被Survivor容纳的话,将其移动到Survivor空间中,并且对象年龄设为1。对象在Survivor区每“熬过”一次Minor GC,年龄就会增加1岁。当它达到一定的阈值(默认为15岁),就将会晋升到老年代中。对象晋升老年代的年龄阈值,可以通过参数-XX:MaxTenuringThreshold设置
然而在JVM中为了更好的适应不同的内存状况,虚拟机并不是永远地要求对象的年龄必须达到了MaxTeuringThreshold才能晋升到老年代,如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无序等到MaxTeuringThreshold所要求的年龄。
下面来图解JVM内存管理大概的流程:
注意:哪个是From Survivor区和To Survivor区是根据当时处理情况而决定的,两个Survivor区轮流充当最后存活对象的存储空间,所以在一个Minor GC中,谁是最终存活对象的存储空间,谁就是To Survivor区
从上图我们就可以看出,为啥新生代是可以使用复制算法的,首先Java对象大多数是具备朝生朝灭的,大多数的对象都会被回收,要复制的对象较少,而且我们复制算法每次浪费利的空间是一个Survivor区,占的比例是比较小。
由于我们只有一个Survivor区来做轮换,如果容纳不下了,则会将无法容纳的对象直接进入老年代,因此就有了以下的判断流程:
每次发生Minor GC之前,虚拟机会先检查老年代最大可用空间是否大于新生代所有对象总空间,具体流程如下: - 老年代最大可用空间是否大于新生代所有对象总空间 - 大于:Minor GC是确保安全的 - 小于:JVM则会查看HandlePromotionFailure设置值是否允许担保失败 - 不允许:则进行一次Full GC //step A - 允 许:那么会继续检查老年代最大可用空间是否大于历次晋升到老年代对象的平均大小 //step B - 大于:则进行一次Minor GC(这是一次冒险)//step 1 - 小于:则进行一次Full GC
JDK update24之后,HandlePromotionFailure参数已经不会影响到虚拟机的空间分配担保策略了,虽然还是定义了,但是并没有使用,而是变成了 只要老年代的连续空间大于新生代对象总大小或者历次晋升的平均大小,就会进行Minor GC,否则就将进行Full GC ,可以理解为上面的流程中step A被堵死了,必须走step B过。
如果有什么错误、建议或者讨论的,可以留言,互相学习进步,谢谢