JVM-垃圾收集器

名词解释

并行和并发

  • 并行与并发在垃圾回收上的含义
    • 并行(Parallel) :指多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态。
    • 并发(Concurrent):指用户线程与垃圾收集线程同时执行(但不一定是并行的,可能会交替执行),用户程序在继续运行。而垃圾收集程序运行在另一个CPU上。?
  • 参考:https://blog.51cto.com/u_15334563/3473784

吞吐量(Throughput)

吞吐量就是CPU用于运行用户代码的时间CPU总消耗时间的比值,即

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

假设虚拟机总共运行了100分钟,其中垃圾收集花掉1分钟,那吞吐量就是99%。

GC回收算法

分代收集(Generational Collection)算法,:

根据对象存活周期不同、对内存区域进行划分(新生代、老年代)、根据各代特点采用不同的GC算法。

分区算法:

堆空间划分连续不同的小空间、每个小区间独立使用、独立回收。

优点:控制一次回收小空间数量、根据目标停顿时间、合理回收若干小区间(而不是整个堆)、减少GC过程的停顿。

垃圾回收器的作用

通过垃圾回收器进程对应用 不在使用的对象 所占用的内存空间进行回收。

对象不在被使用的判断方法

1、引用计数法

在对象头处维护一个计数器,每增加一次对该对象的引用计数器加一,如果对该对象的引用失效,则计数器减一。任何时刻计数器为0则表示该对象不会在被使用。
存在问题:是不能很好的解决循环引用的问题,这也就意味着在这种场景下垃圾回收不在起作用。
循环引用问题:指的是 两个对象之间互相引用,即使它们都无法被外界使用时,它们的引用计数器也不会为0。

2、 可达性分析法

算法思路:通过GC根节点(GC Roots)的对象作为起始点,从这些节点开始进行向下搜索,搜索所走过的路径成为 引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连(用图论的话来说就是从 GC Roots到这个对象不可达)时,则证明此对象是不可用的。如下图所示,对象object 5、object 6、object 7虽然互相有关联,它们的引用并不为0,但是它们到GC Roots是不可达的,因此它们将会被判定为是可回收的对象。

GC Roots
一句话总结 :只要你的对象被 方法的局部变量、类的静态变量 给引用了,就不会回收他们。
假设没有GC Roots引用的对象,是一定立马被回收吗?

其实不是的,这里有一个finalize()方法可以拯救他自己,看下面的代码。

假设有一个ReplicaManager对象要被垃圾回收了,那么假如这个对象重写了Object类中的finialize()方法
此时会先尝试调用一下他的finalize()方法,看是否把自己这个实例对象给了某个GC Roots变量,比如说代码中就给了ReplicaManager类的静态变量。
如果重新让某个GC Roots变量引用了自己,那么就不用被垃圾回收了。
如何判断一个类是无用的类
方法区主要回收的是无用的类,那么如何判断一个类是无用的类的呢?
类需要同时满足下面3个条件才能算是 "无用的类" :

  • 该类所有的实例都已经被回收,也就是 Java 堆中不存在该类的任何实例。
  • 加载该类的 ClassLoader 已经被回收。
  • 该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
    对象在JVM中是如何分配的
    大家对对象内存分配,了解到这个程度就行了,给大家总结一下:

先理解对象优先分配在新生代
新生代如果对象满了,会触发Minor GC(或者叫Young GC)回收掉没有人引用的垃圾对象
如果有对象躲过了十多次垃圾回收,就会放入老年代里
如果老年代也满了,那么也会触发垃圾回收,把老年代里没人引用的垃圾对象清理掉。

垃圾收集算法

标记-复制算法(Copying)

将可用 内存按容量分成大小相等的两块,每次只使用其中的一块。
当这一块内存用完, 就将还存活着的对象 复制到另一块上面,然后再把已 使用过的内存空间一次清理掉。
优点:不产生内存碎片
缺点:

标记-清除算法(Mark-Sweep)

算法分成 "标记"、"清除"两个阶段:
首先 标记存活的对象,统一回 收所有未被标记的对象。标记过程就是对象是否属于垃圾的判定过程(也可以反过来标记出所有需要回 收的对象,在标记完成后,统一回收掉所有被标记的对象)
优点 **:**实现简单,不需要对象进行移动。
缺点:

  1. 效率问题 (如果需要标记的对象太多,效率不高)
  2. 空间问题(标记清除后会产生大量不连续的碎片)

标记-整理算法(Mark-Compact)

此算法的标记过程与 标记-清除 算法一样,但后续步骤不是直接对可回收对象进行清理,而是 让所有存活的对象都向一端移动,然后直接清理掉边界以外的内存。

优点:解决了标记-清理算法存在的内存碎片问题。
缺点:仍需要进行局部对象移动,一定程度上降低了效率

Java垃圾回收器种类

Serial

  • 开启参数: (-XX:+UseSerialGC -XX:+UseSerialOldGC)
    • --XX:+UseSerialGC 参数可以指定使用新生代串行垃圾回收或者老年代串行回收器。
  • 采用的是复制算法。是一个单线程收集器(每次回收的时候只有一条垃圾收集线程去完成垃圾收集工作),更重要的是它在进行垃圾收集时,必须暂停其他所有的工作线程,直至Serial收集器收集结束为止("Stop The World")。这项工作是由虚拟机在后台自动发起和自动完成的,在用户不可见的情况下把用户正常工作的线程全部停掉
  • 由于Stop The World带来的不良用户体验,所以在后续的垃圾收集器设计中停顿时间在不断缩短 (仍然还有停顿,寻找最优秀的垃圾收集器的过程仍然在继续)。
  • Serial收集器优于其他垃圾收集器的地方呢?
    • 它简单而高效(与其他收集器的单线程相比)。
    • Serial收集器由于没有线程交互的开销,自然可以获得很高的单线程收集效率。
  • Serial Old收集器是Serial收集器的老年代版本,
    • Serial老年代收集器使用的是标记-整理算法
    • 它同样是一个单线程收集器。它主要有两大用途:
      • 一种用途是在JDK1.5以及以前的版本中与Parallel Scavenge收集器搭配使用,
  • 另一种用途是作为CMS收集器的后备方案。

Parallel Scavenge 英 /'pærəlel/

  • 开启参数: -XX:+UseParallelGC(年轻代),-XX:+UseParallelOldGC(老年代)
  • 并行的多线程新生代收集器
    • 默认收集线程数跟cpu核数相同,可用参数(- XX:ParallelGCThreads)指定收集线程数,但不推荐修改。
    • 是Serial收集器的多线程版本,除了使用多线程进行垃圾收集外,其余行为(控制参数、收集算法、回收策略等等)和Serial收集器类似。
  • 新生代采用复制算法,老年代采用标记-整理算法。
  • 关注点是吞吐量(高效率的利用CPU)。
    • 所谓吞吐量就是CPU中用于运行用户代码的时间与CPU总消耗时间的比值。
    • ParallelScavenge收集器提供了很多参数供用户找到最合适的停顿时间或最大吞吐量,如果对于收集器运作不太了解的话,可以 选择把内存管理优化交给虚拟机去完成也是一个不错的选择。
    • 在注重吞吐量以及 CPU资源的场合,都可以优先考虑 Parallel Scavenge收集器和Parallel Old收集器(JDK8默认的新生代和老年代收集器)。
  • 与ParNew的区别
    • 关于吞吐量
    • 支持自适应GC调节策略
  • 与CMS的区别
  • CMS等垃圾收集器的关注点更多的是用户线程的停 顿时间(提高用户体验)。

ParNew

  • 开启参数:-XX:+UseParNewGC
  • ParNew收集器其实跟Parallel收集器很类似,区别主要在于它可以和CMS收集器配合使用。 新生代采用复制算法,老年代采用标记-整理算法。
  • 它是许多运行在Server模式下的虚拟机的首要选择,除了Serial收集器外,只有它能与CMS收集器(真正意义上的并发收 集器,后面会介绍到)配合工作。

CMS

工作流程
从整体上来说:CMS不是独占的、可以在应用程序运行的过程中进行垃圾回收
工作流程总体来说:初始化标记--》并发标记--》重新标记--》并发清理

初始化标记:

  • 标记出来所有GC Roots直接引用的对象,速度快
    • 方法的局部变量和类的静态变量是GC Roots。但是类的实例变量不是GC Roots。
  • 第一个阶段,虽然说要造成"Stop the World"暂停一切工作线程,但是其实影响不大,因为他的速度很快
  • 如下图:
    • 在初始标记阶段,仅仅会通过"replicaManager"这个类的静态变量代表的GC Roots,去标记出来他直接引用的ReplicaManager对象,这就是初始标记的过程。
  • 他不会去管ReplicaFetcher这种对象,因为ReplicaFetcher对象是被ReplicaManager类的"replicaFetcher"实例变量引用的

    并发标记:
  • 对老年代所有对象进行GC Roots追踪,其实是最耗时的。
  • 特点:
    • 追踪所有对象是否从根源上被GC Roots引用。
    • 系统线程可以任意创建对象,继续运行。
  • 当前阶段伴随系统程序并发运行,不会对系统造成影响

    重新标记:
  • 重新标记阶段就是为了修正并发标记期间因为用户程序继续运行而导致标记产生变动的那一部分对象的标记记录。
  • 特点:
    • 系统进入:"Stop the World"阶段。
  • 速度极快。(标记少数变动的对象)


并发清理:

  • 开启用户线程,同时GC线程开始对未标记的区域做清扫。这个阶段如果有新增对象会被标记为黑色不做任何处理(见下面三色标记算法详解)。
  • 特点:
    • 耗时长
  • 同系统程序并发运行(不影响系统程序执行)
    **并发重置:**重置本次GC过程中的标记数据。
    总结: CMS(Concurrent Mark Sweep)收集器是一种 获取最短回收停顿时间为目标,采用了 "标记-清除"算法实现的。
    使用场景: 互联网网站或者B/S系统的服务端上的Java应用,这些应用都非常重 视服务的响应速度。
    存在问题
    消耗CPU资源
  • 在并发标记和并发清理阶段CMS的垃圾回收线程是比较耗费CPU资源的。
  • CMS默认启动的垃圾回收线程的数量是(CPU核数 + 3)/ 4。

Concurrent Mode Failure

  • 问题描述:
    • 如果CMS垃圾回收期间,系统程序要放入老年代的对象大于了可用内存空间,此时会如何?
    • 发生Concurrent Mode Failure(并发垃圾回收失败了)。
  • 如何处理:
    • 自动用"Serial Old"垃圾回收器替代CMS
    • 直接强行把系统程序"Stop the World"。
    • 重新进行长时间的GC Roots追踪,标记出来全部垃圾对象,不允许新的对象产生。
    • 然后一次性把垃圾对象都回收掉,完事儿了再恢复系统线程。
  • **结论:**在生产实践中,这个自动触发CMS垃圾回收的比例需要合理优化一下,避免"Concurrent Mode Failure"问题
    产生内存碎片问题
  • 老年代的CMS采用"标记-清理"算法,每次都是标记出来垃圾对象,然后一次性回收掉,这样会导致大量的内存碎片产生。
  • 如果内存碎片太多,会导致后续对象进入老年代找不到可用的连续内存空间了,然后触发Full GC。
  • 所以CMS不是完全就仅仅用"标记-清理"算法的,因为太多的内存碎片实际上会导致更加频繁的Full GC。
  • cms可以通过参数控制进行碎片整理
    • -XX:+UseCMSCompactAtFullCollection",默认就打开了
      • 可以让jvm在执行完标记清除后再做整理。
    • "-XX:CMSFullGCsBeforeCompaction",
  • 这个意思是执行多少次Full GC之后再执行一次内存碎片整理的工作,默认是0,意思就是每次Full GC之后都会进行一次内存整理。
    CMS的相关核心参数
  1. -XX:+UseConcMarkSweepGC:启用cms
  2. -XX:ConcGCThreads:并发的GC线程数
  3. -XX:+UseCMSCompactAtFullCollection:FullGC之后做压缩整理(减少碎片)
  4. -XX:CMSFullGCsBeforeCompaction:多少次FullGC之后压缩一次,默认是0,代表每次FullGC后都会压缩一
  5. -XX:CMSInitiatingOccupancyFraction: 当老年代使用达到该比例时会触发FullGC(默认是92,这是百分比)
  6. -XX:+UseCMSInitiatingOccupancyOnly:只使用设定的回收阈值(-XX:CMSInitiatingOccupancyFraction设
    定的值),如果不指定,JVM仅在第一次使用设定值,后续则会自动调整
  7. -XX:+CMSScavengeBeforeRemark:在CMS GC前启动一次minor gc,目的在于减少老年代对年轻代的引
    用,降低CMS GC的标记阶段时的开销,一般CMS的GC耗时 80%都在标记阶段
  8. -XX:+CMSParallellnitialMarkEnabled:表示在初始标记的时候多线程执行,缩短STW
  9. -XX:+CMSParallelRemarkEnabled:在重新标记的时候多线程执行,缩短STW;
相关推荐
武子康2 分钟前
大数据-258 离线数仓 - Griffin架构 配置安装 Livy 架构设计 解压配置 Hadoop Hive
java·大数据·数据仓库·hive·hadoop·架构
Captain823Jack36 分钟前
nlp新词发现——浅析 TF·IDF
人工智能·python·深度学习·神经网络·算法·自然语言处理
豪宇刘1 小时前
MyBatis的面试题以及详细解答二
java·servlet·tomcat
秋恬意1 小时前
Mybatis能执行一对一、一对多的关联查询吗?都有哪些实现方式,以及它们之间的区别
java·数据库·mybatis
Captain823Jack1 小时前
w04_nlp大模型训练·中文分词
人工智能·python·深度学习·神经网络·算法·自然语言处理·中文分词
是小胡嘛2 小时前
数据结构之旅:红黑树如何驱动 Set 和 Map
数据结构·算法
m0_748255022 小时前
前端常用算法集合
前端·算法
东阳马生架构2 小时前
JVM实战—1.Java代码的运行原理
jvm
FF在路上2 小时前
Knife4j调试实体类传参扁平化模式修改:default-flat-param-object: true
java·开发语言
真的很上进2 小时前
如何借助 Babel+TS+ESLint 构建现代 JS 工程环境?
java·前端·javascript·css·react.js·vue·html