前言:
如果对堆中的分代概念不是很明白,请务必看完博客:堆以及堆分代的详细介绍,接下来对堆中对象分配的过程进行详细的说明。
1、背景
为新对象分配内存是一件非常严谨和复杂的任务,JVM的设计者们不仅需要考虑内存如何分配、在哪里分配等问题,并且由于内存分配算法与内存回收算法密切相关,所以还需要考虑GC执行完内存回收后是否会在内存空间中产生内存碎片。
2、概述
- (1)new的对象先放Eden(伊甸园)区。此区有大小限制。
- (2)当Eden的空间填满时,程序又需要创建对象,JVM的垃圾回收器将对Eden区进行垃圾回收(Minor GC), 将Eden区中的不再被其他对象所引用的对象进行销毁。再加载新的对象放到Eden区
- (3)然后将Eden中的剩余对象移动到幸存者0区。
- (4)如果再次触发垃圾回收,此时上次幸存下来的放到幸存者0区的,如果没有回收,就会放到幸存者1区。
- (5)如果再次经历垃圾回收,此时会重新放回幸存者0区,接着再去幸存者1区
- (6)啥时候能去养老区呢?可以设置次数。默认是15次。
可以设置参数:-XX:MaxTenuringThreshold=<N>
进行设置
3、图解
3.1 标准分配过程(红色代表垃圾)
(1)首先Eden区放满了,此时用户线程停止(STW :stop the world ),触发GC(YGC/Minor GC),红色的是垃圾,被释放掉,绿色是还用着的对象,就往S0或S1放。第一次到so区或者s1区的对象通过年龄计数器,给它们的年龄标为1,此时Eden区里面就没有数据了,被清空了,而s0区有了两个刚进入的对象。
(2)此时又向Eden区放数据,又满了,其中s0中的两个对象,被分配到了s1,它们的年龄也同时加1,变为2,并且Eden区中的对象也被放到了s1,GC执行完之后,Eden和S0又都是空的。
注意:关于幸存者0区,和幸存者1区,其实他们也可以称为from区和to区,但是它们的名字是不是绝对的,可以记为 复制之后有交换,谁空谁是to
(3)此时又向Eden中放数据,又满了,再次触发GC,如下图:S1区中有两个对象的年龄已经达到了15,这个15是一个阈值,就要被放到老年代了,同时没有达到15这个阈值的对象被放到S0,GC执行完后Eden区和S1区又都是空的
注意:这个阈值我们可以通过设置参数-XX:MaxTenuringThreshold=<N>
进行设置,默认是15
3.2流程图解释涉及到的所有分配情况
如上图,可能遇到超大对象直接分到老年代,如果老年代剩下的空间不够则就行majorGC,如果majorGC后还是放不下,就会报错OOM
4、代码举例
首先设置VM option参数为-Xms600m -Xmx600m
/**
*-Xms600m -Xmx600m
*/
public class HeapInstance {
byte[] buffer=new byte[new Random().nextInt(1024*200)]; //为了让对象变大
public static void main(String[] args) {
ArrayList<Object> list = new ArrayList<>();
while(true){
list.add(new HeapInstance());
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
运行代码,我们看java VisualVM中的Visual GC中的内存变化就都明白了
疑问:如果是幸存者区满了会触发GC吗?
不会,幸存者区是由Eden区满了触发GC,自己不能主动触发GC