垃圾很大你忍一下!我看着代码这样说到!“JVM 垃 圾 回 收 算 法”



JVM垃圾回收算法和垃圾回收器

JVM 垃圾回收算法和垃圾回收器。

一、垃圾回收的区域

  • 栈:栈中的生命周期是跟随线程,所以一般不需要关注。
  • 堆:堆中的对象是垃圾回收的重点。
  • 方法区:这一块也会发生垃圾回收,不过这块的效率比较低,一般不是我们关注的重点。

二、怎么判断对象的存活

一般有两种方式(引用计数法、可达性分析),JVM使用的是可达性分析

1. 引用计数法

给对象添加一个引用计数器,当对象增加一个引用时计数器加 1,引用失效时计数器减 1。引用计数为 0 的对象可被回收(Python 在用,但主流虚拟机没有使用)。

  • 优点:快,方便,实现简单。
  • 缺陷:对象相互引用时(A.instance=B 同时 B.instance=A),很难判断对象是否该回收。

2. 可达性分析

来判定对象是否存活的。这个算法的基本思路就是通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到 GC Roots 没有任何引用链相连时,则证明此对象是不可用的。

作为 GC Roots 的对象包括下面几种:

  • 当前虚拟机栈中局部变量表中的引用的对象
  • 方法区中类静态属性引用的对象
  • 方法区中的常量引用的对象

3. finalize

Java提供finalize()方法,垃圾回收器准备释放内存的时候,会先调用finalize(),可以完成对象的拯救(不被回收),但是不能保证一定不被回收,说白了就是没啥用,一个坑。

三、各种引用(Reference)

Reference 中存储的数据代表的是另一块内存的起始地址。

1. 强引用

一般的 Object obj = new Object() ,就属于强引用。

(如果有 GCroots 的强引用)垃圾回收器绝对不会回收它,当内存不足时宁愿抛出 OOM 错误,使得程序异常停止,也不会回收强引用对象。

2. 软引用

SoftReference垃圾回收器在内存充足的时候不会回收它,而在内存不足时会回收它。

示例代码:

public static void main(String[] args) {
    String str = new String("SunnyBear"); // 强引用
    SoftReference<String> strSoft = new SoftReference<String>(str);
    str = null; // 干掉强引用,确保只有strSoft的软引用
    System.out.println(strSoft.get()); // SunnyBear
    System.gc(); // 执行一次gc,此命令请勿在线上使用,仅作示例操作
    System.out.println("------------ gc after");
    System.out.println(str); // null
    System.out.println(strSoft.get()); // SunnyBear
}

所以软引用一般用来实现一些内存敏感的缓存,只要内存空间足够,对象就会保持不被回收掉

3. 弱引用 WeakReference

垃圾回收器在扫描到该对象时,无论内存充足与否,都会回收该对象的内存

示例代码:

public static void main(String[] args) {
    String str = new String("SunnyBear"); // 强引用
    WeakReference<String> strWeak = new WeakReference<String>(str);
    str = null; // 干掉强引用,确保只有strSoft的软引用
    System.out.println(strWeak.get()); // SunnyBear
    System.gc(); // 执行一次gc,此命令请勿在线上使用,仅作示例操作
    System.out.println("------------ gc after"); // null
    System.out.println(str); // null
    System.out.println(strWeak.get()); // null
}

实际应用,如 WeakHashMap、ThreadLocal。

4. 虚引用 PhantomReference

幽灵引用,最弱,被垃圾回收的时候收到一个通知,如果一个对象只具有虚引用,那么它和没有任何引用一样,任何时候都可能被回收。

虚引用主要用来跟踪对象被垃圾回收器回收的活动。

四、GC

1. Minor GC

  • 特点: 发生在新生代上,发生的较频繁,执行速度较快。
  • 触发条件: Eden 区空间不足/空间分配担保。

2. Full GC

  • 特点:主要发生在老年代上(新生代也会回收),较少发生,执行速度较慢。
  • 触发条件:
    • 调用 System.gc() 。
    • 老年代区域空间不足。
    • 空间分配担保失败。
    • JDK 1.7 及以前的永久代(方法区)空间不足。

五、垃圾回收算法

1. 复制算法


复制算法

  • 优点
    • 简单高效,不会出现内存碎片。
  • 缺点
    • 内存利用率低。
    • 存活对象较多时效率明显降低,因为需要移动每个不可回收数据的内存实际位置。

2. 标记-清除算法


标记清除算法

  • 优点
    • 利用率100% 。
  • 缺点
    • 标记和清除效率都不高(对比复制算法)。
    • 会产生大量不连续的内存碎片。

3. 标记-整理算法


标记整理算法

  • 优点
    • 利用率100% 。
    • 没有内存碎片。
  • 缺点
    • 标记和清除效率都不高(对比复制算法及标记清楚算法)。

六、垃圾回收器

1. 可以配套使用的垃圾回收器


垃圾回收器

2. Serial/Serial Old

3. ParNew

4. Parallel Scavenge(ParallerGC)/Parallel Old

5. CMS(Concurrent Mark Sweep)

收集器是一种以获取最短回收停顿时间为目标的收集器。目前很大一部分的 Java 应用集中在互联网站或者 B/S 系统的服务端上,这类应用尤其重视服务的响应速度,希望系统停顿时间最短,以给用户带来较好的体验。

CMS 收集器就非常符合这类应用的需求。-XX:+UseConcMarkSweepGC一般新生代使用 ParNew,老年代的用 CMS,从名字(包含“Mark Sweep”)上就可以看出,CMS 收集器是基于“标记—清除”算法实现的,它的运作过程相对于前面几种收集器来说更复杂一些。

回收过程


CMS回收过程

整个过程分为 4 个步骤,包括:

  1. 初始标记
  2. 并发标记
  3. 重新标记
  4. 并发清除

优缺点

  1. 优点

由于整个过程中耗时最长的并发标记和并发清除过程收集器线程都可以与用户线程一起工作,所以,从总体上来说,CMS 收集器的内存回收过程是与用户线程一起并发执行的

  1. 缺点
  • CPU 资源敏感:因为并发阶段多线程占据 CPU 资源,如果 CPU 资源不足,效率会明显降低。
  • 由于 CMS 并发清理阶段 用户线程还在运行着,伴随程序运行自然就还会有新的垃圾不断产生,这一部分垃圾出现在标记过程之后,CMS 无法在当次收集中处理掉它们,只好留待下一次 GC 时再清理掉。这一部分垃圾就称为 浮动垃圾
  • 由于浮动垃圾的存在,因此需要预留出一部分内存,意味着 CMS 收集不能像其它收集器那样等待老年代快满的时候再回收。在 1.6 的版本中老年代空间使用率阈值(92%),如果预留的内存不够存放浮动垃圾,就会出现 Concurrent Mode Failure,这时虚拟机将临时启用 Serial Old 来替代 CMS。
  • 会产生内存碎片:标记-清除算法 会导致产生不连续的内存碎片。

6. G1

G1相比较CMS的改进

  • 基于标记-整理算法, 不会产生空间碎片,分配大对象时不会无法得到连续的空间而提前触发一次full gc 。
  • 停顿时间可控: G1可以通过设置预期停顿时间(Pause time)来控制垃圾收集时间,但是这个预期停顿时间G1只能尽量做到,而不是一定能做到

可预测的停顿:

G1 收集器之所以能建立可预测的停顿时间模型,是因为它可以有计划地避免在整个 Java 堆中进行全区域的垃圾收集。G1 跟踪各个 Region 里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的 Region(这也就是 Garbage-First 名称的来由)。这种使用 Region 划分内存空间以及有优先级的区域回收方式,保证了 G1 收集器在有限的时间内可以获取尽可高的收集效率。

G1的设置参数

  • -XX:+UseG1GC // 开启G1
  • -XX:MaxGCPauseMillis=200 // 预期停顿时间200毫秒,默认也是200
  • -XX:G1HeapRegionSize=2 // 设置每个区域大小2M,其必须是2的幂,范围允许为1Mb到32Mb
  • -XX:G1NewSizePercent // 新生代最小值,默认值 5%
  • -XX:G1MaxNewSizePercent // 新生代最大值,默认值 60%
  • -XX:ParallelGCThreads // STW 期间,并行 GC 线程数
  • -XX:ConcGCThreads=n // 并发标记阶段,并行执行的线程数

G1是怎么划堆内存的呢

G1 把堆划分成多个大小相等的 独立区域(Region),新生代和老年代不再物理隔离

G1 算法将堆划分为若干个独立区域(Region),它仍然属于分代收集器。不过,这些区域的一部分包含新生代,新生代的垃圾收集依然采用暂停所有应用线程的方式,将存活对象拷贝到老年代或者 Survivor 空间。例如其中一个独立区域如图:


独立区域

GC 的模式

1、Young GC

Young GC 主要是对 Eden 区进行 GC,它在 Eden 空间耗尽时会被触发。在这种情况下,Eden 空间的数据移动到 Survivor 空间中,如果 Survivor 空间不够,Eden 空间的部分数据会直接晋升到老年代空间。Survivor 区的数据移动到新的 Survivor 区中,也有部分数据晋升到老年代空间中。最终 Eden 空间的数据为空,GC 停止工作,应用线程继续执行

2、Mixed GC

选定所有新生代里的 Region,外加根据 global concurrent marking 统计得出收集收益高的若干老年代 Region。在用户指定的开销目标范围内尽可能选择益高的老年代 Region。Mixed GC 不是 full GC,它只能回收部分老年代的 Region。如果 mixed GC 实在无法跟上程序分配内存的速度,导致老年代填满无法继续进行 Mixed GC,就会使用 serial old GC(full GC)来收集整个 GC heap。所以我们可以知道,G1 是不提供 full GC 的

收集过程

G1回收过程

大致分为4个步骤:

1、初始标记:仅仅只是标记一下 GC Roots 能直接关联到的对象,并且修改 TAMS(Nest Top Mark Start)的值,让下一阶段用户程序并发运行时,能在正确可以的 Region 中创建对象,此阶段需要停顿线程(STW),但耗时很短

2、并发标记:从 GC Root 开始对堆中对象进行可达性分析,找到存活对象,此阶段耗时较长,但可与用户程序并发执行

3、最终标记:为了修正在并发标记期间因用户程序继续运作而导致标记产生变动的那一部分标记记录,虚拟机将这段时间对象变化记录在线程的 Remembered Set Logs 里面,最终标记阶段需要把 Remembered Set Logs 的数据合并到 Remembered Set 中。这阶段需要停顿线程(STW),但是可并行执行

4、筛选回收:首先对各个 Region 中的回收价值和成本进行排序,根据用户所期望的 GC 停顿时间来制定回收计划。此阶段其实也可以做到与用户程序一起并发执行,但是因为只回收一部分 Region,时间是用户可控制的,而且停顿用户线程将大幅度提高收集效率

七、垃圾回收器的一些重要参数

参数描述
UseSerialGC虚拟机运行在 Client 模式下的默认值,打开此开关后,使用 Serial+Serial Old 的收集器组合进行内存回收
UseParNewGC打开此开关后,使用 ParNew + Serial Old 的收集器组合进行内存回收
UseConcMarkSweepGC打开此开关后,使用 ParNew + CMS + Serial Old 的收集器组合进行内存回收。Serial Old 收集器将作为 CMS 收集器出现 Concurrent Mode Failure 失败后的后备收集器使用
UseParallelGC虚拟机运行在 Server 模式下的默认值,打开此开关后,使用 Parallel Scavenge + Serial Old(PS MarkSweep) 的收集器组合进行内存回收
UseParallelOldGC打开此开关后,使用 Parallel Scavenge + Parallel Old 的收集器组合进行内存回收
SurvivorRatio新生代中 Eden 区域与 Survivor 区域的容量比值,默认为 8,代表 Eden : Survivor = 8 : 1
PretenureSizeThreshold直接晋升到老年代的对象大小,设置这个参数后,大于这个参数的对象将直接在老年代分配
MaxTenuringThreshold晋升到老年代的对象年龄,每个对象在坚持过一次 Minor GC 之后,年龄就增加 1,当超过这个参数值时就进入老年代
UseAdaptiveSizePolicy动态调整 Java 堆中各个区域的大小以及进入老年代的年龄
HandlePromotionFailure是否允许分配担保失败,即老年代的剩余空间不足以应付新生代的整个 Eden 和 Survivor 区的所有对象都存活的极端情况
ParallelGCThreads设置并行 GC 时进行内存回收的线程数
GCTimeRatio GC时间占总时间的比率,默认值为 99,即允许 1% 的 GC 时间,仅在使用 Parallel Scavenge 收集器生效
MaxGCPauseMillis设置 GC 的最大停顿时间,仅在使用 Parallel Scavenge 收集器时生效
CMSInitiatingOccupancyFraction设置 CMS 收集器在老年代空间被使用多少后触发垃圾收集,默认值为 68%,仅在使用 CMS 收集器时生效
UseCMSCompactAtFullCollection设置 CMS 收集器在完成垃圾收集后是否要进行一次内存碎片整理,仅在使用 CMS 收集器时生效
CMSFullGCsBeforeCompaction设置 CMS 收集器在进行若干次垃圾收集后再启动一次内存碎片整理,仅在使用 CMS 收集器时生效

都读到这里了,来个 点赞、评论、关注、收藏 吧!

最后,感谢大家的观看,谢谢大家的支持,能三连关注收藏就最好啦,祝各位面试必过,升职加薪,早日人生巅峰!

  • 8
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 9
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

牛战士从不脱下面具

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值