JVM学习总结五:垃圾回收(☆)

文章目录

五、垃圾回收(☆)

1.垃圾回收概述

1.什么是垃圾?

没有任何引用指向的一个对象或者多个对象(循环引用)

2.为什么需要GC?
  1. 如果不断进行内存分配而不进行垃圾回收,内存迟早会被消耗完;
  2. GC可以解决内存中的碎片化问题,从而能够为较大的对象分配足够的内存空间;
  3. 随着应用程序的不断完善,用户越来越多,更需要GC来进行性能优化。
3.垃圾回收的主要区域
  1. 方法区:主要回收常量池中废弃的常量(字面量及符号引用)及不再使用的类型
  2. 堆空间:回收垃圾(没有任何引用指向的一个对象或者多个对象(循环引用))

2.垃圾回收相关算法

判断对象存活方式:引用计数算法可达性分析算法
1.标记阶段(表明什么对象需要回收)
1.引用计数算法

原理:每个对象都有一个引用计数器,来记录对象被引用的次数,每增加一次引用计数器+1;每减少一次引用计数器-1,当引用计数器记录值为0时,就表名该对象没有被任何对象引用,则表名该对象为垃圾。

优缺点

优点:实现简单,垃圾对象便于识别;回收效率高;

缺点:出现循环引用的情况

  1. 无法处理循环引用的情况;从而造成内存泄露;
    在这里插入图片描述

  2. 空间:因为需要计数器,所以造成额外的空间开销;

  3. 时间:需要频繁的加1或者减1操作,造成一定的时间开销;

举例:
在这里插入图片描述
在这里插入图片描述

2.可达性分析算法(根搜索算法)

GC Roots:一组活跃引用的根对象集合

基本原理:以根对象为起始点,从上到下搜索被根对象所连接的目标是否可以到达,内存中的存活对象都直接或者间接地与根对象集合相连,搜索所走过的路径被称为引用链,如果目标对象没有被任何引用链相连,则为不可到达,即为内存中的垃圾

优点:可以解决循环引用的问题;

缺点:相较于引用计数算法来说,回收效率稍慢一些。

在这里插入图片描述

3. 可以被当做GC Roots的元素有哪些?(重点是堆外保存堆内对象的地址的那些区域)
  1. 虚拟机栈中引用的对象;如:各个线程中被调用的方法的局部变量等
  2. JNI(本地方法栈中引用的对象)
  3. 方法区(元空间)静态属性引用的对象:如静态变量
  4. 方法区(元空间)中常量引用的对象。如字符串常量池中引用的对象
  5. 所有被synchronized持有的对
  6. 基本数据类型对应的Class对象、异常类对象及系统类加载器
  7. 还需要一些“临时性”对象加入GC Roots结合中,如只针对新生代回收,则堆中新生代以外的引用对象也需要加入GC Roots集合中。
2.标记阶段(表明什么对象需要回收)
1.什么是对象的finalization机制

在gc 回收某个对象之前,会先调用对象的finalize()方法。Object类中的finalize()方法没有任何方法体,对象类可以重写这个方法。可以在对象销毁之前做一些操作。

finalize()的工作原理应该是这样的:一旦垃圾收集器准备好释放对象占用的存储空间,它首先调用finalize(),而且只有在下一次垃圾收集过程中,才会真正回收对象的内存.所以如果使用finalize(),就可以在垃圾收集期间进行一些重要的清除或清扫工作.

2.虚拟机中的对象有哪几种可能的状态(生存还是死亡)?
  1. 可触及的:能够与引用连相连接的对象;
  2. 可复活的:没有任何引用的对象,但是可以在finalize()中被复活的对象;
  3. 不可触及的:对象的finalize()被调用,但是没有被复活的对象。**finalize()**方法只能被调用一次。
3. 判断一个对象是否可以被回收所经历的两次标记过程(如何判断一个对象是否可以被回收?)(面试重点)
  1. 如果对象到GC Root没有引用链连接或者引用计数,则进行第一次标记;

  2. 进行筛选,判断是否有必要调用对象的 finalize() 方法

    1. 对象类没有重写finalize()方法或者已经调用过了finalize()方法,则为不可触及的, 直接回收
    2. 对象重写了该方法但是还没调用过该方法,则回收前会先调用此方法;
  3. 执行二次标记:如果执行finalize()方法后,该对象与引用链上的任何一个对象建立连接,则为复活状态,否则判定为不可达状态。

3.清除阶段(如何回收垃圾)
1.标记-清除算法
  1. 原理:当需要进行GC时,会停止整个程序(STW),然后整个GC过程分为标记阶段和清除阶段
    标记:从引用根对象开始遍历,标记所有被引用的对象,一般在对象头中记录为可达对象。
    清除:对堆内存进行从头到尾的遍历,如果发现某个对象的对象头没有被标记为可达对象,则进行回收

在这里插入图片描述

  1. 优点:最基本的垃圾回收算法

  2. 缺点

    1. 因为需要遍历,所以执行效率不高;
    2. 在进行GC时,需要STW,效率不高的话,会影响用户体验;
    3. 产生内存碎片化问题
2.复制算法(适合在新生代)
  1. 原理:将内存空间分为大小相等的两块,每次只使用其中的一块,垃圾收集时,将正在使用的内存中的存活的对象复制到另一块内存中,然后将正在使用的内存清空。交换两个内存的角色,完成垃圾回收。在堆中新生代分为eden,s0,s1,其中s0,s1就是用的复制算法

在这里插入图片描述

  1. 优点

    1. 执行效率高,省去了清除中的遍历问题
    2. 解决了内存碎片化的问题
  2. 缺点

    1. 空间浪费较明显,始终有一块内存无法使用;
    2. 对于G1这种分成很多region的垃圾回收器来说,复制意味着需要维持region之间对象的引用关系,空间和时间的开销比较大。
  3. 适合场景

    1. 比较适合于垃圾对象较多,存活对象较少的区域,如新生代中的Survivor0区和Survivor1区。不适合老年代中垃圾回收。

在这里插入图片描述

3.标记-整理(压缩)
  1. 原理
    1. 第一阶段和标记-清除算法的标记阶段一样,从根结点开始标记所有被引用的对象
    2. 第二阶段将所有存活的对象压缩到内存的一端,按顺序排放,然后清除边界以外的内存空间

在这里插入图片描述

  1. 优点

    1. 解决了标记-清除内存碎片化的问题
    2. 解决了标记-复制空间浪费一半的问题
  2. 缺点

    1. 整理过程中需要移动对象,如果对象被其他对象所引用,则需要不断调整引用的地址
    2. 效率相对另外两种算法较低;
  3. 适用场景:适合于老年代中的垃圾收集

4.三种垃圾回收算法对比
算法标记-清除复制标记-压缩(整理)
执行效率中等最快最慢
空间开销少(存在内存碎片化大(浪费一半空间)小(不会产生内存碎片化
移动对象
5.分代收集理论

目的:不同声明周期的对象可以采用不用的回收算法,以提高整体的回收效率。

HotSpot虚拟机中的回收策略

  1. 新生代

    1. 新生代特点:区域相对老年代小,对象声明周期短,回收频繁
    2. 针对这种情况应当采用复制算法,回收效率高,针对于空间利用率不高的问题,采用两个Survivor区得以缓解;
  2. 老年代

    1. 老年代特点:相对于新生代大,对象的声明周期长,回收不频繁。
    2. 针对这种情况采用标记-清除+标记-整理相结合的方法,首先标记阶段还是采用两者中的标记方法,即从根对象开始,依次标记所有的存活对象,然后采用清除算法,暂时容忍碎片化问题,等到碎片化问题影响到内存分配时,再采用整理算法,整理碎片化内存。
6.增量收集算法(实际就是每次只收集一部分)

如果一次性将所有垃圾进行回收,需要造成系统长时间的停顿,影响用户体验,可以让垃圾收集线程和用户线程交替执行,每次垃圾收集线程只收集一部分的内存空间,接着切换到应用程序,依次反复,直到垃圾收集完成。

优点:低延迟,用户体验更优;

缺点:吞吐量下降

7.分区收集算法

将整个堆空间划分为连续不等的小区间,根据目标的停顿时间,每次合理的回收若干个小区间,而不是整个堆空间。每一个小区间都独立使用,独立回收。
在这里插入图片描述

3.垃圾回收相关概念

1. System.gc()

会显示触发Full GC,但是无法保证对垃圾收集器的调用

在这里插入图片描述

2.内存溢出与内存泄露
1. 内存溢出(OOM-Out-Of-Memory)

没有空闲内存,并且垃圾收集器也无法提供更多内存。

2.没有空闲内存的原因
  1. 设置的堆内存太小,此时可以通过-Xms:和-Xmx来设置堆空间的起始大小和最大大小;
  2. 代码中创建了大对象,并且长时间不能被垃圾回收器所收集;
3.内存泄露:(非常重要)
  1. 严格意义对象不会被程序用到,但是GC又没有办法回收掉该对象,此时就称为发生了内存泄露
  2. 宽泛意义:实际情况中,一些不好的编程实践造成了对象的声明周期变得很长甚至导致OOM,(如本来可以定义为方法内部的局部变量,定义为类的成员变量甚至定义为静态变量(随着类的加载而加载,随着类的消亡而消亡)造成变量的声明周期加长,本来方法弹栈后可能就会被释放(没有发生逃逸))。

在这里插入图片描述

  1. 举例说明内存泄露(举出关于内存泄露的例子)
    1. 单例的生命周期和程序一样长,单例程序中如果存在对外部对象的引用,则外部对象是没有办法被回收的,会造成内存泄露
    2. 一些提供 close() 的资源未关闭而导致内存泄露(如数据库的连接必须手动close,否则不能被回收)
3.STW

无论那种垃圾收集器,在进行垃圾收集的过程中,都会使程序停顿,这称为STW,被STW中断的程序在完成GC后会自动恢复。

目的:为了保证数据的一致性(不能统计垃圾的时候还一边造垃圾)

4.垃圾回收并行与并发
1.程序的并行与并发
  1. 并发:从一段时间来看,有多个任务在执行,从单一的时间点上来看,只有一个任务在执行,时间上是CPU在快速切换任务交替执行;
  2. 并行:当系统有多个CPU时,一个cpu可以执行一个进程,另一个cpu可以执行另一个进程,两个进程不会互相抢cpu资源,可以同时进行,此称为并行。
  3. 对比并发指的是同一个时间段,多个任务发生了,且抢占cpu资源;并行指的是在用一个时间点,多个任务发生了,不抢占cpu资源
2.垃圾回收的并行与并发
  1. 并行:多条垃圾回收线程并行执行,此时需要停顿用户线程
    在这里插入图片描述

  2. 串行:只有一条垃圾回收线程,当内存不够时,程序暂停,启动垃圾回收,回收完,再启动程序的线程。
    在这里插入图片描述

用户线程与垃圾回收线程同时执行,垃圾回收线程在执行时,不会停顿用户程序的执行。

用户程序在继续进行,而垃圾收集线程运行在另一个CPU,如:CMS和G1。

5.强引用(Strong Reference)
  1. 定义:在java程序中通过new创建了一个对象,并将其赋值给一个变量,该变量就称为指向该对象的一个强引用
  2. 适用场景:99%以上的都是强引用。
  3. 垃圾回收:强引用的对象都是可触及的,GC不会回收掉被强引用的对象(强引用,不回收)。
    在这里插入图片描述
6. 软引用(Soft Reference)
  1. 定义:描述的是一些还在用,但是非必须的对象。
  2. 适用场景:通常用来实现内存敏感的缓存,如高速缓存就用到软引用
  3. 垃圾回收:内存不足即回收
    在这里插入图片描述
7. 弱引用(Weak Reference)
  1. 定义:弱引用也是描述那些非必须的对象,与软引用的区别在于GC时,对于软引用来说需要判断当前内存是否不足,不足的话才进行回收,而对于弱引用不需要进行判断直接回收
  2. 适用场景:保存那些可有可无的缓存数据。
  3. 垃圾回收:发现即回收
    在这里插入图片描述

补充面试题:你用过weakHashMap吗?

采用weakHashMap进行存储后,当内存不足时,能够对该部分进行回收,因为内存的Entry<k,v>类继承了弱引用类。
在这里插入图片描述

8.虚引用(Phantom Reference)
  1. 定义:所有引用中最弱的一个,为一个对象设置虚引用关联的目的是跟踪垃圾回收的过程,被回收后可以发出相应的通知
  2. 适用场景:实现跟踪对象的垃圾回收过程
  3. 垃圾回收:跟踪对象回收过程

4.垃圾回收器

1.垃圾回收主要性能指标
  1. 吞吐量:用户程序运行时间/用户程序运行时间+垃圾回收时间
    1. 吞吐量越高越好,这样会提升用户体验,认为只有应用程序在执行。
  2. 暂停时间:执行垃圾收集时,程序被暂停的时间。
    1. 暂停时间越低越好,对于交互式应用程序,暂停时间越长,越容易出现卡顿现象,影响用户体验。
  3. 现在标准:在最大吞吐量优先的情况下,降低停顿时间。
2.垃圾回收器概述(分类)

串行 (STW时只有一个垃圾回收线程):SerialSerial Old

并行(STW时有多个垃圾回收线程):ParNewParallel ScavengeParallel Old (其中old指的是老年代)

并发(垃圾回收线程和用户线程并发执行):CMSG1

在这里插入图片描述

3. 垃圾回收器的组合关系

新生代Serial, ParNew Parallel Scavenge

老年代Serial Old Parallel OldCMS

新生代和老年代G1

在这里插入图片描述

组合关系

在这里插入图片描述

补充

  1. jdk9中移除了Serial+CMSParNew+Serial Old这两种组合,上图红色虚线部分
  2. jdk14中,删除了CMS垃圾回收器。

目前组合

目前的组合:Serial +Serial OldParallel Scavenge+Parallel OldG1

4. Serial垃圾回收器(串行回收)
1.Serial垃圾回收器

采用复制算法串行收集Stop The World的方式来对新生代进行垃圾收集。是HotSpot虚拟机在客户端模式下默认的新生代垃圾回收器

2.Serial Old垃圾回收器

采用标记-整理算法串行收集Stop The World的方式来对老年代进行垃圾收集。是HotSpot虚拟机在客户端模式下默认的老年代垃圾回收器

3.回收过程

在这里插入图片描述

4.回收优势

因为是单线程,因此没有线程交换的开销,相对于其他垃圾收集器的单线程相比,简单而高效。

5.参数设置
-XX:useSerialGC/useSerialOldGC设置
5.ParNew垃圾回收器(并行回收)
1.回收模式

采用复制算法并行回收和STW的机制进行垃圾回收,是JVM在服务端下的默认新生代垃圾回收器

2.组合搭配

可以和Serial Old搭配使用(JDK9中移除);可以和CMS配合使用(JDK14中删除)

在这里插入图片描述

3.优势

对于新生代,回收次数频繁,因此采用并行方式更加高效。

4.参数设置
-XX:useParNewGC
6.Parallel垃圾回收器(吞吐量优先)
1.Parallel Scavenge垃圾回收器(新生代)

采用复制算法并行回收STW的机制进行垃圾收集。

2.Parallel Scavenge垃圾回收器与ParNew的区别

Parallel Scavenge的目标是达到一个可控吞吐量,而且具备自适应调节策略。

3.Parallel Old垃圾回收器(老年代)

采用标记-整理并行回收STW的机制进行垃圾收集,在JDK8中,默认Parallel Scavenge+Parallel Old为垃圾回收器。

在这里插入图片描述

4.适用场景

高吞吐量则可以高效的利用cpu的时间,来快速实现计算。主要适合在后台运行而不需要太多交互的任务

7.CMS(Concurrent Mark Sweep)垃圾回收器(低延时,老年代)
1.概念

CMS(Concurrent Mark Sweep)垃圾回收器是HotSpot虚拟机第一款并发垃圾回收器,实现了垃圾收集线程和用户线程同时工作。是基于标记-清除算法,并发回收的老年代垃圾收集器,只搭配ParNew和Serial使用,在jdk14时,CMS垃圾回收器被删除。

2.工作原理

在这里插入图片描述

  1. 初始标记:仅仅只是标记出GC Roots能够直接关联到的对象,存在STW机制(很短暂的STW)。
  2. 并发标记:从GC Root直接关联到的对象开始,遍历整个对象图的过程,该阶段不需要停顿用户线程。
  3. 重新标记:修正并发标记阶段,因用户线程运行而导致标记产生变动的那一部分对象的标记记录,存在STW机制,会产生浮动垃圾
  4. 并发清除:清除标记为已经死亡的对象,释放内存空间。
3.优缺点
  1. 优点:并发收集;低延迟;

  2. 缺点:

    1. 会产生内存碎片,当老年代需要为大对象分配内存时,不得不提前触发Full GC;
    2. 无法处理"浮动垃圾",可能导致"并发失败",从而引发Full GC
    3. 对CPU资源比较敏感:CMS默认的垃圾回收线程数为(处理器核心数+3)/4,对于处理器核心数比较少的情况,垃圾回收线程就占比较大,影响执行速度。

    浮动垃圾:因为在并发清理阶段,垃圾收集线程和用户线程在并发执行,用户线程运行过程中,会产生新的垃圾,而这部分垃圾是发生在标记阶段之后的,所以只能等到下一次GC时,才能够进行回收,这时候需要预留出一定的内存空间,在下图中,a=null后b和c就是垃圾了但是在这一轮垃圾回收中判定不是垃圾,但是会在下次垃圾回收中回收。

在这里插入图片描述

4.使用场景:适用于强交互的应用
5.参数设置
  1. -XX:+UseConcMarkSweepGC:设置使用CMS垃圾收集器;
  2. -XX:CMSinitialingOccupanyFraction:设置堆内存使用率阈值;jdk5,默认为68%;jdk6.默认为92%。
  3. -XX:ParallelCMSThreads:设置CMS的线程数量
6.关于CMS的面试题
  1. 为什么说CMS是一款低延时的垃圾回收器?

因为在初始标记阶段和重新标记阶段,会发生STW,但是此部分时间很短,而在并发标记和并发清除阶段,虽然占据的时间比较长,但是在此期间,垃圾回收线程和用户线程并发执行,因此为低延时垃圾回收器

  1. CMS会产生内存碎片化问题,为什么不用标记整理算法呢?

因为CMS在并发标记和并发清除阶段,用户线程和垃圾回收线程并发执行,而标记整理算法中存在对象在内存中的定向移动,用户线程在执行过程中,如果发生移动,会造成安全问题。因此无法采用标记-整理算法。

8.G1垃圾回收器
1.概述
  1. 目标是在停顿时间可控的情况下尽可能的提高吞吐量的垃圾收集器
  2. 将整个堆内存分为多个region区,跟踪各个Region区的垃圾堆积的价值大小,然后后台维护一个优先级列表每次根据允许的收集时间,优先回收价值大的region.
  3. 基于复制算法标记-整理算法的并行垃圾回收器,为jdk9之后默认的垃圾回收器
2.回收过程(整体)

整体分为年轻代GC(Young GC), 并发标记老年代混合回收过程,如果G1失效,Full GC作为后备机制

在这里插入图片描述

  1. 当伊甸园区满之后,开始进行年轻代回收,移动存活对象到Survivor区或者Old区;
  2. 当堆内存到达一定阈值(默认45%)时,启动并发标记老年代;
  3. 并发标记结束后,进行混合回收,收集老年代时,一次只需回收一部分老年代对象,(因为存在时间限制)
3.优缺点
1.优点
  1. 并发与并行

并行性:在G1垃圾回收期间,可以有多条垃圾回收线程同时进行回收,有效利用多核计算能力。

并发性:在G1并发标记阶段,允许垃圾收集线程和用户线程并发执行。

  1. 分代收集

G1同时兼顾了年轻代和老年代。因为G1垃圾收集器将堆空间分为不同的Region区,这些区域包含了逻辑上的新生代和老年代。

  1. 不存在内存碎片化问题

因为G1垃圾回收器以Region为基本内存回收单元,Region之间采用复制算法,但是从整体上来说,是采用标记-整理算法,因此可以避免内存碎片问题。

  1. 可预测的停顿时间模型(软实时)

使使用者明确在一个长度为M的时间片段内,用于垃圾回收的时间不超过N

主要是因为G1跟踪各个Region的垃圾堆积价值的大小,然后后台维护一个优先级列表,在有限的时间内,优先收集价值大的Region

2.缺点(不适合小内存应用)

由于垃圾收集产生的内存占用相对大,小内存应用上性能不如CMS。

4.面向服务端的垃圾收集器,主要针对配备多核CPU和大容量内存的机器(低延时,大内存)。
5.Region相关介绍
  1. 大小:G1将整个堆大小分为约2048个Region,每个Region大小相同,且为2的n次幂(一般在1M-32M之间),且在JVM的生命周期内不会改变。
  2. 分布:一个Region有可能属于伊甸园区,S区或者Old区。G1还在堆内存中存放了一个Humogous区,简称H区,当对象大小超过1.5倍的Region区时,将其称为大对象,放在H区。(问题:如果是1.2倍Region大的对象放在哪儿?)

在这里插入图片描述

  1. 设置H区的原因?(防止大对象直接进入老年代)

原因在于,如果不设置H区,那么大的对象则会直接进入到老年代,但是如果这个对象的声明周期比较短,则会长时间存在,并且占据着较大内存,所以将其存放在H区,如果对象大于一个H区大小,则会存在在连续的H区中。如果整个H区都装不下,则会触发Full GC.

  1. 内存分配

Region区采用指针碰撞的方式进行内存分配,并且每个Region内部存在TLAB(线程本地分配缓冲区)。

在这里插入图片描述

6.Remembered Set(记忆集)
  1. 存在必要性:因为存在跨Region引用的存在,判断存活时,如果挨个遍历每个Region的话,势必造成效率降低,于是引进了记忆集。
  2. 原理:每个Region都有一个记忆集,当进行引用数据写入时,先判断是否存在其他Region的引用关系,如果存在的话,则将该引用关系对应的对象写入记忆集的卡表(CardTable)中,然后在GC过程中将Rset加入到GC Roots中
7.回收过程(具体)

在这里插入图片描述

  1. 年轻代GC(与之前讲的一样,只不过是基于分区思想)

JVM优先分配对象到伊甸园区,当伊甸园区满了之后,开始进行年轻代GC,然后,存活下来的对象进入到S区,当S区的对象分代年龄达到阈值后,进入到老年代中,清理过程中的算法采用复制算法。

在这里插入图片描述

  1. 并发标记老年代(深入理解java虚拟机中此部分为G1的收集过程)

    1. 初始标记:只标记GC Roots直接关联到的对象,此过程需要STW(短暂STW)
    2. 并发标记:从GC Root关联到的对象开始,递归遍历整个对象图,此时垃圾收集线程和用户线程并发执行
    3. 最终标记(类似于CMS重新标记):由于在并发标记阶段,用户线程在执行,需要对标记结果进行修正,此时需要STW.
    4. 筛选回收:根据用户设置停顿时间,采用复制算法进行清理(将决定回收的Region中的存活对象复制到空的Region中,然后清空旧的Region空间),此过程需要STW

在这里插入图片描述

  1. 混合回收
    //TODO
8.垃圾回收器总结

在这里插入图片描述

9.如何选择垃圾回收器
  1. 优先调整堆的大小,让JVM能够适应;
  2. 如果是内存比较小,选择Serial +Serial Old垃圾回收器;
  3. 如果是单核,没有停顿时间要求,选择Serial +Serial Old垃圾回收器;
  4. 如果是多核CPU,需要高吞吐量,并且停顿时间不长,选择Parallel+Parallel Old垃圾回收器;
  5. 如果是多核CPU,低停顿时间
  6. 如果是jdk14以前,则可以选用ParNew+CMS或者G1;jdk14后,选择G1;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值