垃圾回收了解

一、如何判断对象可以回收

1引用计数法

python虚拟机早期使用的

只要一个对象被其它变量所引用,则该对象计数+1,引用两次计数+2,如果一个变量不在被引用,则计数-1,当对象的引用计数变为0,则没有被引用,可以作为垃圾回收

弊端: 对象互相循环使用
A对象引用B对象,则B引用计数+1.同时B对象引用A对象,则A引用计数+1,
没有谁在引用他们,他们各自的引用都是1,即使都不会被使用,永远不会为0,不会垃圾回收,会造成内存泄漏
在这里插入图片描述

2 可达性分析算法

扫描堆中的所有对象,看是否能沿着GC Root对象(一系列根对象,不能被当作垃圾回收的对象)为起点的引用链(被根对象直接或间接引用)找到该对象,找不到可以回收

举例:拿一串葡萄,在根上的葡萄果不能被回收,有跟引用它们
不在根上的可以被垃圾回收

通过工具分析 GC Root对象常见有以下几种
1.System Class 由启动器加载类加载类,核心类对象 Object,HashMap,String
2.Native Stack 调用操作系统方法,操作系统方法执行时引用的java对象
3.Busy Monitor 加锁的对象,所在引用的其它对象也是需要保留的
4.Thread 线程一次次方法调用组成,每次方法调用产生栈帧,活动线程内局部变量,方法参数,引用的对象可以根对象

二、java中引用类型

说明:1.实线代表强引用,虚线代表软、弱、虚、终结器引用,
2.软、弱引用可以配合引用队列使用;虚引用、终结器引用必须配合引用队列使用
引用队列理解:比如:当软引用的对象被回收后,软引用自身也是一个对象,创建时分配了引用对列,引用对象被回收时,它会进入引用队列,因为自身也要占用内存,如果需要释放,需要找到他们进行处理。
在这里插入图片描述

1 强引用

只有所有GC Roots对象都不通过【强引用】引用该对象,该对象才能被垃圾回收

2 软引用(SoftReference)

  • 仅有软引用引用该对象时,在垃圾回收后,内存仍不足时再次出发垃圾回收,回收软引用对象
  • 可以配合引用队列来释放软引用自身
    在这里插入图片描述
    图片不太重要的资源,在内存紧张时释放掉,在用到在读取,以上强引用不推荐
/**
 * 设置堆内存-Xmx20m
 */
public class SoftReferenceTest {
    private static final int _4M = 4 * 1024 * 1024;

    public static void main(String[] args) {
        //list--强-->SoftReference--弱->Byte[]
        List<SoftReference<byte[]>> list = new ArrayList<>();
       for (int i = 0; i < 5; i++) {
            SoftReference<byte[]> ref=new SoftReference(new byte[_4M]);
            list.add(ref);
            System.out.println(ref.get()+"===="+list.size());
        }
        System.out.println("循环结束"+list.size());

       //软引用对应的byte数组对象对应的内存进行了释放
       for(SoftReference<byte[]> ref: list){
           System.out.println(ref.get());//循环结束取前4个元素是null,只有最后一个保留
       }
    }
}

前面的软引用对象null没必要在保存到list中,这种希望软引用本身也清理,这时需要配合引用队列

public class SoftReferenceTest {
    private static final int _4M = 4 * 1024 * 1024;

    public static void main(String[] args) {
        // List和SoftReference是强引用,而SoftReference和byte数组则是软引用
        List<SoftReference<byte[]>> list = new ArrayList<>();
        // 引用队列,用于移除引用为空的软引用对象
        ReferenceQueue<byte[]> queue = new ReferenceQueue<>();
        for (int i = 0; i < 5; i++) {
            // 关联引用队列,当软引用所关联的byte[]被回收时,软引用自己会加入到queue中去
            SoftReference<byte[]> ref = new SoftReference<>(new byte[_4M], queue);
            System.out.println(ref.get());
            list.add(ref);
            System.out.println(list.size());
        }

        // 遍历,从引用队列中获取无用的软引用对象,并移除
        Reference<? extends byte[]> poll = queue.poll();
        while (poll != null) {
            // 引用队列不为空,则从集合中移除该元素
            list.remove(poll);
            // 移动到引用队列中的下一个元素
            poll = queue.poll();
        }

        System.out.println("==========================");
        for (SoftReference<byte[]> reference : list) {
            System.out.println(reference.get());
        }
    }
}

3 弱引用(WeakReference)

  • 仅有弱引用引用该对象时,在垃圾回收时,无论内存是否充足,都会回收弱引用对象
  • 可以配合引用队列来释放弱引用自身

4 虚引用(PhantomReference)

  • 必须配合引用队列使用,主要配合ByteBuffer使用,被引用对象回收时,会将虚引用入队,由Reference Handler线程调用虚引用相关方法释放内存
    在这里插入图片描述
    使用场景:创建ByteBuffer的实现类对象时,会创建一个叫Cleaner的虚引用对象,ByteBuffer会分配一块直接内存,并且把直接内存地址传递给虚引用对象,将来ByteBuffer自己被回收,分配的直接内存不受java管理,让虚引用对象进入引用队列,会由ReferenceHandler线程来定时到引用队列找有没有新入队的Cleaner,有调用Cleaner的clean方法记录直接内存地址调用unsafe对象的freeMemory方法,释放直接内存。

5 终结器引用(FinalReference)

当一个对象重写了Object父类的finalize() 终结方法,并且没有强引用引用该对象时,对象会由虚拟机创建对应的终结器引用,当该对象被垃圾回收时,会把终结器引用加入引用队列(注意这时该对象还没有被垃圾回收),再由优先级很低的线程finalizeHandler线程,在某些时机查看是否有终结器引用,根据它找到要作为垃圾回收的对象,调用finalize()方法,下一次GC就可以回收该对象占用的内存

三、垃圾回收算法

1 标记清除

定义:Mark Sweep
步骤:
1.标记哪些对象是垃圾(沿着GC Root对象的引用链去扫描堆中的所有对象,没有GC Root直接或间接引用)
2.把垃圾对象所占用的空间释放

特点:

  • 速度快
  • 会造成内存碎片
    说明:
    1.释放不是每个内存进行清零操作
    2.把每个对象占用内存的起始和结束地址记录下来,放在一个空闲地址列表中。下次分配新对象时,去空闲地址列表寻找有没有足够的空间放下我的新对象,如果有进行内存分配。
    3.清除后不会对内存空间进行整理,分配一个大的对象数组,第一个空白放不下,第二个空白放不下,总的空间够,但是由于空间不连续,导致新的对象没有有效的内存给它用,导致内存碎片
    在这里插入图片描述

2 标记整理

定义:Mark Compact
特点:

  • 速度慢
  • 没有内存碎片
    说明:整理避免了标记清除时内存碎片的问题,在清除垃圾过程中,把可用对象向前移动,内存更紧凑,连续空间更多,由于整理,对象的移动,局部变量,引用,需要改变引用地址,效率较低
    在这里插入图片描述

3 复制

定义:Copy
特点:

  • 不会有内存空间
  • 占用双倍内存

说明:把内存区域划成了大小相等的两块区域,左边是from区域,右边to区域空闲,一个对象没有
首先标记不被引用的对象为垃圾
在这里插入图片描述
把from区域存活的对象赋值到to区域,同时完成碎片的整理,from区域垃圾全部清空
在这里插入图片描述
然后交换from 和to的位置
在这里插入图片描述
注:以上三种算法,JVM根据不用情况采用,协同工作

四、垃圾回收机制

1 分代回收

特点:

  • 对象首先分配在伊甸园区域
  • 新生代空间不足时,触发Minor GC,伊甸园和from存活的对象使用复制算法复制到to中,存活的对象年龄加1,并交换from和to
  • Minor GC会引发stop the world,暂停其它用户的线程,等垃圾回收结束,用户线程才恢复进行
  • 当对象寿命超过阈值时会晋升至老年代,最大寿命是15(4bit)
  • 当老年代空间不足,会先尝试触发minor gc,如果之后空间仍不足,会触发 full gc,STW的时间更长
  • 大对象老年代空间足够,新生代空间不够,直接晋升到老年代,不触发垃圾回收

说明:
将堆内存划分新生代 、老年代
老年代:长时间使用的对象(垃圾回收很久一次)
新生代:用完就可以丢弃的对象(垃圾回收频繁)
根据对象生命周期不同特点进行不同的垃圾回收算法,更有效的对垃圾回收进行管理

当创建一个新的对象时,会放入伊甸园的空间
在这里插入图片描述

当伊甸园空间不够时会触发一次垃圾回收(Minor GC),沿着GC Root引用链去找对象进行标记,采用复制算法,把存活的对象复制到幸存区To中,让幸存对象寿命+1(开始为0,经历了一次垃圾回收存活下来,寿命+1)
在这里插入图片描述
复制完成后,交换幸存区from和to的位置
在这里插入图片描述
回收后伊甸园空间充足,可继续分配对象,经过一段时间,伊甸园空间不够,触发第二次Minor GC,伊甸园幸存对象放入**幸存区to,**寿命+1,幸存区继续存活的对象移到to,寿命变成2,from 和 to
交换位置
在这里插入图片描述
当幸存区对象寿命超过默认阈值15,经历15次垃圾回收,将它晋升到老年代
在这里插入图片描述
当老年代空间不足,新生代空间不足,会触发Full GC,从新生到老年代整个垃圾回收

五、垃圾回收器

1 串行

单线程,垃圾回收时其它线程暂停
堆内存较小,适合个人电脑

分为两个部分Serial(新生代,采用复制算法)+SerialOld(老年代,标记整理算法)
回收过程:堆内存不足,进行垃圾回收,让其它线程在安全点停下来(SWT操作),进入阻塞状态,Serial和SerialOld都是单线程垃圾回收线程结束,其它用户线程恢复运行
在这里插入图片描述

2 吞吐量优先

多线程,堆内存较大,多核CPU
让单位时间内STW时间最短0.2+0.2=0.4
多核cpu,用户线程在安全点停下,垃圾回收器会开启多个线程(与cpu核数相关相关)进行垃圾回收,结束,其它线程恢复运行

虚拟机参数:

// 1.吞吐量优先垃圾回收器开关:(默认开启)
-XX:+UseParallelGC~-XX:+UseParallelOldGC
// 2.采用自适应的大小调整策略:调整新生代(伊甸园 + 幸存区FROM、TO)内存的大小
-XX:+UseAdaptiveSizePolicy  
// 3.调整吞吐量的目标:吞吐量 = 垃圾回收时间/程序运行总时间
-XX:GCTimeRatio=ratio
// 4.垃圾收集最大停顿毫秒数:默认值是200ms
-XX:MaxGCPaiseMillis=ms
// 5.控制ParallelGC运行时的线程数
-XX:ParallelGCThreads=n

在这里插入图片描述

3 响应时间优先

多线程,堆内存较大,多核CPU
尽可能让单次STW时间最短 0.1+0.1+0.1+0.1+0.1=0.5

虚拟机参数:

// 开关:
-XX:+UseConMarkSweepGC~-XX:+UseParNewGC~SerialOld  
// ParallelGCThreads=n并发线程数 
// ConcGCThreads=threads并行线程数
-XX:ParallelGCThreads=n~-XX:ConcGCThreads=threads
// 执行CMS垃圾回收的内存占比:预留一些空间保存浮动垃圾
-XX:CMSInitiatingOccupancyFraction=percent
// 重新标记之前,对新生代进行垃圾回收
-XX:+CMSScavengeBeforeRemark

用户线程和垃圾回收线程并发进行,都会抢占cpu
在这里插入图片描述

4 G1

Garbage First,JDK 9以后默认使用,而且替代了CMS 收集器:

适用场景:
同时注重吞吐量和低延迟(响应时间)。
超大堆内存(内存大的),会将堆内存划分为多个大小相等的区域。
整体上是标记-整理算法,两个区域之间是复制算法。

相关参数:JDK8 并不是默认开启的,需要参数开启:

// G1开关
-XX:+UseG1GC
// 所划分的每个堆内存大小:
-XX:G1HeapRegionSize=size
// 垃圾回收最大停顿时间
-XX:MaxGCPauseMillis=time

4.1 G1垃圾回收阶段

循环过程,新生代垃圾收集,老年代内存超过阈值,在新生代垃圾收集同时进行并发标记,然后进行混合(新生代,老年代)收集
在这里插入图片描述

4.2 Young Collection

把整个堆内存分成一个个大小相等区域,每个区域都可独立作为伊甸园,幸存区,老年代
在这里插入图片描述
一段时间后,伊甸园对象多啦,minor gc
在这里插入图片描述
在工作一段时间
在这里插入图片描述

4.2 Young Collection+CM

CM:并发标记!
在 Young GC 时会对 GC Root 进行初始标记。
在老年代占用堆内存的比例达到阈值时,进行并发标记(不会STW),阈值可以根据用户来进行设定:
-XX:InitiatingHeapOccupancyPercent=percent // 默认值45%
在这里插入图片描述

4.3 Mixed Collection

会对E、S 、O 进行全面的回收。
最终标记
拷贝存活
// 用于指定GC最长的停顿时间
-XX:MaxGCPauseMillis=ms
在这里插入图片描述

疑问:为什么有的老年代被拷贝了,有的没拷贝?
解答:G1会根据最大暂停时间进行有选择进行垃圾回收,选择回收价值最高的老年代区域,释放空间最多,赋值区域少啦,可以达到暂停时间目标

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值