GC垃圾回收机制

  1. GC的作用:进行内存管理
    C语言中的内存,申请内存之后需要手动释放;一旦忘记释放,就会发生内存泄漏!
    而Java语言中,申请内存后会由GC来释放内存空间,无需手动释放;
    GC虽然代替了手动释放的操作,但是它也有局限性:
  • 需要消耗更多的资源;
  • 没有手动释放那么及时;
  • STW(Stop The World)会影响程序的执行效率
  1. GC主要回收哪些内存
    (1)堆:主要回收堆中的内存
    (2)方法区:需要回收
    (3)栈(包括本地方法栈和JVM虚拟机栈):不需要回收,栈上的内存什么时候释放是明确的(线程结束,栈上的内存也就被释放了;对应的某个栈帧销毁[某个方法执行完毕],也会导致对应的局部变量被释放)
    (4)程序计数器:不需要被回收
  2. GC回收内存的基本单位:对象
  3. GC回收对象的基本思路:
    (1)标记:判断当前对象的生死,对象不再被使用为死,则需要回收,反之不需要被回收;
    标记的方法:
  • 引用计数法:
    记录当前这个对象是否有引用指向,有则引用计数加1,如果当前这个对象的引用指向了其他新的对象,则引用计数减1,当引用计数为0的时候,我们认为这个对象需要被回收!
缺点:无法解决循环引用问题
下面用一段伪代码来演示一下循环引用问题:
class Test{
Test t = null;
}
Test a = new Test();
Test b = new Test();
a.t = b;
b.t = a;
a = null;
b = null;

我们发现,在上述代码中已经没有办法使用对象a和对象b了,但是它们的引用计数不为1.想使用对象a,就得找到对象a的引用,但是对象a的引用又在对象b当中。想使用对象b,就得找到对象b 的引用,但是对象b的引用又在对象a当中。
在这里插入图片描述

  • 可达性分析:
    代码中的对象具有一定的关联关系,这样错综复杂的关系,构成了一个"有向图"。可达性分析也就是遍历这个对象关系的“有向图”。如果某个对象可以被遍历到,那么它就是可达的(非垃圾),那么就是不可达的(是垃圾)
    那么可达性分析从哪里开始呢?
    a)针对每个线程的每个栈帧的局部变量表(线程有很多,每个线程栈帧也有很多,每个栈帧也会有很多个变量);
    b)常量池中引用的对象;
    c)方法区中静态变量引用的对象;
    因为遍历的起点不止一个,而是很多个起点,因此把这些起点也称之为GCRoot

  • 回收方法区对象的规则:
    a)该类的所有实例已经被回收;
    b)加载类的ClassLoader也已经被回收了;
    c)该类对象没有在代码中使用了
    同时具备以上三个条件,就认为该类对象是可以被回收的
    (2)回收
    回收的方法:

  • 标记-清除【适合老年代】
    在这里插入图片描述通过上面的图,我们可以发现,两个空闲区被其他的对象分隔开了。一旦需要一个比较大的空间,就会申请失败。
    标记-清除法的优缺点:
    优点:简单高效
    缺点:会出现内存碎片

  • 标记-复制【适合新生代】
    在这里插入图片描述
    优点:解决了内存碎片问题,保证回收之后不会存在碎片(回收后使用的对象之间是连续的,空余内存之间也是连续的)
    缺点:需要一块额外的空间,如果生存的对象较多就比较难低效

  • 标记-整理【适合老年代】
    在这里插入图片描述
    优点:没有内存碎片问题,也不需要额外的空间
    缺点:类似于顺序表的删除操作,效率不是很高

  1. 分代回收
    按照对象的年龄,将堆内存分为:新生代(伊甸区和生存区)、老年代
    对象的年龄不是直接使用时间来记录,而是使用对象活过GC轮次来记录(GC是按照一定周期来运行)
    在这里插入图片描述
    一个对象的一生:
    (1)对象诞生于新生代的伊甸区。新产生的对象的内存就是新生代中的内存
    (2)第一轮GC扫描伊甸区之后,就会把大量的对象回收掉。少数没有被回收的对象,就会通过标记-复制算法进入到生存区
    (3)少数进入生存区的对象,再次被GC扫描(对这些对象进行可达性分析)。如果发现该对象已经不可达,也就被销毁了。没有被销毁的对象,再次通过标记-复制算法,把它拷贝到另一个生存区。
    (4)对象在两个生存区中经过若干次拷贝,如果还没有被回收,那么就说明这些个对象存活时间比较久,就拷贝到老年代
    (5)老年代的对象也是要经过GC扫描的。由于老年代的对象生存时间比较长。因此扫描周期要比新生代的周期要长
    相关术语:
  • Partical GC:只进行一部分内存区域的GC
  • Full GC:针对整个内存区域进行GC
  • Minor GC:针对新生代内存的GC,执行频繁,速度较快
  • Major GC:针对老年代的GC,没那么频繁。速度较慢,通常由Minor GC 触发
  1. 垃圾回收器
    在这里插入图片描述
    垃圾回收器做的两件事情:标记(可达性分析)+回收(标记清除,标记复制,标记整理)
  • Serial收集器(给新生代使用,串行回收)【存在STW】
    在这里插入图片描述
    采用复制算法,单线程进行标记和回收

  • ParNew收集器(新生代收集器,多线程GC)
    在这里插入图片描述
    采用复制算法,多线程进行标记和回收

  • Parallel scavenge收集器(新生代收集器,并行GC)
    设计初衷是为了缩短STW时间,以牺牲吞吐量和新生代空间作为代价。
    相当于承诺用户,在一定时间内就会完成一次GC。

  • Serial Old收集器(老年代收集器,串行GC)
    在这里插入图片描述

  • Parallel old收集器(老年代收集器,并行GC)
    在这里插入图片描述
    使用多线程完成标记整理,效率更高,消耗的CPU资源更多

  • CMS垃圾回收器(老年代收集器,并行GC,采用多线程标记清除算法)
    a)初始标记【STW】
    只是把和GCRoot相关的对象标记出来,涉及STW
    b)并发标记
    执行整个标记遍历的过程(从GCRoot开始,把能访问的对象都遍历)
    不需要暂停用户线程

消耗的时间相对比较久,但是可以和用户线程并发

注意:当进行并发标记的时候,当用户线程也在执行,可能导致某个对象,刚刚标记的时候不是垃圾,代码执行后,就成了垃圾

c)重新标记(CMS remark)【STW】
修正误差
d)并发清除
多线程的方式将刚刚的垃圾对象都清除释放掉,可以和应用程序并发执行
优点:能够让STW时间尽量短
缺点:有内存碎片; GC操作和应用程序并发进行,消耗CPU资源多;

  • G1回收器(Java11开始默认使用)
    既可以回收新生代,也可以回收老年代
    在这里插入图片描述
    每个矩形称为一个region
    E表示伊甸区
    S表示生存区
    T表示老年代
    H表示存放大对象的区域
    以region为单位进行回收,回收粒度更精细
    针对新生区的region同样适用复制算法
    针对老年代的回收类似于CMS
    a)初始标记【STW】:只去找和GRoot直接相连的对象
    b)并发标记:和应用程序并发执行,进行可达性分析,遍历所有对象。如果发现某个老年代region中已经没有存活对象,就直接回收
    c)最终标记:修正第二步产生的误差
    d)筛选回收:挑选出对象存活率低的region进行回收
  1. 总结:
    在这里插入图片描述
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值