03_垃圾回收

【简述】

垃圾回收GC(Garbage Collection),GC中的垃圾,特指存于内存中不会再使用的对象,回收相当于清除垃圾。

垃圾回收有很多种算法,如:引用计数法、标记压缩法、复制算法、分代分区思想。

[ 引用计数法 ]

是比较古老经典的垃圾收集算法,其核心就是对象在被其引用时计数器+1,而当引用失效时-1,这种方式有一个非常严重的问题:无法处理循环引用的情况,且每次进行操作比较浪费系统性能。

[ 标记清除法 ]

分为标记和清除两个阶段来处理内存中的对象。这种方式也有一个弊端:空间碎片问题,垃圾回收后的空间不是连续的,不连续的内存空间的工作效率要低于连续的内存空间。

[ 复制算法 ]

其核心思想就是将内存空间分为两块,每次只使用其中一块,在垃圾回收时,将正在使用的内存中的存留对象复制到未被使用的内存块中,之后去清除之前正在使用的内存块中的所有对象,反复去交换这两个内存的角色,完成垃圾收集。

Java中新生代的from和to区就是使用这个算法。

[ 标记压缩法 ]

标记压缩法对标记清除法基础上做了优化,把存活的对象压缩到内存一段,然后进行垃圾清除。

Java中老年代使用的就是标记压缩法。

【 分代算法和分区算法 】 

[ 分代算法 ]

就是根据对象的特点把内存分成N块,然后根据每个内存的特点使用不同的算法。

对于新生代和老年代来说,新生代回收的频率很高,但是每次回收耗时都很短。

而老年代回收的频率很低,相对耗时会比较长,所以应该尽量减少老年代的GC。

[ 分区算法 ]

主要就是将整个内存分为N多个小的独立空间,每个小空间都可以独立使用,这样细粒度地控制一次回收多少个小空间和哪些小空间,而不是对整个空间进行GC,从而提升性能,并减少GC的停顿时间。

【垃圾回收时的停顿现象】

垃圾回收器的任务是识别和回收垃圾对象进行垃圾清理,为了让垃圾回收器高效地执行,大部分情况下,会要求系统进入一个停顿的状态,停顿的目的是终止所有的应用程序,只有这样系统才不会有新的垃圾产生,同时停顿保证了系统状态在某一瞬间的一致性,也有利于更好地标记垃圾对象。

因此在垃圾回收的时,都会产生应用程序的停顿。

【对象如何进入老年代】

 一般对象首次创建会被放在新生代的eden区域,如果没有GC介入,则对象不会离开eden区。

那么对象怎么进入老年代呢?

一般来讲,只要对象的年龄达到一定的大小后,就会自动离开新生代进入老年代,对象的年龄是由对象经历数次GC决定的,在新生代每次GC之后如果对象咩有被回收则年龄+1,虚拟机提供了一个参数来控制新生代对象的最大年龄,当超过这个年龄范围就会晋升到老年代。

-XX:MaxTenuringThreshold,默认情况下是15。

【初始的对象分配在eden区】

package com.jvm.demo01;

public class Demo05 {

    public static void main(String[] args) {
        //初始的对象在eden区
        //参数:-Xmx64M -Xms64M -XX:+PrintGCDetails
        for(int i=0; i< 5; i++){
            byte[] b = new byte[1024*1024];
        }
    }
}

【-Xmx64M -Xms64M -XX:+PrintGCDetails 运行结果】

【测试进入老年代的对象】

package com.jvm.demo01;

import java.util.HashMap;
import java.util.Map;

public class Demo05 {

    public static void main(String[] args) {

        //测试进入老年代的对象
        //参数:-Xmx1024M -Xms1024M -XX:+UseSerialGC -XX:MaxTenuringThreshold=15 -XX:+PrintGCDetails 
        //每次分配1M内存,执行6000次
        for(int k = 0; k<20; k++) {                
            for(int j = 0; j<300; j++){
                byte[] b = new byte[1024*1024]; 
            }
        }
    }
}

【运行结果】

[ 总结 ]

根据设置MaxTenuringThresgold参数,可以指定新生代对象经历多少次回收后进入老年代。

另外,大对象(新生代eden区无法装入时,也会直接进入老年代)。JVM里有个参数可以设置对象的大小超过指定的大小之后,直接进入老年代(针对大对象)。

-XX:PretenureSizeThreshold  

【大对象直接进入老年代的例子】

package com.jvm.demo01;

import java.util.HashMap;
import java.util.Map;

public class Demo06 {
        
    public static void main(String[] args) {
        //-XX:PretenureSizeTheshold 可以直接指定进入老年代的对象大小 -XX:PretenureSizeThreshold=1024*1000  < 1024*1024  
        //-Xmx30m -Xms30m -XX:+UseSerialGC -XX:+PrintGCDetail -XX:PretenureSizeThreshold=1024000
        Map<Integer, byte[]> m = new HashMap<Integer, byte[]>();
        
        for(int i=0;i<5;i++){
            byte[] b =new byte[1024*1024]; //每次分配的对象大小都大于设定的1024*1000
            m.put(i, b);
        }
    }
    
}

【大对象直接进入老年代的运行结果】

【-XX:PretenureSizeThreshold  设置的值较小(1024B),生成对象(1024)稍大,但没有存入老年代的例子】

package com.jvm.demo01;

import java.util.HashMap;
import java.util.Map;

public class Demo06 {
        
    public static void main(String[] args) {
        //1.默认使用TLAB区
        //参数:-Xmx30M -Xms30M -XX:+UseSerialGC -XX:+PrintGCDetails -XX:PretenureSizeThreshold=1000
        //这种现象原因为:虚拟机对于体积不大的对象 会优先把数据分配到TLAB区域中,因此就失去了在老年代分配的机会
        //2.禁用TLAB区
        //参数:-Xmx30M -Xms30M -XX:+UseSerialGC -XX:+PrintGCDetails -XX:PretenureSizeThreshold=1000 -XX:-UseTLAB
        Map<Integer, byte[]> m = new HashMap<Integer, byte[]>();
        for(int i=0; i< 5*1024; i++){   //5*1024次
            byte[] b = new byte[1024];   //每次1024B
            m.put(i, b);
        }
    }
    
}

【使用TLAB区域运行结果】

【禁用TLAB区域运行结果 -XX:-UseTLAB】

[ 总结 ] 

使用PretenureSizeTheshold可以直接指定进入老年代的对象大小,但是要注意TLAB区域的优先分配空间。

【TLAB区域】

TLAB区域全称是Thread Local Allocation Buffer ,即线程本地分配缓存,从名字上看是一个线程专用的内存专用分配区域,是为了加速对象的分配而诞生的。每一个线程都会产生一个TLAB,该线程独享的工作区域,Java虚拟机使用这种TLAB区域来避免多线程冲突问题,提高了对象分配的效率。TLAB空间一般不会太大,当大对象无法在TLAB分配时,则会直接分配到堆上。

[ 参数 ]

-XX:+UseTLAB  设置TLAB

-XX:+TLABSize  设置TLAB大小

-XX:TLABRefillWasteFraction  设置维护进入TLAB空间的单个对象的大小,它是一个比例值,默认为64,即如果对象大于整个空间的1/64,则在堆创建对象。

-XX:+PrintTLAB  查看TLAB信息

-XX:ResizeTLAB  自调整TLABRefillWasteFraction阈值

 [ 一个对象的创建流程  ]

一个对象创建在什么位置,我们的Jvm会有一个比较细节的流程,根据数据的大小,参数的设置,决定如果创建分配,以及其位置。

原文地址:https://www.cnblogs.com/HigginCui/p/8445717.html