堆内存:划分、识别垃圾对象、分配策略和回收时机

【参考链接】

Java 垃圾收集机制http://wiki.jikexueyuan.com/project/java-vm/garbage-collection-mechanism.html

 

 

划分

堆内存划分为新生代和老年代,新生代又分为eden和survivor(伊甸园和幸存者),survivor又分为大小相同的两块from和to。

 

 

识别垃圾对象

如何识别是垃圾对象?判断可触及性

即从根节点开始是否可以访问到这个对象,如果不可以说明已经不在使用了,需要被回收。这些根节点叫做GC Roots,在Java中的GC Roots包括如下几种:

1、  方法区中的静态成员变量引用的对象

2、  栈中的栈帧中的局部变量表中引用的对象

3、  本地方法栈中的

 

分配策略和回收时机

结论

1、  优先在eden上分配对象,不超出eden大小的对象,直接在eden上分配

2、  优先在eden上分配对象,但是eden的剩余空间不够时,触发【MinorGC清理空间后再分配。MinorGC】会

1)      标记eden+from中的存活对象,将其放入到to中,清除eden+from

2)                    如果from/to放不下存活对象,则将其放入到老年代中

3)      from/to中存活的对象,每经历一次GC,它的年龄就会加1,当对象的年龄到达一定的大小,则将其移动到老年代中

3、  优先在eden上分配对象,当对象大小超出eden大小时,直接分配到老年代上。

4、  尝试去进行MinorGC,导致有存活对象进入老年代,当老年代的剩余空间也不够时,触发【FullGC清理空间后再分配。FullGC】会

标记新生代和老年代中的存活对象,清除垃圾,将存活对象整理到老年代中,清空新生代。(也会清理方法区)

 

MinorGC和FullGC

上面说了,GC分为两种,MinorGC和FullGC

1、  处理的区域不同,MinorGC只处理新生代,FullGC处理新生代、老年代、还有方法区

2、  MinorGC回收的频率很高,但是每次回收的耗时都很短,而FullGC回收的频率比较低,但是会消耗更多的时间

3、  MinorGC采用复制方式

 

 

新生代空间分为eden和survivor,survivor又分为from、to两部分。其中from和to空间可以视为用于复制的两块大小相同、可进行角色互换的空间。

在垃圾回收时,eden空间中的存活对象会被复制到未使用的survivor空间(假设是to),正在使用的survivor空间(假设是from)中的年轻对象也会被复制到to空间中。

此时eden和from中的剩余对象就是垃圾对象,可以直接清空,to空间存放次次回收后的存活对象。

下次回收时,则复制eden和to中的存活对象到from,清空eden和to,依次类推。

FullGC采用压缩方式

 

 

将所有存活的对象压缩到内存的一端

4、  MinorGC的日志是

FullGC的日志是

 

实验

前面我们讲过了,基本类型的数组是在堆上分配的,下面我们就使用字节数组,配置不同的区域大小,通过日志来观察内存的分配和回收,分别验证上面的结论

1.        优先在eden上分配对象,不超出eden大小的对象,直接在eden上分配

 Java Code 

1
2
3
4
5
6
7
8
9
10
11
12

//-Xms20m -Xmx20m -Xmn8m -XX:SurvivorRatio=2 -XX:-UseTLAB -XX:+UseSerialGC -XX:+PrintGCDetails
//
新生代8M //eden 4M //from/to 2M
//
老年代12M

public class Main {
    
    
public static void main(String[] args) {
        System.out.println(
"申请分配一个3M内存");
        
byte[] a=new byte[3*1024*1024];
    }
    
}

 

可以看到3M都分配在了eden中 (还有Class对象+String对象等占去(92%-75%)x4096K≈696K)

 

2.         

2.1.        优先在eden上分配对象,但是eden的剩余空间不够时,触发MinorGC,标记eden+from中的存活对象,将其放入到to中,清除eden+from,再分配

 Java Code 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

//-Xms20m -Xmx20m -Xmn8m -XX:SurvivorRatio=2 -XX:-UseTLAB -XX:+UseSerialGC -XX:+PrintGCDetails -XX:+PrintHeapAtGC
//
新生代8M //eden 4M //from/to 2M
//
老年代12M
public class Main {
    
    
public static void main(String[] args) {
        System.out.println(
"申请分配一个1M内存");//不回收,一直保存到内存中
        byte[] a=new byte[1*1024*1024];
        
        System.out.println(
"申请分配一个2M内存");
        
byte[] b=new byte[2*1024*1024];
        b=null;
        
        System.out.println(
"申请分配一个2M内存");
        
byte[] c=new byte[2*1024*1024];//再申请时eden已经放不下了//需要先进行MinorGC,把存活的1M放入到to中,清空eden+from//这2M放到eden中
    }
    
}

 

分配c之前,会先进行MinorGC,将存活的1M保存到from/to中,清空eden+from/to

然后把c分配到eden上

 

2.2.        优先在eden上分配对象,但是eden的剩余空间不够时,触发MinorGC,标记eden+from中的存活对象,如果from/to放不下存活对象,则将其放入到老年代中,清除eden+from

 Java Code 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

//-Xms12m -Xmx12m -Xmn6m -XX:SurvivorRatio=4 -XX:-UseTLAB -XX:+UseSerialGC -XX:+PrintGCDetails -XX:+PrintHeapAtGC
//
新生代6M //eden 4M //from/to 1M
//
老年代12M
public class Main {
    
    
public static void main(String[] args) {
        System.out.println(
"申请分配一个2M内存");//不回收,一直保存到内存中
        byte[] a=new byte[2*1024*1024];
        
        System.out.println(
"申请分配一个1M内存");
        
byte[] b=new byte[1*1024*1024];
        b=null;
        
        System.out.println(
"申请分配一个2M内存");
        
byte[] c=new byte[2*1024*1024];//再申请时eden已经放不下了//需要先进行MinorGC,把存活的2M放入到老年代中,清空eden+from//这2M放到eden中
    }
    
}

 

分配c之前,会先进行MinorGC,将存活的2M保存到老年代中,清空eden+from/to

然后把c分配到eden上

 

 

2.3.        from/to中存活的对象,每经历一次GC,它的年龄就会加1,当对象的年龄到达一定的大小,则将其移动到老年代中

默认情况下,这个数值为15,可以使用-XX:MaxTenuringThreshold来设置这个年龄

不过它指的是最大晋升年龄,是晋升老年代的充分必要条件,即到达该年龄对象必然晋升,而未到达该年龄对象也可能晋升。

实际上对象的实际晋升年龄是由虚拟机在运行时动态判断的。

 Java Code 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

//-Xms20m -Xmx20m -Xmn12m -XX:SurvivorRatio=1 -XX:-UseTLAB -XX:+UseSerialGC -XX:+PrintGCDetails -XX:+PrintHeapAtGC -XX:MaxTenuringThreshold=3
//
新生代12M //eden 4M //from/to 4M
//
老年代8M
public class Main {
    
    
public static void main(String[] args) {
        System.out.println(
"申请分配一个2M内存");//不回收,一直保存到内存中
        byte[] a=new byte[2*1024*1024];
        
        System.out.println(
"申请分配一个3M内存");
        
byte[] b=new byte[3*1024*1024];//在分配前触发一次MinorGC
        b=null;
        
        System.out.println(
"申请分配一个3M内存");
        
byte[] c=new byte[3*1024*1024];//在分配前触发一次MinorGC
        c=null;
        
        System.out.println(
"申请分配一个3M内存");
        
byte[] d=new byte[3*1024*1024];//在分配前触发一次MinorGC//会把2M从from/to放入到老年代中//把3M放入eden中
    }
    
}

 

可以看到虽然设置了MaxTenuringThreshold=3,但是在第2次MinorGC的时候就会把a从新生代放入到老年代

 

3.        优先在eden上分配对象,当对象大小超出eden大小时,直接分配到老年代上

 Java Code 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

//-Xms20m -Xmx20m -Xmn8m -XX:SurvivorRatio=2 -XX:-UseTLAB -XX:+UseSerialGC -XX:+PrintGCDetails -XX:+PrintHeapAtGC
//
新生代8M //eden 4M //from/to 2M
//
老年代12M
public class Main {
    
    
public static void main(String[] args) {
        System.out.println(
"申请分配一个1M内存");
        
byte[] a=new byte[1*1024*1024];
        
        System.out.println(
"申请分配一个3M内存");
        
byte[] b=new byte[3*1024*1024];//在分配前触发一次MinorGC
        
        System.out.println(
"申请分配一个6M内存");
        a=null;
        b=null;
        
byte[] c=new byte[6*1024*1024];//在分配前触发一次MinorGC//把6M直接分配到老年代上
    }
    
}

 

要分配b时会先触发一次MinorGC,后续会把b分配到eden上

然后分配c时直接分配在了老年代上

 

4.        尝试去进行MinorGC,导致有存活对象进入老年代,当老年代的剩余空间也不够时,触发FullGC,标记新生代和老年代中的存活对象,清除垃圾,将存活对象整理到老年代中,清空新生代,再分配。

 Java Code 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

//-Xms16m -Xmx16m -Xmn8m -XX:SurvivorRatio=2 -XX:-UseTLAB -XX:+UseSerialGC -XX:+PrintGCDetails -XX:+PrintHeapAtGC
//
新生代8M //eden 4M //from/to 2M
//
老年代8M
public class Main {
    
    
public static void main(String[] args) {
        System.out.println(
"申请分配一个1M内存");
        
byte[] aa=new byte[1*1024*1024];//分配前触发一次MinorGC
        
        System.out.println(
"申请分配一个3M内存");
        
byte[] a=new byte[3*1024*1024];//分配前触发一次MinorGC
        
        System.out.println(
"申请分配一个3M内存");
        
byte[] b=new byte[3*1024*1024];//在分配前触发一次MinorGC//a3M超出from/to放入老年代中//然后b3M分配在eden上
        
        a=null;
//解除a的引用
        System.out.println("申请分配一个3M内存");
        
byte[] c=new byte[3*1024*1024];//在分配前触发一次MinorGC//把b3M也放入到老年代中//!!!a3M虽然已经没有引用,但是MinorGC并不会回收老年代//然后c3M分配到eden上
        
        
//此时eden中c3M,from/to中1M,老年代中6M(a3M可回收, b3M)
        System.out.println("申请分配一个3M内存");//在分配前触发一次MinorGC?//会导致c3M进入老年代,老年代不够,触发FullGC
        byte[] d=new byte[3*1024*1024];
        
    }
    
}

 

分配a内存前触发MinorGC,将1M移动到from/to中,a3M后续分配到eden上

 

分配b内存前触发MinorGC,将a3M移动到老年代中,(from/to中的1M也被移动到了老年代中),b3M后续分配到eden上

 

分配c内存前触发MinorGC,将b3M移动到老年代中,a3M虽然已经解除引用,但是MinorGC不会回收(老年代回收后继续增加)

 

分配d内存,、尝试MinorGC,将c3M移动到老年代中,老年代剩余空间不足,触发FullGC,标记新生代和老年代中的存活对象,aa1M+b3M+c3M,将存活对象全部整理到老年代中,清空新生代

d然后分配到eden上

 

 

 


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值