jvm之垃圾回收

jvm垃圾回收

在jvm中,由于不同位置的对象存活时间不同,所以回收各个区域的次数也就不同。总的来说即:

  • 频繁回收新生代(包括幸存者区)
  • 较少回收 老年代
  • 基本不动方法区(元空间)

垃圾回收概念

STW

英文全称为:Stop-the-World,即这个世界都停止。它表示的是在jvm进行垃圾回收的期间,整个应用线程都会被暂停,没有任何的响应。(在STW的时间内,用户的线程会被暂停,但是后台线程仍然在执行)

所有的GC都有STW。

STW的作用:

  • 保证在可达性分析算法中所有的分析工作都是在一个相同一致性快照中执行的
  • 若不能保证一致性,则分析结果无法保证

内存泄漏和内存溢出

内存溢出:

即内存不够用,即程序执行报了OOM的异常。一般来说,在OOM之前,会进行垃圾回收。若分配了一个超大对象,如这个对象超过了堆的大小,则会直接报出OOM。

内存泄漏:

简单来说,内存泄漏就是这内存已经不再被使用了,但是又无法将它进行垃圾回收

内存泄漏的分类:

  • 经常发生:发生内存泄露的代码会被多次执行,每次执行,泄露一块内存;
  • 偶然发生:在某些特定的情况下发生
  • 一次性:发生内存泄漏的方法只会执行异常
  • 隐式泄漏:一直占着内存不释放,直到执行结束;严格的说这个不算内存泄漏,因为最终释放掉了,但是如果执行时间特别长,也可能会导致内存耗尽。

内存泄漏的常见情况

  1. 静态集合:因为静态属性的生命周期和jvm一致,所以生命周期长,所以满足隐式泄漏
  2. 单例模式:和静态集合一样,也是隐式泄漏
  3. 内部类持有外部内的实例对象,则外部内无法被GC。属于隐式泄漏
  4. 各种连接:如数据库连接,若不关闭,则jvm不会去回收它,则会造成堆中又大量的连接对象,但又无法使用,也无法被回收
  5. 变量的作用域不合理:如一个变量之在方法里面使用,但是却定义为成员变量,那么明明这个对象可以在方法执行完就销毁,变成了和对象的生命周期一致了。
  6. 改变hash值:若一个对象存入到了hash表中,在存入只会这个对象的属性值发生了改变,那么这个对象 的hash也就发生了改变,则contains方法找不到之前的hash,那么就会添加一个新的对象,从而导致内存泄漏
  7. 缓存泄漏:将对象的引用放入 缓存中(HashMap),很容易发生遗忘,此时缓存不删除,对象则一直被引用,无法删除。解决方法:使用弱引用或软引用
  8. 监听器的回调

5种引用

  • 强引用:只要存在引用,则不会被回收。是发生内存泄漏的主要原因
  • 软引用:在内存不足的时候

安全区域和安全点

安全点:
在程序执行的时候并非在所有的地方都能够让程序停止下来进行GC,只有在特定的位置才能够停下来进行GC,这些位置就称为安全点

安全点位置的选择选择也行重要,若安全点的数量较少,则程序每次执行的时候到达安全点的时间 也就会更长,从而导致GC等待的时间较长。若安全点数量较多,则会导致运行时的性能问题。所以通常是选择让程序长时间执行的特性来选择安全点,一般是方法调用、循环跳转等

如何在GC发生时,检查所有的线程都跑到最近的安全点停下来?

  • 主动式中断:设置一个中断标识位,在线程到达安全点的时候轮询查看这个标志,若为真,则将程序中断挂起
  • 抢先式中断:让所有线程中断,让后如果线程安全点,则让其恢复,到安全点再停下来(已不再使用此方法)

安全区域

安全区域是指在某一块代码片段种,程序的引用关系不再发生变化,在这个区域中所有位置进行GC都是安全的。

在安全区域中程序的执行:

  • 达到安全区域的时候:会将线程标识为已经进入了Safe Region,如果这段时间内发生GC,JVM会忽略标识为Safe Region状态的线程;
  • 离开安全区域的时候:当线程即将离开Safe Region时,会检查JVM是否已经完成GC,如果完成了,则继续运行,否则线程必须等待直到收到可以安全离开Safe Region的信号为止;

安全区域和安全点的作用:

  • 安全点是程序正常执行时可以触发GC的位置
  • 安全区域是像线程进行sleep的时候,程序无法响应jvm的中断请求,这样可以交个安全区域处理

垃圾回收相关算法

判断垃圾的方法

判断某一个对象是否是垃圾有两种方法:引用计数算法可达性分析算法。引用计数法因为产生循环的时候会判断错误,所以java中采用的是可达性分析算法

可达性分析算法

从根节点出发,能够到达的结点就是有用的,若不能够达到的结点,则代表其没有被任何对象给引用,则是垃圾

那么怎样的对象才能够称为GC Root呢?

  • 虚拟机栈中的引用对象:栈中保存的是方法,若还在栈中,则代表该对象后续还可能被引用
  • 本地方法栈(JNI)中的引用对象
  • 类静态属性引用对象:类静态属性的引用对象和jvm的生命周期一直的,因为静态属性可以通过类直接调用
  • 方法区(元空间)中的常量引用对象:字符串表和常量
  • 所有被同步锁synchronized持有的对象
  • jvm内部的引用:如空指针异常的对象,系统类加载器
  • 反映java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等。
  • 除了这些固定的GC Roots集合以外,根据用户所选用的垃圾收集器以及当前回收的内存区域不同,还可以有其他对象“临时性”地加入,共同构成完整GC Roots集合。比如:分代收集和局部回收(Partial GC)。

总结:由于Root 采用栈方式存放变量和指针,所以如果一个指针,它保存了堆内存里面的对象,但是自己又不存放在堆内存里面,那它就是一个Root

垃圾清除算法

标记-清除算法:

在空间耗尽的时候,则进行GC,它会做两件事情

  1. 从GC Root出发,标记所有被引用的对象,一般在对象的header中记录为可达对象
  2. 对所有对象进行遍历,如果发现这个对象没有被标记,则回收它。

注意:这里标记的是可达对象,清除的是不可达对象。

详情请参考:《编译原理》机械工业出版社 P304

缺点:

  • 效率低,因为要对全部对象遍历两次
  • 清除后的内存空间不连续可能会产生内存碎片
  • 在进行GC的时候,整个线程都会停止,用户体验差
复制算法

字面含义,即将从GC Root可达的对象复制到另外一个内存空间中

优点:

  • 简单:直接复制对象即可
  • 不会有内存碎片

缺点:

  • 耗费空间,因为要使用两片内存空间来进行GC操作
  • 对于G1这种分拆成为大量region的GC,复制而不是移动,意味着GC需要维护region之间对象引用关系,不管是内存占用或者时间开销也不小。

使用场景:

  • 新生代中的垃圾回收都是使用的复算法,因为存活对象少,垃圾多,所以这种算法高效
标记-复制算法

新生代因为存活的对象比较少,可以使用复制算法,倒是到了老年代,复制算法不只浪费内存空间,而且因为存活对象多,所有性能也会下降。所有基于老年代的特定,出现了标记-复制算法

该算法在进行GC时会进行如下步骤:

  • 第一阶段和标记-清除算法一样,从根节点开始标记所有被引用对象
  • 将所有存活的对象压缩到内存的一端,按顺序排放
  • 清除其他地方的内存

优点:

  • 对应未使用的内存来说,空间是连续的,只需要记录一个内存的起始地址即可
  • 相对于复制算法来说,不需要额外空间

缺点:

  • 效率不高
  • 若对象还引用了其他对象,则还要调整对象的引用地址
  • 在移动过程中,全部线程都要暂停
分代收集算法

这不是一种垃圾的清除机制,而是一种垃圾的管理算法,即将生命周期不同的对象进行不同的管理:如分为年轻代、老年代。这样有助于提高执行的效率,在每个不同的区域也可以使用不同的算法进行垃圾的清理。

就如同家里含有一个杂货间,对家中进行打扫的时候一般不对杂货间进行处理,而处理杂货间的时候,打扫的方式也有所不同

分区算法

这个算法是对堆空间进行管理的算法。

G1使用的便是这个算法,即将对空间分为很多个小的区域,每个区域都进行独立的使用和独立的回收,这种算法的好处是:每次回收若干个小块,而不是回收整个堆空间,从而使每次GC的停顿时间减少

分区算法如图:

在这里插入图片描述

垃圾回收器

评价指标

GC的评价指标包括吞吐量和暂停时间

  • 吞吐量:CPU运行用户代码是时间和总时间的比值,即:吞吐量 = 运行用户代码时间 /(运行用户代码时间 + 垃圾收集时间)
  • 暂停时间:即一段时间内用户线程暂停的时间,即垃圾收集的时间

吞吐量和暂停时间是一个矛盾的关系,所以现在的jvm调优选择的是:在最大吞吐量的前提下降低停顿时间

各种垃圾回收器

Serial GC:串行回收

串行:像糖葫芦一样是一串的,吃了第一个 糖葫芦才可以吃下面的哪个。

串行回收代表的是:用户线程执行后,到了GC的时候,有GC的线程进行垃圾回收

如图所示:

在这里插入图片描述

这个收集器是一个单线程的收集器,但它的“单线程”的意义并不仅仅说明它只会使用一个 CPU 或一条收集线程去完成垃圾收集工作,更重要的是在它进行垃圾收集时,必须暂停其他所有的工作线程,直到它收集结束(Stop The World)。

优势:在当线程的情况下,它比较高效,因为可以节约线程切换的时间

Parallel New GC:并行回收

简称:ParNew GC

并行:同时又多个线程一起执行。三人行,必有我师焉

在多线程的情况下,ParNew比Serial更好,但是单线程下则不是。

Parallel GC:吞吐量优先

它和Parallel New一样,都是并行的垃圾回收器,但是它的特点是它遵循吞吐量优先原则,而且它还具有自适应调节机制

在JDK1.6时提供了用于执行老年代垃圾收集的Parallel Old收集器,用来代替老年代的Serial Old收集器。

注意:Parallel Old和Parallel New GC不能一起使用

高吞吐量则可以高效率地利用 CPU 时间,尽快完成程序的运算任务,主要适合在后台运算而不需要太多交互的任务。因此,常见在服务器环境中使用。例如,那些执行批量处理、订单处理、工资支付、科学计算的应用程序。

GC图示:

在这里插入图片描述

CMS:低延迟

并发:在某一时间段内内发生

第一款真正意义上的并发收集器,它第一次实现了让垃圾收集线程与用户线程同时工作。

这下面的并发代表的是:在这一段时间内,用户线程和并发线程一起交替执行,不是如图所示的一起执行

CMS图示:

在这里插入图片描述

解释:

  • 初始标记:标记GC Roots直接关联的对象,耗时很短
  • 并发标记:从GC Roots出发,遍历全部对象,这个时间不会使线程暂停。这个过程耗时长
  • 从新标记:修复并发标记中,因线程导致的不一致问题。
  • 并发清理:最耗时

补充:

  • 由于在GC阶段用户线程仍然会执行,所以它无法像其他垃圾回收期一样等老年代空间不足的时候才回收,它有一个阈值,当堆内存使用率达到某一阈值时,便开始进行回收
  • 若CMS运行期间预留的内存无法满足程序需要,就会出现一次“Concurrent Mode Failure”失败,这时虚拟机将启动后备预案:临时启用 Serial Old 收集器来重新进行老年代的垃圾收集,这样停顿时间就很长了。
  • 它采用的是的标记-清除算法,会产生一些垃圾碎片
  • 因为在GC过程中,用户线程仍然在使用,所以无法使用标记-复制算法

CMS的优点:

  • 并发收集
  • 低延迟

CMS的缺点:

  • 会有内存碎片
  • 无法处理浮动垃圾:在并发标记阶段产生的垃圾,只能在下一次回收的时候再处理
  • 对CPU的资源非常敏感:在并发阶段,它虽然不会导致用户停顿,但是会因为占用了一部分线程而导致应用程序变慢,总吞吐量会降低。

G1 GC:区域化分代式

概念:
  • G1(Garbage-First)是一款面向服务端应用的垃圾收集器,兼顾吞吐量和停顿时间的GC实现。
  • 在jdk 9默认的GC选项,取代了jdk8的CMS
  • 官方设定的目标是在延迟可控的情况下获得尽可能高的吞吐量
特点:
  • 并行和并发都具有
  • 分区(Region)收集垃圾,每一个Region中都是一个逻辑上独立的区域,它可以用来存储伊甸园区、幸存者区、老年代的对象
  • 相对于CMS,其使用的是标记-压缩(Mark-Compact)算法
  • 可预测的停顿时间模型:根据后台维护的优先列表来选择回收的区域,以获得尽可能高的收集效率。
  • HotSpot 垃圾收集器里,除了G1以外,其他的垃圾收集器使用内置的JVM线程执行GC的多线程操作,而G1 GC可以采用应用线程承担后台运行的GC工作,即当JVM的GC线程处理速度慢时,系统会调用应用程序线程帮助加速垃圾回收过程。
缺点:

用户程序运行过程中,G1无论是为了垃圾收集产生的内存占用(Footprint)还是程序运行时的额外执行负载(Overload)都要比CMS要高。

从经验上来说,在小内存应用上CMS的表现大概率会优于G1,而G1在大内存应用上则发挥其优势。平衡点在6-8GB之间

回收过程

根据内存的紧张程度,进行不同的GC

  1. 年轻代GC:当Eden空间耗尽时,G1会启动一次年轻代垃圾回收过程,只会回收Eden区和Survivor区。会STW
  2. 年轻代GC+并发标记过程:在这个过程中,标记根节点的时候会有STW
  3. 混合回收 :回收一部分老年代和全部新生代
  4. Full GC:性能非常差,要避免
使用建议:
  • 尽量不要去设置新生代的大小
  • 暂停时间不用太过于苛刻,太苛刻会影响吞吐量
G1调优步骤

第一步:开启G1垃圾收集器
第二步:设置堆的最大内存
第三步:设置最大的停顿时间

总结:

垃圾回收期的关系

在这里插入图片描述

各种垃圾回收其的特点

在这里插入图片描述

调优永远是针对特定场景、特定需求,不存在一劳永逸的收集器

参考视频:尚硅谷-jvm

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值