java垃圾回收机制整理

简述一下Java的内存回收机制?

在Java中,程序猿不用显示的去释放一个对象的内存,而是由虚拟机自动执行的;在JVM中,有一个垃圾回收线程,他是低优先级的,在正常情况下是不会执行的,只有在虚拟机空闲或者当前堆内存不足的情况下,才会触发执行,扫描那些没有被任何引用的对象,并对他们进行回收;

垃圾回收的原理?怎么主动通知虚拟机进行垃圾回收?什么时候会触发垃圾回收?

对于GC来说,当程序猿创建对象时,GC就开始监控这个对象的地址,大小以及使用情况了

垃圾回收的时机

  1. 显式的手动调用System.gc():调用此方法建议JVM进行FGC,虽然建议并非一定有效,但是一般都会触发FGC,但是一般不使用这种方法,而是让虚拟机自己去管理它的内存;
  2. 由JVM垃圾回收机制决定
    • 创建对象的额时候需要分配对象,如果空间不足,就会触发GC;
    • 其他回收机制
      java.lang.Object中有一个finalize()方法,当JVM确定不再有指向该对象的引用的时候,垃圾收集器会调用该方法。finalize()方法有点类似于对象周期的临终方法,JVM调用这个方法的时候,表示该对象即将死亡,之后就可以回收这个对象了 。

怎么判断一个对象已死可回收了?垃圾回收策略?

在回收一个对象之前,需要先进行是否可回收判断,一般有两种算法:

1. 引用计数算法:

  • 在对象中添加一个引用计数器,每当有一个地方调动它的时候,计数器加1,当引用失效的时候,计数器减1,当计数器等于0的时候,这个对象就是不可能再被使用的,可以回收的。
  • 引用计数器 算法实现简单,效率也很高,但是有一个缺点:不能解决对象之间互相循环引用的问题。java中不适用这种方式,常在脚本语言中应用。
    2. 可达性分析算法:
  • 通过一系列的" GC Roots"对象作为起点,从这些节点开始往下搜索,搜索走过的路径称为"GC Roots 引用链",当一个对象到GC Roots没有任何引用链连接时,也就是这个对象不可达,证明这个对象就是不可用的。java,c#等语言都是这种方式进行垃圾回收。
    在这里插入图片描述

finalize()方法:

object中有finalize()方法,当JVM不确定不再有指向该对象的引用存在时,垃圾回收器就调用该方法来进行判断,用该方法进行一个可回收标记。JVM调用该方法,表示对象即将死亡,之后就可以进行对象回收了。注意:回收还是再JVM中处理的,所以手动调用finalize方法并不会造成该对象的死亡。

Java中的引用类型有哪些?

  • 强引用:大部分的引用都是强引用,如Object obj = new Object(); 这类引用,只要强引用还在,Java 虚拟机宁愿抛出 OutOfMemoryError 错误,垃圾收集器永远不会回收被引用的对象;
  • 软引用:用来描述一些还有用但是并不是很需要的对象,对于软引用关联着的对象,在系统将要发生内存溢出异常之前(OOM)会被回收。
    - 弱引用:描述非必需对象,被弱引用关联着的对象只能生存到下一次GC之前,在下一次GC的时候, 无论内存是否足够,都会回收掉被弱引用关联着的对象;
  • 虚引用:也叫幽灵引用。虚引用不会对一个对象的生存空间造成任何影响,也无法通过一个虚引用来获取到一个对象的实例,唯一目的就是能在这个对象被GC时收到一个系统通知。
    在这里插入图片描述

需要垃圾回收的内存划分

在这里插入图片描述

元空间/方法区

  • jdk1.7的方法区在GC中一般称为 永久代;
  • jdk1.8的元空间存在于本地内存中,GC也就是对元空间进行垃圾回收表;
  • 方法区或者元空间的垃圾回收主要是回收两个部分:废弃常量和无用类;(此区域进行垃圾收集的性价比较低)

GC堆划分

Java堆是垃圾收集器主要管理的场所,因此很多时候也被称为“GC 堆”;

由于现在的垃圾收集器基本都是采用分代收集,所以Java堆还可以细分为:

1. 新生代(Young Generation):新生代又可分为Eden区和Survivor区(s0,s1区)

  • 新生代的垃圾回收称为:Minor GC或者Young GC;
  • 新生代的垃圾回收指发生在新生代的垃圾收集动作,因为新生代对象具有朝生夕灭的特性,所以Minor GC非常频繁,一般回收动作也比较快;
  • 新生代对象产生的结果:1.新生代空间很快不足,需要YGC 2.回收的对象非常多,GC之后,新生代可用的内存空间也非常多 3.可能会导致老年代GC
  • Minor GC的步骤:
  • 1.从Eden区复制存活的对象到suvivor区中的另一块(s1)(S1区和S2区互相转换复制,谁空就复制到谁)
  • 2.清空Eden区和前一块survivor区(s0)
  • 3.给新的需要创建的对象分配内存
    在这里插入图片描述
    . 老年代(Old Generation)
  • 老年代垃圾回收称为:Major GC/old GC
  • 老年代垃圾回收指发生在老年代上的垃圾回收动作。出现了Major GC,一般都会伴随着至少一次的Minor GC;(但不绝对)
  • Major GC 一般比Minor GC慢十倍以上;

3.Full GC:一般根据语义来定回收的内存区域,一般都存在老年代GC。

垃圾回收算法?

  1. 标记清除算法(Mark—Sweep算法)
    • 老年代的垃圾收集算法
    • 算法分为"标记"和"清除"两个阶段:首先标记处所有需要回收的对象,在标记完成后,统一回收所有标记的对象;
    • 不足之处:效率问题:标记和清除两个过程效率都不高。空间碎片问题:标记清除后有大量不连续的空间碎片,导致后续无法分配较大对象,会进一步触发Major GC。
  2. 标记整理算法(Mark—Compact算法)
    • 老年代的垃圾收集算法
    • 标记过程和标记清除阶段一样,但是第二个阶段不是清除,而是将所有存活的对象都移到一端,然后直接清除掉边界以外的内存;
  3. 复制算法(Copying算法)
    • 新生代的垃圾收集算法
    • 将可用内存划分为两部分,每次只用掉其中的一部分,当这一部分内存块用完的时候,就将存活着的对象复制到另外一块没有使用的内存上,然后把已经用掉的那一部分内存全部清理掉;
    • 优点:实现简单高效没有空间碎片。缺点:复制算法的代价就是将内存缩小为原来的一半,代价太高
  4. 分代收集算法(Gener ation Collection)
    • 新生代因为每次回收的时候都有大量的对象死去,所以采用复制算法;(因为新生代对象朝生夕灭,所以将空间划分为8:1:1,可以提减少代价)
    • 老年代对象存活率高,同时也没有额外的空间为它担保,所以只能采用"标记-清除"或者"标记-整理"算法;

内存回收与分配策略?

1. 对象优先在Eden区分配
大多数情况下,对象在新生代的Eden区中分配。当Eden没有空间进行分配的时候JVM就会进行一次Minor GC;

  • Minor GC的步骤:

  • 1.从Eden区复制存活的对象到suvivor区中的另一块(s1)(S1区和S2区互相转换复制,谁空就复制到谁)

  • 2.清空Eden区和前一块survivor区(s0)

  • 3.给新的需要创建的对象分配内存
    2**. 大对象直接进入老年代**

  • 所谓大对象,就是需要大量的连续空间的Java对象,最典型的就是很长的字符串或者数组;

  • 大对象对内存来说不是一个好消息,因为经常出现大对象容易导致内存还有不少空间的时候就提前触发Major GC以获取足够的连续内存空间来放置大对象;
    3. 长期存活的对象会进入老年代

    JVM为了识别哪些对象放在新生代,哪些对象放在老年代,虚拟机给每个对象都定义了一个年龄计数器。如果对象在Eden区出生并且经过了一次Minor GC后存活,并且能被Survivor容纳的话,就会被移动到Survivor空间中,并且把年龄设置为1。这个对象每在Survivor中熬过一次Minor GC 后,就把他的年龄计数器加1岁,当他的年龄增加到一定程度的时候(一般默认为15岁),就会晋升到老年代;

  1. 动态年龄判断
    • 为了更好的适应不同的内存情况,JVM不是永远都要求对象年龄必须达到MaxTenuringThreshold才能晋升到老年代;
    • 如果Survivor空间中相同年龄的所有对象总和大于Survivor空间的一半,年龄大于或者等于该年龄的对象就会进入到老年代,无需等待MaxTenuringThreshold中要求的年龄;
  2. 空间分配担保
    • 在发生MinorGC的时候,使用复制算法将Eden区和Survivor区中存活的对象复制到另一个Survivor区;
      • 放置复制的存活对象的Survivor区只占新生代的10%的内存,不能保证每次Survivor的空间都够用,因此需要依赖其他内存进行分配担保,当另一块Survivor区中没有足够的内存存放上一次Minor GC后新生代存活下来的对象时,这些对象会直接通过分配担保机制进入老年代;
    • 在发生Minor GC之前,虚拟机会检查老年代的最大连续可用内存空间是否大于新生代所有对象的总空间;
      • 如果大于,就说明Minor GC 是安全的;如果小于,虚拟机会查看HandlePromotionFailure设置值是否允许担保失败;
      • 如果HandlepromotionFailure = true,那么会继续检查老年代的最大可用连续空间是否大于每次晋升到老年代的对象的平均大小,如果大于,继续尝试一次Minor GC,此时Minor GC还是有风险的;如果小于或者HandlepromotionFailure = false,则需要进行一次Full GC;

垃圾收集的过程?

  • 对象优先在Eden区分配,Eden空间不足时,触发一次Minor GC,采用复制算法,将Eden区和一个Survivor区中在Minor GC后还存活着的对象一次性全部复制到另一块Survivor区,然后清理掉Eden区和用过的Survivor区;
  • Minor GC结束后,用户线程又开始创建对象放在Eden区,当Eden区空间不足时,重复上述步骤进行Minor GC;
  • 年老的对象进入老年代;
  • Survivor空间不足时,存活的对象通过分配担保机制进入老年代;
  • 老年代空间不足时,触发Major GC;

垃圾收集时会造成的影响?

1. 用户线程暂停(STW)
垃圾回收工作是在垃圾回收线程中执行的,在很多情况下,执行垃圾回收工作某一个步骤的时候,需要暂停用户线程,也就是Stop-The-World,这个过程

  • 新生代GC时都会STW但是因为时间非常快,所以可以忽略不计。老年代GC根据各个阶段看是否造成STW,影响较大。
    在垃圾收集器中,并发和并行的概念有所不同:

  • 并行:指多条垃圾收集线程同时执行,用户线程处于等待状态;

  • 并发:指用户线程和垃圾收集线程同时执行(不一定是同时执行,也可能是交替执行),用户线程继续执行,而垃圾收集线程在另外一个CPU上
    2. 评判垃圾收集器的指标:吞吐量和用户体验

  • 吞吐量:CPU用于运行用户代码的时间和CPU消耗总时间的比值,即吞吐量=运行用户代码的时间/(运行用户代码的时间+垃圾收集的时间)

  • 停顿时间(用户体验):GC造成的用户线程单次停顿的时间;单次停顿时间和总停顿时间成反比;

  • 用户体验优先:用户单次停顿时间越短,用户体验越好,即使总的停顿时间长一点也是可以接受的;

  • 吞吐量优先:用户总的停顿时间越短,吞吐量越高,即使单次停顿时间长点也可以接受;

  • 用户体验优先和吞吐量优先是成相反的关系;

垃圾收集器

是做垃圾回收工作的任务,JVM提供了很多具体的实现,针对不同的区域有具体的垃圾收集器,可能采取不听的垃圾收集算法。

在这里插入图片描述

Serial收集器(新生代收集器,串行GC)

  • 特性:
    • 单线程
    • 使用复制算法
    • STW
  • 应用场景:使用的很少
    • Client模式下的默认新生代收集器
  • 优势:
    • 对于单个CPU来说,Serial收集器没有线程交互的开销,可以专心做垃圾收集,具有最高的收集效率

ParNew收集器(新生代收集器,并行GC)

  • 特性:
    • 多线程
    • 使用复制算法
    • STW
  • 应用场景:
    • 搭配CMS收集器在用户体验优先的程序中使用
  • 优势:
    • 相比于单个CPU环境,随着可以使用CPU数量的增加,他对于CPU资源的利用还是很有好处的

Parallel Scavenge收集器(新生代收集器,并行GC)

  • 多线程。STW。
  • 使用复制算法
  • 可控制的吞吐量
  • 自适应的调节策略:JVM设置参数等于true之后,可以动态监控
  • 应用场景:“吞吐量优先”收集器,适用于吞吐量需求高的任务型程序

Serial Old收集器(老年代收集器,串行GC)

  • 特性:
    • 单线程
    • 标记-整理算法
  • 应用场景:
    • 作为CMS的备用方案使用,在并发收集失败时使用

Parallel Old收集器(老年代收集器,并发GC)

  • 特性:
    • 多线程
    • 标记-整理算法
  • 应用场景:
    • 吞吐量优先场景

CMS收集器(老年代收集器,并发GC)

  • 特性:

    • 并发收集、低停顿;
    • 会用“标记-清楚”算法;
    • 用户体验优先
    • 整个过程分为四个步骤:
      • 初始标记:初始标记标记一下GC Roots能关联到的对象,速度很快,会STW;
      • 并发标记:并发标记就是进行GC Roots 追踪的过程,从GC Roots关联到的对象开始向下搜索标记没有被引用链连接起来的对象;
      • 重新标记:重新标记阶段是为了修正并发标记阶段,因为用户线程也在并发执行,所以导致的原本没有被GC Roots关联到的对象又被关联起来了,标记产生了变动,需要重新标记一下。这个阶段需要STW,停顿的时间比初识标记长一点,但是远比并发标记的时间短;
      • 并发清除:会并发的清除标记的对象;
    • 由于整个阶段耗时最长的并发标记和并发清除两个阶段都是可以和用户线程一起并发执行,不会STW的,所以,从整体上看,CMS收集器的内存回收是与用户线程一起并发执行的。
  • 缺陷:

    • CMS会抢占CPU资源;
    • CMS无法处理浮动垃圾(浮动垃圾就是并发清除阶段,用户线程还在继续执行,还有对象进入老年代,这些对象中的垃圾);
    • CMS使用“标记-清除”算法,会导致大量空间碎片的产生;

    G1收集器(全区域的垃圾收集器)

  • 实现:

    • 把heap堆内存划分为很多的region块,每个region区都是动态指定为Eden区,S区,T区,然后并行对其进行垃圾回收;
    • G1垃圾回收的时候基本不会STW,而是基于most garbage优先回收(整体上看是“标记-整理”算法,从局部上看是两个region的复制算法)的策略对region进行垃圾回收的;
    • 用户体验优先;
    • G1收集器在清理掉垃圾所占的空间后,还会做内存压缩;

如上图所示,E表示Eden区,S表示Survivor区,T表示Tenured区,空白表示未使用的内存空间。G1垃圾收集器还增加了一种新的内存区域,叫Humongous内存区域,如图中的H区域,这种内存用于存储大对象(大小超过一个region大小的50%的对象)

  • 新生代垃圾回收

    • Minor GC采用的是复制算法,把Eden区和Survivor区中还存活的对象复制到某个region区(空白region区动态指定位Survivor区);
    • 对于Eden区和Survivor区满足进入老年区的对象,复制到某个region区(空白region区动态指定为Tenured区)
    • 可以存在多个E区、S区和T区
      在这里插入图片描述
  • 老年代垃圾回收

    • 和CMS一样,也分为四个阶段
      • 初始标记:与CMS不同的是,它不用STW,而是和Minor GC一起发生(G1触发Minor GC的时候,同时将老年代的初始标记给做了)
      • 并发标记:这个阶段G1和CMS做的事情是一样的,不过G1还多做了一件事,就是在并发标记阶段,如果发现哪个T区中对象的存活几率很小或者根本没有对象存活,那么G1会在这个阶段就把他回收掉,不用等待后面的筛选回收阶段;同时,在这个阶段,G1还会计算每个region的对象存活率,方便后面筛选回收阶段使用;
      • 最终标记:这个阶段和CMS的重新标记做的事情是一样的;
        筛选回收:G1中没有CMS的并发清除阶段,而是筛选回收;在筛选回收阶段,G1会挑选出在并发标记阶段计算出的存活率低的region对象进行回收,这个阶段也是和Minor GC一起发生的。

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值