垃圾回收

垃圾回收GC

GC的概念及意义?

  • 概念:垃圾回收,释放jvm内存,可以在一定程度上避免OOM问题。

  • 意义:每当我们新建对象时,jvm就会自动监控这个对象的地址大小与使用情况,通过可达性分析算法寻找不可达对象,判断是否需要进行GC释放内存。在JVM中,有一个垃圾回收线程,它是低优先级的,在正常情况下是不会执行的,只有在虚拟机空闲或者当前堆内存不足时,才会触发执行,扫面那些没有被任何引用的对象,并将它们添加到要回收的集合中,进行回收。

  • 程序员可以手动执行System.gc(),通知GC运行,但是Java语言规范并不保证GC一定会执行。

对象的引用类型?

  • 强引用:发生gc时不会收回强引用所关联的对象,比如new

  • 软引用:有用但非必须的对象,在OOM之前会把这些对象列进回收范围之中进行第二次回收,若第二次回收还没有足够的内存,则会抛出OOM。也就是第一次快要发生OOM的时候不会立马抛出OOM,而是会回收掉这些软引用,然后再看内存是否足够,若还不够才会抛出OOM。

  • 弱引用:有用但不是必须的对象,在下一次GC时会被回收

  • 虚引用:也叫幽灵引用/幻影引用,无法通过虚引用获得对象,他的意义在于能在这个对象被GC掉时收到一个系统通知

垃圾回收算法?

标记清除算法

所有的垃圾回收算法都是以它为基础的。
分为两步:
标记和清除:
首先需要标记出所有需要回收的对象,然后进行清除回收变为可用内存。
优点
实现简单,不需要对象进行移动。
缺点
算法效率低,会产生垃圾碎片,提高了垃圾回收的频率

复制算法

将可用堆内存按照容量分为大小相等的两块,每次只用一块,当这块内存快用完了,就将还存活着的对象复制到另一块上面,然后再把已使用过的内存一次清理掉。年轻代from/to(s1/s2)采取的就是此种算法。老年代一般不会采取此种算法,因为老年代都是大对象且存活的久的,空间压缩一半代价略高。

  • 优点:运行高效,不会产生碎片。

  • 缺点:可用的内存大小缩小为原来的一半,对象存活率高时会频繁进行复制。

标记整理算法

在标记可回收的对象后将所有存活的对象压缩到内存的一端,使他们紧凑的排列在一起,然后对端边界以外的内存进行回收。

在新生代中可以使用复制算法,但是在老年代就不能选择复制算法了,因为老年代的对象存活率会较高,这样会有较多的复制操作,导致效率变低。标记-清除算法可以应用在老年代中,但是它效率不高,在内存回收后容易产生大量内存碎片。回收后,已用和未用的内存都各自一边。

分代收集算法

根据对象存活周期的不同将内存划分为几块,一般是新生代、老年代,新生代基本采用复制算法老年代采用标记整理算法

分代原因

不同对象生命周期是不一样的,在不进行对象存活时间区分的情况下,每次垃圾回收都是对整个堆空间进行回收,花费时间会相对较长,也有很多对象完全没必要遍历,比如大对象存活的时间更长,遍历下来发现不需要回收,造成资源性能的浪费。所以就有了分代收集算法,进行区域划分,把不同生命周期的对象放在不同的区域,不同的区域采取最适合他的垃圾回收方式进行回收。

分代工作流程

新生代一般采用复制算法,有 3 个分区:Eden、To Survivor、From Survivor,它们的默认占比是 8:1:1
老年代一般采用标记整理算法,提高回收效率。
新生代默认的空间占比总空间的 1/3,老生代的默认占比是 2/3。

  • 新生代:

  1. 把 Eden From Survivor 存活的对象放入 To Survivor 区;

  2. 清空 Eden 和 From Survivor 分区;

  3. From Survivor 和 To Survivor 分区交换,From Survivor 变 To Survivor,To Survivor 变 From Survivor。

每次在 From Survivor 到 To Survivor 移动时都存活的对象,年龄就 1,当年龄到达 15(默认配置是 15)时,升级为老年代。大对象也会直接进入老年代。

  • 老年代:老年代当空间占用到达某个值之后就会触发全局垃圾收回,一般使用标记整理的执行算法。以上这些循环往复就构成了整个分代垃圾回收的整体执行流程。

垃圾回收器

新生代回收器(3个)


Serial

采取复制算法,用于新生代,单线程收集器,所以在他工作时会产生StopTheWorld(GC卡顿)。单线程情况下效率更高,比如用于GUI小程序。

ParNew

采取复制算法,用于新生代,是Serial的多线程版本,多个GC线程同时工作,但是也会产生StopTheWorld(GC卡顿),因为不能和工作线程并行。

Parallel Scavenge

采取复制算法,用于新生代,和ParNew一样,所以也会产生STW,多线程收集器,他是吞吐量优先的收集器,提供了很多参数来调节吞吐量。


老年代回收器(3个)


Serial Old

Serial的老年版本,采取标记整理算法,用于老年代,单线程收集器,所以在他工作时会产生StopTheWorld(GC卡顿)。单线程情况下效率更高,比如用于GUI小程序

Parallel Old

采取标记整理算法,用于老年代,Parallel Scavenge收集器的老年代版本,吞吐量优先。

CMS

采取标记清除算法,老年代并行收集器,号称以最短STW(GC卡顿)时间为目标的收集器,并发高、停顿低、STW(GC卡顿)时间短的优点。主流垃圾收集器之一。

CMS 是英文 Concurrent Mark-Sweep 的简称,是以牺牲吞吐量为代价来获得最短回收停顿时间的垃圾回收器。对于要求服务器响应速度的应用上,这种垃圾回收器非常适合。在启动 JVM 的参数加上“-XX: UseConcMarkSweepGC”来指定使用 CMS 垃圾回收器。CMS 使用的是标记清除的算法实现的,所以在 gc 的时候回产生大量的内存碎片,当剩余内存不能满足程序运行要求时,系统将会出现 Concurrent Mode Failure(并发模式失败 老年代正在清理,从新生代晋升了新的对象,或者直接分配大对象新生代放不下导致直接在老年代生成,这时候老年代也放不下),临时 CMS 会采用 Serial Old 回收器进行垃圾清除,此时的性能将会被降低。


整堆回收器

G1

采取标记整理算法的并行收集器。对比CMS的好处之一就是不会产生内存碎片,此外,G1收集器不同于之前的收集器的一个重要特点是:G1回收的范围是整个Java堆(包括新生代,老年代),而前六种收集器回收的范围仅限于新生代或老年代。而且他的STW(GC卡顿)停顿时间是可以手动控制一个长度为M毫秒的时间片段(可以用JVM参数 -XX:MaxGCPauseMillis指定),设置完后垃圾收集的时长不得超过这个(近实时)。

详解CMS

采取标记清除算法,老年代并行收集器,号称以最短STW(GC卡顿)时间为目标的收集器,并发高、停顿低、STW(GC卡顿)时间短的优点。主流垃圾收集器之一。
工作流程

  • 初始标记:标记 GC Roots 能直接关联的对象,速度很快,仍然需要暂停所有的工作线程。所以此阶段会STW,但时间很短。

  • 并发标记:进行 GC Roots 跟踪的过程,和用户线程一起工作,不需要暂停工作线程。不会STW。

  • 重新标记:为了修正在并发标记期间,因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,仍然需要暂停所有的工作线程。STW时间会比第一阶段稍微长点,但是远比并发标记短,效率也很高。

  • 并发清除:清除GC Roots不可达对象,和用户线程一起工作,不需要暂停工作线程。

优点:
①并发高②停顿低③STW时间短
缺点:

  • 对cpu资源非常敏感(并发阶段虽然不会影响用户线程,但是会一起占用CPU资源,竞争激烈的话会导致程序变慢)。

  • 无法处理浮动垃圾,当剩余内存不能满足程序运行要求时,系统将会出现 Concurrent Mode Failure,失败后而导致另一次Full GC的产生,由于CMS并发清除阶段用户线程还在运行,伴随程序的运行自然会有新的垃圾产生,这一部分垃圾是出现在标记过程之后的,CMS无法在本次去处理他们,所以只好留在下一次GC时候将其清理掉。

  • 内存碎片问题(因为是标记清除算法)。当剩余内存不能满足程序运行要求时,系统将会出现 Concurrent Mode Failure(并发模式失败),临时 CMS 会采用 Serial Old 回收器进行垃圾清除,此时的性能将会被降低。

详解G1

采取标记整理算法的并行收集器。
特点:

  • 并行与并发执行:利用多CPU的优势来缩短STW时间,在GC工作的时候,用户线程可以并行执行。

  • 分代收集:无需其他收集器配合,自己G1会进行分代收集。

  • 空间整合:不会像CMS那样产生内存碎片。

  • 可预测的停顿:可以手动控制一个长度为M毫秒的时间片段(可以用JVM参数 -XX:MaxGCPauseMillis指定),设置完后垃圾收集的时长不得超过这个(近实时)。
    步骤:

  • 初始标记:仅仅标记GCRoots能直接关联到的对象,且修改TAMS的值让下一阶段用户程序并发运行时能正确可用的Region中创建的新对象。速度很快,会STW。

  • 并发标记:进行 GC Roots 跟踪的过程,和用户线程一起工作,不需要暂停工作线程。不会STW。

  • 最终标记:为了修正在并发标记期间,因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,仍然需要暂停所有的工作线程。STW时间会比第一阶段稍微长点,但是远比并发标记短,效率也很高。

  • 筛选回收:首先对各个Region的回收价值和成本进行排序,根据用户所期望的GC停顿时间来制定回收计划。

原理:

G1并不是简单的把堆内存分为新生代和老年代两部分,而是把整个堆划分为多个大小相等的独立区域(Region),新生代和老年代也是一部分不需要连续Region的集合。G1跟踪各个Region里面的垃圾堆积的价值大小,在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region。

Region不是孤立的,也就是说一个对象分配在某个Region中,他并非只能被本Region中的其他对象引用,而是整个堆中任意的对象都可以相互引用,那么在【可达性分析法】来判断对象是否存活的时候也无需扫描整个堆,Region之间的对象引用以及其他手机其中新生代和老年代之间的对象引用虚拟机都是使用Remembered Set来避免全堆扫描的。

Minor GC与Full GC分别在什么时候发生

  • Minor GC 是指发生在新生代的 GC,因为 Java 对象大多都是朝生夕死,所有 Minor GC 非常频繁,一般回收速度也非常快;

  • Full GC 是指发生在老年代的 GC,出现了 Major GC 通常会伴随至少一次 Minor GC。Major GC 的速度通常会比 Minor GC 慢 10 倍以上。发生原因:①老年代被写满(默认90%)②System.gc()被显示调用。

栈上分配

JVM允许将线程私有的对象分配在栈上,而不是分配在堆上。分配在栈上的好处是栈上分配不需要考虑垃圾回收,因为出栈的时候对象就顺带着一起出去了,没了,而不需要垃圾回收器的介入,从而提高系统性能。

  • 对象逃逸

  • TLAB

对象分配规则

对象的内存分配通常是在 Java 堆上分配,对象主要分配在新生代的 Eden 区,如果启动了本地线程缓冲,将按照线程优先在 TLAB 上分配。少数情况下也会直接在老年代上分配。遵循以下规则:

对象优先在 Eden 区分配

对象都在新生代 Eden 区分配。当 Eden 区分配没有足够的空间进行分配时,虚拟机将会发起一次 Minor GC。如果本次 GC 后还是没有足够的空间,则将启用分配担保机制在老年代中分配内存。

  • Minor GC 是指发生在新生代的 GC,因为 Java 对象大多都是朝生夕死,所有 Minor GC 非常频繁,一般回收速度也非常快;

  • Major GC/Full GC 是指发生在老年代的 GC,出现了 Major GC 通常会伴随至少一次 Minor GC。Major GC 的速度通常会比 Minor GC 慢 10 倍以上。

大对象直接进入老年代

所谓大对象是指需要大量连续内存空间的对象,频繁出现大对象是致命的,会导致在内存还有不少空间的情况下提前触发 GC 以获取足够的连续空间来安置新对象。前面我们介绍过新生代使用的是标记-清除算法来处理垃圾回收的,如果大对象直接在新生代分配就会导致 Eden 区和两个 Survivor 区之间发生大量的内存复制。因此对于大对象都会直接在老年代进行分配。

长期存活对象将进入老年代

虚拟机采用分代收集的思想来管理内存,那么内存回收时就必须判断哪些对象应该放在新生代,哪些对象应该放在老年代。因此虚拟机给每个对象定义了一个对象年龄的计数器,如果对象在 Eden 区出生,并且能够被 Survivor 容纳,将被移动到 Survivor 空间中,这时设置对象年龄为 1。对象在 Survivor 区中每「熬过」一次 Minor GC 年龄就加 1,当年龄达到一定程度(默认 15) 就会被晋升到老年代。

动态判断对象的年龄

动态判断对象的年龄,如果Survivor区中相同年龄的所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象可以直接进入老年代。无需等到-XX:MaxTenuringThreshold参数要求的年龄。

空间分配担保

每次进行YGC时,JVM会计算Survivor区移至老年区的对象的平均大小,如果这个值大于老年区的剩余值大小则进行一次Full GC,如果小于检查HandlePromotionFailure设置,如果true则只进行YGC,如果false则进行Full GC。

来源:https://www.icode9.com/content-4-860451.html

(0)

相关推荐

  • Java虚拟机垃圾回收(三) 7种垃圾收集器

    主要特点 应用场景 设置参数 基本运行原理 在<Java虚拟机垃圾回收(一) 基础>中了解到如何判断对象是存活还是已经死亡?在<Java虚拟机垃圾回收(二) 垃圾回收算法>了解 ...

  • 深度揭秘垃圾回收底层,这次让你彻底弄懂她

    Java 与 C++ 之间有一堵由内存动态分配和垃圾收集技术所围成的高墙 ---<深入理解Java虚拟机> 我们知道手动管理内存意味着自由.精细化地掌控,但是却极度依赖于开发人员的水平和细 ...

  • JVM真香系列:如何判断对象是否可被回收?

    回复"000"获取大量电子书 在JVM中程序寄存器.Java虚拟机栈.本地方法栈,这三个区是随着线程的创建而创建,随着线程结束而销毁. 其实就是这三个的生命周期和线程的生命周期一样 ...

  • 应该是全网最全的JVM知识点总结

    应该是全网最全的JVM知识点总结

  • 搞定这24道JVM面试题,要价30k都有底气~

    回复"面试"获取全套面试资料 1.什么是JVM? JVM 的全称是 「Java Virtual Machine」,也就是我们耳熟能详的 Java 虚拟机.它能识别 .class后缀 ...

  • 2021最新 Java虚拟机(JVM)面试题精选(附刷题小程序)

    推荐使用小程序阅读 为了能让您更加方便的阅读 本文所有的面试题目均已整理至小程序<面试手册> 可以通过微信扫描(或长按)下图的二维码享受更好的阅读体验! 目录 推荐使用小程序阅读 1. J ...

  • Java虚拟机(JVM)面试题(2020最新版)

    大家好,我是CSDN的博主ThinkWon,"2020博客之星年度总评选'开始啦,希望大家帮我投票,每天都可以投多票哦,点击下方链接,然后点击'最大",再点击'投TA一票'就可以啦 ...

  • 垃圾回收算法有几种类型? 他们对应的优缺点又是什么?

    常见的垃圾回收算法有: 标记-清除算法.复制算法.标记-整理算法.分代收集算法 标记-清除算法 标记-清除算法包括两个阶段:"标记"和"清除". 标记阶段:确定 ...

  • Go 语言如何实现垃圾回收中的 Stop the World (STW)

    Illustration created for "A Journey With Go", made from the original Go Gopher, created by ...

  • 面试题-python 垃圾回收机制?

    前言 简历上写着熟悉 python 面试官上来就问:说下python 垃圾回收机制?一盆冷水泼过来,瞬间感觉 python 不香了. Python中,主要通过引用计数(Reference Counti ...

  • 【译】Java 14 Hotspot 虚拟机垃圾回收调优指南

    本文主要包括以下内容: 优化目标与策略(Ergonomics) 垃圾收集器实现(Garbage Collector Implementation) 影响垃圾收集性能的因素 总堆(Total Heap) ...

  • PHP垃圾回收机制的一些浅薄理解

    PHP垃圾回收机制的一些浅薄理解 相信只要入门学习过一点开发的同学都知道,不管任何编程语言,一个变量都会保存在内存中.其实,我们这些开发者就是在来回不停地操纵内存,相应地,我们如果一直增加新的变量,内 ...

  • PHP中的垃圾回收相关函数

    PHP中的垃圾回收相关函数 之前我们已经学习过 PHP 中的引用计数以及垃圾回收机制的概念.这些内容非常偏理论,也是非常常见的面试内容.而今天介绍的则是具体的关于垃圾回收的一些功能函数.关于之前的两篇 ...

  • [PHP小课堂]PHP垃圾回收机制的一些浅薄理解

    [PHP小课堂]PHP垃圾回收机制的一些浅薄理解 关注公众号:[硬核项目经理]获取最新文章 添加微信/QQ好友:[xiaoyuezigonggong/149844827]免费得PHP.项目管理学习资料 ...

  • [PHP小课堂]PHP中的垃圾回收相关函数

    [PHP小课堂]PHP中的垃圾回收相关函数 关注公众号:[硬核项目经理]获取最新文章 添加微信/QQ好友:[xiaoyuezigonggong/149844827]免费得PHP.项目管理学习资料 知乎 ...

  • .Net平台GC VS JVM垃圾回收

    前言 不知道你平时是否关注程序内存使用情况,我是关注的比较少,正好借着优化本地一个程序的空对比了一下.Net平台垃圾回收和jvm垃圾回收,顺便用dotMemory看了程序运行后的内存快照,生成内存快照 ...

  • 第 111 天:Python 垃圾回收机制

    众所周知,Python 是一门面向对象语言,在 Python 的世界一切皆对象.所以一切变量的本质都是对象的一个指针而已. Python 运行过程中会不停的创建各种变量,而这些变量是需要存储在内存中的 ...