关于JVM垃圾回收的一些笔记

概念

对于某个对象而言,只要应用程序中持有该对象的引用,就说明该对象不是垃圾。当程序运行到某一阶段的时候,这个对象没有任何指针对其引用,那么它就是垃圾。

1.1 如何识别垃圾

1.1.1 Reference count 引用计数

判断这个对象没有被引用,那么它就是垃圾。这个方法实现简单,效率高,但是目前主流的虚拟机中并没有选择这个算法来管理内存,主要它存在对象之间相互循环引用的问题。

1.2.2 Root Searching 根搜索/根可达

能作为GC ROOTs根节点有: 线程栈的本地变量,静态变量,本地方法栈变量,常量引用,类加载器。

通过GC ROOTS,这些GC ROOTS向下搜索,看对象是否可达,找到的对象都标记为非垃圾对象,其余未标记的对象都是垃圾对象。
在这里插入图片描述

2 垃圾回收算法

2.1 Mark-Sweep 标记-清除算法

首先标记可以回收的对象,标记完成后统一回收所有被标记的对象。这种算法在清除后会产生大量不连续的内存碎片。

例如:回收前
在这里插入图片描述
回收后:
在这里插入图片描述

2.2 Copying 复制算法

将内存分成大小相同的两块,一次只使用其中的一块。当一块内存使用完成后,就将存活的对象复制到另一块内存块去,然后在把使用的空间一次性清理掉。缺点:内存的利用率低,浪费内存

垃圾回收前:
在这里插入图片描述
垃圾回收后:
在这里插入图片描述

2.3 Mark-compact 标记整理算法

首先标记存活对象,然后让所有的存活对象向一端移动,然后清除掉所有的边界以外的内存。效率低!

处理前
在这里插入图片描述
处理后
在这里插入图片描述

3 分代收集算法

当前的虚拟机的垃圾收集都采用分代收集算法,根据对象的存活周期不同将内存分为几块。一般分为新生代和老年代。更具各个年代的特点选择合适的垃圾收集算法。

在新生代,大量的对象存活时间很短,所以可以选择复制算法,只需要付出少量对象的复制成本就可以完成每次垃圾收集。而老年代的对象存活几率是比较高的,所有一般选择"标记清除"或者“标记整理”算法经行垃圾收集。标记清除或者标记整理算法比复制算法慢10倍以上。
在这里插入图片描述
MinorGC = YGC 新生代垃圾回收
MajorGC = FGC 老年代垃圾回收,应该尽量减少尽量减少FGC

1). 新生代 = Eden + 2个suvivor区
YGC回收之后,大多数的对象会被回收,活着的进入s0
再次YGC,活着的对象eden + s0 -> s1
再次YGC,eden + s1 -> s0
年龄足够 -> 老年代 (15 CMS 6)
s区装不下 -> 老年代

2). 老年代
顽固分子 年代满了FGC Full GC

4 垃圾收集器

垃圾回收器的发展路线,是随着内存越来越大的过程而演进.从分代算法演化到不分代算法
Serial算法 几十兆
Parallel算法 几个G
CMS 几十个G - 承上启下,开始并发回收

在这里插入图片描述

4.1 Serial 年轻代 串行回收

Serial(串行)收集器是最基本、历史最悠久的垃圾收集器了。是一个单线程收集器了。它的 “单线程” 的意义不仅仅意味着它只会使用一条垃圾收集线程去完成垃圾收集工作,更重要的是它在进行垃圾收集工作的时候必须暂停其他所有的工作线程( Stop The World),直到它收集结束。

单机cpu效率最高,虚拟机是client模式的默认垃圾回收器

新生代采用复制算法,老年代采用标记整理算法
在这里插入图片描述
Serial收集器由于没有线程交互的开销,自然可以获得很高的单线程收集效率。

Serial Old收集器是Serial收集器的老年代版本,它同样是一个单线程收集器。

它主要有两大用 途:一种用途是在JDK1.5以及以前的版本中与Parallel Scavenge收集器搭配使用,另一种用途是 作为CMS收集器的后备方案。

4.2 ParNew收集器

ParNew收集器其实就是Serial收集器的多线程版本,除了使用多线程进行垃圾收集外,其余行为 (控制参数、收集算法、回收策略等等)和Serial收集器完全一样。使用:-XX:UserParNewGC

默认的收集线程数跟cpu核数 相同,当然也可以用参数(-XX:ParallelGCThreads)指定收集线程数,但是一般不推荐修改
在这里插入图片描述
它是许多运行在Server模式下的虚拟机的首要选择,只有它能与CMS收集器 (真正意义上的并发收集器,后面会介绍到)配合工作。

4.3 Parallel Scavenge收集器

Parallel Scavenge 收集器类似于ParNew 收集器,是Server 模式(内存大于2G,2个cpu)下的默认收集器。
使用: -XX:+UseParallelGC(年轻代),- XX:+UseParallelOldGC(老年代)。

Parallel Scavenge收集器关注点是**吞吐量(**高效率的利用CPU)。CMS等垃圾收集器的关注点 更多的是用户线程的停顿时间(提高用户体验)。

所谓吞吐量就是CPU中用于运行用户代码的时 间与CPU总消耗时间的比值。 Parallel Scavenge收集器提供了很多参数供用户找到最合适的停顿时间或最大吞吐量。

新生代采用复制算法,老年代采用标记-整理算法。

在这里插入图片描述

4.4 CMS收集器

CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。它非常符合在注重用户体验的应用上使用,它是HotSpot虚拟机第一款真正意义上的并发收集器, 它第一次实现了让垃圾收集线程与用户线程(基本上)同时工作。使用:-XX:+UseConcMarkSweepGC(old)

从名字中的Mark Sweep这两个词可以看出,CMS收集器是一种 “标记-清除”算法实现的,整个过程分为四个步骤:

初始标记 : 暂停所有其他线程Stop The Word,并记录gc roots能直接引用的对象,速度很快。
并发标记 :同时开启GC和用户线程,用一个闭包结构去记录可达对象。但在这个阶段结束,这个闭包结构并不能保证包含当前所有的可达对象。因为用户线程可能会不断的更新引用域,所以GC线程无法保证可达性分析的实时性。所以这个算法里会跟踪记录这些发生引 用更新的地方。
重新标记 :修改并发标记因用户程序变动的内容 Stop the World
并发清理 :开启用户线程,同时GC线程开始对未标记的区域做清扫。
由于整个过程中,并发标记和并发清除,收集器线程可以与用户线程一起工作,所以总体上来 说,CMS收集器的内存回收过程是与用户线程一起并发地执行的。

优点 :并发收集,停顿低
缺点:
1.对CPU资源敏感(会和服务抢资源)
2.无法处理浮动垃圾(在并发清理阶段产生垃圾,这种浮动垃圾只能等下次gc再经行清理)
3.使用标记-清除算法,会导致大量的空间碎片产生,当然 通过参数-XX:+UseCMSCompactAtFullCollection可以让jvm在执行完标记清除后再做整理
4 .执行过程中的不确定性,会存在上一次垃圾回收还没执行完,然后垃圾回收又被触 发的情况,特别是在并发标记和并发清理阶段会出现,一边回收,系统一边运行,也许没回 收完就再次触发full gc,也就是"concurrent mode failure",此时会进入stop the world,用serial old垃圾收集器来回收

4.5 G1收集器

G1 (Garbage-First) 是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器. 以极高概率满足GC停顿时间要求的同时,还具备高吞吐量性能特征。使用: -XX:UseG1GC
在这里插入图片描述
G1将Java堆划分为多个的独立区域(Region),JVM最多可以有2048个Region。 一可以 用参数"-XX:G1HeapRegionSize"手动指定Region大小,但是推荐默认的计算方式。 G1保留了年轻代和老年代的概念,逻辑上分代,但是物理上不分代。

默认年轻代对堆内存的占比是5%,可以通过“-XX:G1NewSizePercent”设置新生代初始占比,在系统 运行中,JVM会不停的给年轻代增加更多的Region,但是最多新生代的占比不会超过60%,可以 通过“-XX:G1MaxNewSizePercent”调整。年轻代中的Eden和Survivor对应的region也跟之前 一样,默认8:1:1,假设年轻代现在有1000个region,eden区对应800个,s0对应100个,s1对应 100个。

一个Region可能之前是年轻代,如果Region进行了垃圾回收,之后可能又会变成老年代,也就是说Region的区域功能可能会动态变化。

G1有专门分配大对象的Region叫Humongous区。在G1中,大对象的判定规则就是一个大对象超过了一个Region大小的50%,而且 一个大对象如果太大,可能会横跨多个Region来存放。Humongous区专门存放短期巨型对象,不用直接进老年代,可以节约老年代的空间,避免因为老 年代空间不够的GC开销。 Full GC的时候除了收集年轻代和老年代之外,也会将Humongous区一并回收。

G1收集器一次GC的运作过程大致分为以下几个步骤:
**初始标记(initial mark ,STW):**暂停所有的其他线程,并记录下gc roots直接能引用 的对象,速度很快
并发标记 (Concurrent Marking): 从GC Roots进行可达性分析,找出存活的对象,与用户线程并发执行
**最终标记(Final Marking STW)😗*修正在并发标记阶段因为用户程序的并发执行导致变动的数据,需暂停用户线程
**筛选回收(Live Data Counting and Evacuation,STW):**对各个Region的回收价值和成本进行排序,根据 用户所期望的GC停顿时间制定回收计划

比如说老年代此时有1000个Region都满了,但是因为根据预期停顿时间,本次垃圾回收可能只能停顿200毫秒,那么通过之前回收成本计算得知,可能回收其中800个 Region刚好需要200ms,那么就只会回收800个Region,尽量把GC导致的停顿时间控制在 我们指定的范围内。这个阶段其实也可以做到与用户程序一起并发执行,但是因为只回收一部分Region,时间是用户可控制的,而且停顿用户线程将大幅提高收集效率。不管是年轻代 或是老年代,回收算法主要用的是复制算法(局部,从整体上看是标记整理算法),将一个region中的存活对象复制到另一个 region中,G1采用复制 算法回收几乎不会有太多内存碎片。
在这里插入图片描述

G1收集器在后台维护了一个优先列表,每次根据允许的收集时间,优先选择回收价值最大的 Region(这也就是它的名字Garbage-First的由来),比如一个Region花200ms能回收10M垃圾,另外一个Region花50ms能回收20M垃圾,在回收时间有限情况下,G1当然会优先选择后面这个Region回收。这种使用Region划分内存空间以及有优先级的区域回收方式,保证了G1收集 器在有限时间内可以尽可能高的收集效率。

G1 收集器的特点
**分代收集:**虽然G1可以不需要其他收集器配合就能独立管理整个堆,但是还是保留了分代的概念

空间整合: 与CMS的“标记–清除” 算法不同,G1整体来看是基于“标记整理”算法实现的收集器;从局部上看是“复制”算法实现的

**可预测的停顿:**这是G1相对于CMS的另一个大优势,降低停顿时间是G1 和 CMS 共同的关注点,但G1 除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指 定在一个长度为M毫秒的时间片段(通过参数"-XX:MaxGCPauseMillis"指定)内完成垃圾收集。
停顿时间:垃圾收集器进行垃圾回收终端应用执行响应的时间

吞吐量:运行用户代码时间/(运行用户代码时间+垃圾收集时间)

停顿时间越短就越适合需要和用户交互的程序,良好的响应速度能提升用户体验;

高吞吐量则可以高效地利用CPU时间,尽快完成程序的运算任务,主要适合在后台运算而不需要太多交互的任 务。

G1 垃圾收集分类

  1. YoungGC :YoungGC并不是说现有的Eden区放满了就会马上触发,而且G1会计算下现在Eden区回收大 概要多久时间,如果回收时间远远小于参数 -XX:MaxGCPauseMills 设定的值,那么增加年轻代 的region,继续给新对象存放,不会马上做Young GC,直到下一次Eden区放 满,G1计算回收时 间接近参数 -XX:MaxGCPauseMills 设定的值,那么就会触发Young GC。

  2. MixedGC:不是FullGC,老年代的堆占有率达到参数(-XX:InitiatingHeapOccupancyPercen)设定的值 则触发,回收所有的Young和部分Old(根据期望的GC停顿时间确定old区垃圾收集的优先顺序)以 及大对象区,正常情况G1的垃圾收集是先做MixedGC,主要使用复制算法,需要把各个region中 存活的对象拷贝到别的region里去,拷贝过程中如果发现没有足够的空region能够承载拷贝对象 就会触发一次Full GC

  3. Full GC:停止系统程序,然后采用单线程进行标记、清理和压缩整理,好空闲出来一批Region来供下 一次MixedGC使用,这个过程是非常耗时的。

G1 垃圾收集器优化建议

假设参数 -XX:MaxGCPauseMills 设置的值很大,导致系统运行很久,年轻代可能都占用了堆 内存的60%了,此时才触发年轻代gc。 那么存活下来的对象可能就会很多,此时就会导致Survivor区域放不下那么多的对象,就会进 入老年代中。

或者是你年轻代gc过后,存活下来的对象过多,导致进入Survivor区域后触发了动态年龄判定 规则,达到了Survivor区域的50%,也会快速导致一些对象进入老年代中。

所以这里核心还是在于调节 -XX:MaxGCPauseMills 这个参数的值,在保证他的年轻代gc别太 频繁的同时,还得考虑每次gc过后的存活对象有多少,避免存活对象太多快速进入老年代,频繁触 发mixed gc.

如何选择垃圾收集器

  1. 优先调整堆的大小让服务器自己来选择
  2. 如果内存小于100M,使用串行收集器
  3. 如果是单核,并且没有停顿时间要求,使用串行或JVM自己选
  4. 如果允许停顿时间超过1秒,选择并行或JVM自己选
  5. 如果响应时间最重要,并且不能超过1秒,使用并发收集器
  6. 串行收集器->Serial和Serial Old 只能有一个垃圾回收线程执行,用户线程暂停。 适用于内存比较小的嵌入式设备
  7. 并行收集器[吞吐量优先]->Parallel Scanvenge、Parallel Old 多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态。 适用于科学计算、后台处理等若交互场景
  8. 并发收集器[停顿时间优先]->CMS、G1 用户线程和垃圾收集线程同时执行(但并不一定是并行的,可能是交替执行的),垃圾收集线程在执行的 时候不会停顿用户线程的运行。 适用于相对时间有要求的场景,比如Web

关于垃圾回收机制的关键点

  1. 只回收JVM堆内存中的对象空间;
  2. 堆其他物理连接,比如数据库连接,输入流、输出流,Socket连接无能为力;
  3. JVM有多种垃圾回收机制实现算法,变现各异;
  4. 垃圾回收机制具有不可预知性,程序无法精确控制垃圾回收机制的执行;
  5. 可以将应用变量的值设置为null,暗示垃圾回收机制可以回收该对象;
  6. 可以通过System.gc()或者Runtime.getRuntime().gc()来通知系统进行垃圾回收,会有一些效果,但是系统是否进行垃圾回收依然不确定;
  7. 垃圾回收机制回收任何对象之前,总会先调用它的finalize方法(如果覆盖该方法,让一个新的引用变量重新引用该变量,则会重新激活对象)。
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值