Java-GC和JVM垃圾回收器

【JVM】GC基础知识、GC算法、JVM调优 - 笔记 JVM垃圾回收 3种垃圾回收算法 1、Mark-Sweep 缺点:碎片化 2、Copying 3、Mark-Compact 碎片整理,一边整理,一边压缩空间。缺点:效率低 10种垃圾回收器 分代模型 / 不分代模型 左面6种垃圾回收器都是配对使用,1.8默认PS+PO 分代模型 分为新生代、老年代 新生代使用拷贝算法:分为2种、3个区域(伊甸区、幸存区),直接把有用的对象从伊甸区拷贝到... 阅读详情

GC是什么

GC英文全称为Garbage Collection,即垃圾回收。
Java中的GC就是对内存的GC。
Java的内存管理实际上就是对象的管理,其中包括对象的分配和释放。
我们知道对象的分配,程序员可以通过new关键字,Class的new-Instance方法等来显示的分配;而对象的释放,程序员不能实时的进行释放,这就需要GC来完成

为什么要GC

  • 因为内存处理是编程人员容易出现问题的地方,忘记或者错误的内存回收会导致程序或系统的不稳定甚至崩溃。因此,Java提供了GC功能。该功能可以自动监测对象是否超过作用域从而判断是否需要回收内存
  • GC有效的防止了内存泄漏,可以有效的使用可以使用的内存
  • 对于GC来说,当程序员创建对象时,GC就开始监控这个对象的地址、大小以及使用情况。通常,GC采用有向图的方式记录和管理堆(heap)中的所有对象。通过这种方式确定哪些对象是"可达的",哪些对象是"不可达的".当GC确定一些对象为"不可达"时,GC就有责任回收这些内存空间
  • 注:判断对象"不可达",一般有两种方法,即引用计数法和可达性分析法

GC算法总体概述

JVM在进行GC时,并非每次都对上面三个内存区域一起回收的,大部分时候回收的都是指新生代

因此GC按照回收的区域又分了两种类型,一种是普通GC(minor GC),一种是全局GC(major GC or Full GC)

  • 普通GC(minor GC):只针对新生代区域的GC

  • 全局GC(major GC or Full GC):针对年老代的GC,偶尔伴随对新生代的GC以及对永久代的GC

引用计数法

在这里插入图片描述

复制算法(Copying)

年轻代中使用的是Minor GC,这种GC算法采用的是复制算法(Copying)。

  • 原理
    在这里插入图片描述
    Minor GC会把Eden中的所有活的对象都移到Survivor区域中,如果Survivor区中放不下,那么剩下的活的对象就被移到Old generation中,也即一旦收集后,Eden是就变成空的了

当对象在 Eden ( 包括一个 Survivor 区域,这里假设是 from 区域 ) 出生后,再经过一次 Minor GC 后,如果对象还存活,并且能够被另外一块 Survivor 区域所容纳( 上面已经假设为 from 区域,这里应为 to 区域,即 to 区域有足够的内存空间来存储 Eden 和 from 区域中存活的对象 ),则使用复制算法将这些仍然还存活的对象复制到另外一块 Survivor 区域 ( 即 to 区域 ) 中,然后清理所使用过的 Eden 以及 Survivor 区域 ( 即 from 区域 ),并且将这些对象的年龄设置为1,以后对象在 Survivor 区每熬过一次 Minor GC,就将对象的年龄 + 1,当对象的年龄达到某个值时 ( 默认是 15 岁,通过-XX:MaxTenuringThreshold 来设定参数),这些对象就会成为老年代

算法分析

年轻代中的GC,主要是复制算法(Copying)

HotSpot JVM把年轻代分为了三部分:1个Eden区和2个Survivor区(分别叫from和to)。默认比例为8:1:1,一般情况下,新创建的对象都会被分配到Eden区(一些大对象特殊处理),这些对象经过第一次Minor GC后,如果仍然存活,将会被移到Survivor区。对象在Survivor区中每熬过一次Minor GC,年龄就会增加1岁,当它的年龄增加到一定程度时,就会被移动到年老代中。因为年轻代中的对象基本都是朝生夕死的(90%以上),所以在年轻代的垃圾回收算法使用的是复制算法,复制算法的基本思想就是将内存分为两块,每次只用其中一块,当这一块内存用完,就将还活着的对象复制到另外一块上面。复制算法不会产生内存碎片

在这里插入图片描述

在GC开始的时候,对象只会存在于Eden区和名为“From”的Survivor区,Survivor区“To”是空的。紧接着进行GC,Eden区中所有存活的对象都会被复制到“To”,而在“From”区中,仍存活的对象会根据他们的年龄值来决定去向。年龄达到一定值(年龄阈值,可以通过-XX:MaxTenuringThreshold来设置)的对象会被移动到年老代中,没有达到阈值的对象会被复制到“To”区域。经过这次GC后,Eden区和From区已经被清空。这个时候,“From”和“To”会交换他们的角色,也就是新的“To”就是上次GC前的“From”,新的“From”就是上次GC前的“To”。不管怎样,都会保证名为To的Survivor区域是空的。Minor GC会一直重复这样的过程,直到“To”区被填满,“To”区被填满之后,会将所有对象移动到年老代中

在这里插入图片描述
因为Eden区对象一般存活率较低,一般的,使用两块10%的内存作为空闲和活动区间,而另外80%的内存,则是用来给新建对象分配内存的。一旦发生GC,将10%的from活动区间与另外80%中存活的eden对象转移到10%的to空闲区间,接下来,将之前90%的内存全部释放,以此类推

在这里插入图片描述

  • 劣势
    复制算法它的缺点也是相当明显的
  1. 它浪费了一半的内存,这太要命了
  2. 如果对象的存活率很高,我们可以极端一点,假设是100%存活,那么我们需要将所有对象都复制一遍,并将所有引用地址重置一遍。复制这一工作所花费的时间,在对象存活率达到一定程度时,将会变得不可忽视。 所以从以上描述不难看出,复制算法要想使用,最起码对象的存活率要非常低才行,而且最重要的是,我们必须要克服50%内存的浪费

标记清除(Mark-Sweep)

老年代一般是由标记清除或者是标记清除与标记整理的混合实现

  • 原理
    在这里插入图片描述

当堆中的有效内存空间(available memory)被耗尽的时候,就会停止整个程序(也被称为stop the world),然后进行两项工作,第一项则是标记,第二项则是清除

标记:从引用根节点开始标记所有被引用的对象。标记的过程其实就是遍历所有的GC Roots,然后将所有GC Roots可达的对象 标记为存活的对象

清除:遍历整个堆,把未标记的对象清除

缺点:此算法需要暂停整个应用,会产生内存碎片

用通俗的话解释一下标记/清除算法,就是当程序运行期间,若可以使用的内存被耗尽的时候,GC线程就会被触发并将程序暂停,随后将依旧存活的对象标记一遍,最终再将堆中所有没被标记的对象全部清除掉,接下来便让程序恢复运行

在这里插入图片描述

  • 劣势
  1. 首先,它的缺点就是效率比较低(递归与全堆对象遍历),而且在进行GC的时候,需要停止应用程序,这会导致用户体验非常差劲

  2. 其次,主要的缺点则是这种方式清理出来的空闲内存是不连续的,这点不难理解,我们的死亡对象都是随即的出现在内存的各个角落的,现在把它们清除之后,内存的布局自然会乱七八糟。而为了应付这一点,JVM就不得不维持一个内存的空闲列表,这又是一种开销。而且在分配数组对象的时候,寻找连续的内存空间会不太好找

标记清除(Mark-Sweep)

老年代一般是由标记清除或者是标记清除与标记整理的混合实现

  • 原理
    在这里插入图片描述

在整理压缩阶段,不再对标记的对象做回收,而是通过所有存活对象都向一端移动,然后直接清除边界以外的内存

可以看到,标记的存活对象将会被整理,按照内存地址依次排列,而未被标记的内存会被清理掉。如此一来,当我们需要给新对象分配内存时,JVM只需要持有一个内存的起始地址即可,这比维护一个空闲列表显然少了许多开销

标记/整理算法不仅可以弥补标记/清除算法当中,内存区域分散的缺点,也消除了复制算法当中,内存减半的高额代价

  • 劣势
    标记/整理算法唯一的缺点就是效率也不高,不仅要标记所有存活对象,还要整理所有存活对象的引用地址。从效率上来说,标记/整理算法要低于复制算法

标记清除压缩(Mark-Sweep-Compact)

标记清除、标记压缩的结合使用

  • 原理
    在这里插入图片描述
    在这里插入图片描述

算法总结

内存效率:复制算法>标记清除算法>标记整理算法(此处的效率只是简单的对比时间复杂度,实际情况不一定如此)

内存整齐度:复制算法=标记整理算法>标记清除算法

内存利用率:标记整理算法=标记清除算法>复制算法

可以看出,效率上来说,复制算法是当之无愧的老大,但是却浪费了太多内存,而为了尽量兼顾上面所提到的三个指标,标记/整理算法相对来说更平滑一些,但效率上依然不尽如人意,它比复制算法多了一个标记的阶段,又比标记/清除多了一个整理内存的过程

没有最好的算法,只有最合适的算法

分代收集算法。

  • 年轻代(Young Gen)

    • 年轻代特点是区域相对老年代较小,对像存活率低。

    • 这种情况复制算法的回收整理,速度是最快的。复制算法的效率只和当前存活对像大小有关,因而很适用于年轻代的回收。而复制算法内存利用率不高的问题,通过hotspot中的两个survivor的设计得到缓解。

  • 老年代(Tenure Gen)

    • 老年代的特点是区域较大,对像存活率高

    • 这种情况,存在大量存活率高的对像,复制算法明显变得不合适。一般是由标记清除或者是标记清除与标记整理的混合实现

    • Mark阶段的开销与存活对像的数量成正比,这点上说来,对于老年代,标记清除或者标记整理有一些不符,但可以通过多核/线程利用,对并发、并行的形式提标记效率

    • Sweep阶段的开销与所管理区域的大小形正相关,但Sweep“就地处决”的特点,回收的过程没有对像的移动。使其相对其它有对像移动步骤的回收算法,仍然是效率最好的。但是需要解决内存碎片问题

    • Compact阶段的开销与存活对像的数据成开比,如上一条所描述,对于大量对像的移动是很大开销的,做为老年代的第一选择并不合适

    • 基于上面的考虑,老年代一般是由标记清除或者是标记清除与标记整理的混合实现。以hotspot中的CMS回收器为例,CMS是基于Mark-Sweep实现的,对于对像的回收效率很高,而对于碎片问题,CMS采用基于Mark-Compact算法的Serial Old回收器做为补偿措施:当内存回收不佳(碎片导致的Concurrent Mode Failure时),将采用Serial Old执行Full GC以达到对老年代内存的整理

垃圾回收器

垃圾回收器分类

  • 按线程数分,可以分为串行垃圾回收器和并行垃圾回收器
    在这里插入图片描述
    串行垃圾回收: 指的是在同一个时间段内只允许有一个CPU用于执行垃圾回收操作,此时工作线程被暂停,直至垃圾收集工作结束

并行垃圾回收: 是和串行垃圾回收相反的,并行运算可以拥有多个CPU同时执行垃圾回收,因此提升了应用的吞吐量,不过并行回收仍然与串行回收一样,采用独占式,使用“Stop The World”的机制。在回收的时候,需要暂停所有的线程

  • 按照工作模式分,可以分为并发式垃圾回收器和独占式回收器
    在这里插入图片描述

并发式垃圾回收器: 与应用程序交替工作,以尽可能减少应用程序的停顿时间。

独占式垃圾回收器: 就是Stop - The - World。一旦运行,就停止应用程序中的所有用户线程,直到垃圾回收过程完全结束。

  • 按照工作的内存区间,又可分为年轻代垃圾回收器和老年代垃圾回收器
    在这里插入图片描述
    年轻代垃圾回收器: 效率高,采用复制算法,但对内存的占用控制不精确,容易造成内存溢出。

老年代垃圾回收器: 效率低,在执行的时候,会STW,但一般启动次数少。

GC的性能指标

  • 吞吐量:运行用户代码的时间栈总运行时间的比例。
    (总运行时间:程序的运行时间+内存回收时间)

  • 垃圾收集开销:垃圾收集所用时间与总运行时间的比例。

  • 暂停时间:执行垃圾收集时,程序的工作线程被暂停的时间。

  • 收集频率:相对于应用程序的执行,收集操作发生的频率。

  • 内存占用:Java堆区所占的内存大小。

  • 快速:一个对象从诞生到被回收所经历的时间。

HotSpot虚拟机中的垃圾收集器

因为HotSpot的虚拟机是现在主流的商用虚拟机,也是非常经典的一款虚拟机所以在这我就自己学了一下这款虚拟机的垃圾收集。

这些经典的收集器尽管已经算不上最先进的技术,但它们曾在实践中千锤百炼,足够成熟,基本上可认为是现在能够在商用生产环境中放心使用的全部垃圾收集器了。
各款经典收集器之间的关系如图。

在这里插入图片描述
如果两个收集器中间存在连线,就说明他们可以搭配使用。图中收集器所处的区域,则表示它是属于新生代收集器或者老年代收集器。

串行回收器:Serial,Serial old;
并行回收器:ParNew,Parallel scavenge,Parallel old;
并发回收器:CMS、G1;

新生代收集器:Serial,ParNew,Parallel scavenge;
老年代收集器:Serial old,Parallel old.cMS;
整堆收集器:G1;

虽然我们会对各个收集器进行比较,但并非为了挑选一个最好的收集器出来,虽然垃圾收集器的技术在不断进步,但是直到现在还没有最好的收集器出来,更加不存在“万能”的收集器,所以我们选择的只是对具体应用最合适的收集器。
如果有一种放之四海,任何场景都使用的完美收集器存在,那么HotSpot虚拟机就完全没有必要实现那么多种不同的收集器了

在说收集器的时候,会接触到“并发”和“并行”
并行: (Parallel)描述的是多条垃圾收集器线程之间的关系。

说明同一时间有多条这样的线程协同工作,通常默认此时用户线程是处于等待状态。

并发: (Concurrent): 并发描述的是垃圾收集器线程与用户线程之间的关系。

说明在同一时间垃圾收集器线程与用户线程都在运行。由于用户线程并非被冻结,所以程序仍然是能够响应服务请求的,但由于垃圾收集器线程占有了一部分的系统资源,此时应用程序的处理的吞吐量将会受到一定的影响。

Serial 垃圾收集器(单线程)

Serial收集器是最基础、历史最悠久的收集器,曾经(在JDK1.3.1之前)是HotSpot虚拟机新生代收集器的唯一选择。它是一个“单线程”的收集器,但是它的“单线程”的意义并不仅仅是说明它只会使用一个处理器或一条收集线程去完成垃圾收集工作,更重要的是强调在它进行垃圾收集的时候,必须停止其他所有的工作线程,直到结束。

运行示意图:
在这里插入图片描述

特点:单线程,简单高效,采用的是复制算法,对于单CPU的环境来说,Serial收集器由于没有线程交互的开销,专心做垃圾收集器自然可以获得最高的单线程收集效率。收集器进行垃圾回收时,必须暂停其他所有的线程(Stop The World),直到它结束。

虽然它是最早产生的,但是它与其他收集器的单线程相比是最简单而高效的。所以在用户桌面的应用场景以及近几年来流行的部分微服务器应用中是一个很好的选择

应用场景:适用Client模式下的虚拟机。

Serial Old 垃圾收集器(单线程)

Serial Old是Serial收集器的老年代的版本,单线程,使用标记 - 整理算法。

应用场景:主要也是使用在 Client 模式下的虚拟机中。也可在 Server 模式下使用。

ParNew 垃圾收集器

ParNew收集器实质上是Serial收集器的多线程版本,除了同时使用多线程进行垃圾收集之外,其余的行为包括Serial收集器可用的所有控制参数(例如: -XX:SurvivorRatio、-XX:PretenureSizeThreshold、-XX:HandlePromotionFailure等)、收集算法、STW、对象分配规则、回收策略等都与Serial收集器完全一致,在实现上这两种收集器也共用了相当多的代码

在这里插入图片描述
特点:多线程、ParNew收集器默认开启的收集线程数与CPU的数量相同,在CPU非常多的环境中,可用使用 -XX:ParallelGCThreads参数来限制垃圾收集的线程。和Serial收集器一样存在Stop The World 问题。

应用场景:ParNew收集器是许多运行在Server模式下的虚拟机中首选的新生代收集器,因为它是除了Serial收集器外,唯一一个能与CMS收集器配合工作的。

Parallel Scavenge 垃圾收集器

Parallel Scavenge 收集器也是一款新生代的收集器,它同样是基于 标记 - 复制算法实现的收集器,也是能够并行收集的多线程收集器

特点: 它的关注点与其他收集器是不同的,CMS等收集器的关注点是尽肯地缩短垃圾收集时用户线程的停顿使劲,而Parallel Scavenge收集器的目标则是达到一个可控制的吞吐量(Throughput)。

所谓吞吐量就是处理器用于远程用户代码的时间与处理器总消耗时间的比值,即:

在这里插入图片描述
高吞吐量则可以最高效率地利用处理器资源,尽快地完成程序的运算任务,主要适合在后台运算而不需要太多交互的分许任务。

Parallel Scavenge收集器提供了两个参数用于精确控制吞吐量,分别是控制最大垃圾收集停顿时间的 -XX:MaxGCPauseMillis参数以及直接设置吞吐量大小的 -XX:GCTimeRatio参数。

-XX:MaxGCPauseMillis参数允许的值是一个大于0的毫秒数,收集器将尽力保证内存回收花费的时间不超过用户设定的值。(但是设置太小的话,停顿时间的确在下降,但吞吐量也降下来了)

-XX:GCTimeRatio参数的值为一个正整数,表示用户期望虚拟机消耗在GC上的时间不超过程序运行时间的 1/(1+N)。默认值为99.含义是尽可能保证应用程序执行的时间为收集器执行时间的99倍,也即收集器的时间消耗不超过总运行时间的1%。

由于与吞吐量关系密切,Parallel Scavenge收集器也经常被称作“吞吐量优先收集器”

Parallel Old垃圾收集器

是Parallel Scavenge收集器的老年版本。

特点:支持多线程并发,采用标记-整理算法。

在这里插入图片描述
这个收集器是在JDK 6时才开始提供的,在此之前,新生代的Parallel Scavenge收集器一直都处于一个相当尴尬的场面,原因是如果新生代选择了Parallel Acavenge收集器,老年代除了Serial Old收集器以外别无选择,其他表现良好的老年代收集器,比如CMS是无法配合它工作的。由于老年代Serial Old的“拖累”,使得Sarallel Scavenge收集器是没有办法在整体上获得吞吐量最大化的效果。

直到Parallel Old 收集器出现后,“吞吐量优先”收集器终于有了比较名副其实的搭配组合。

应用场景:在注重吞吐量或者处理器资源较为稀缺的场合。都可以优先考虑Parallel Scavenge加Parallel Old收集器这个组合

CMS垃圾收集器

CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。目前很大一部分的Java应用集中在互联网或者基于浏览器的B/S系统的服务端上,这类应用通常都会较为关注服务的响应速度,希望系统停顿时间尽可能短,以给用户带来良好的交互体验。CMS收集器就非常符合这类应用的需求。

它的运作过程相对于前面几种收集器来说要更为复杂一些,整个过程分为四个步骤:

  • 初始标记(CMS initial mark)

    • 这个阶段仅仅只是标记一下GC Roots能直接关联到的对象,速度快。
  • 并发标记(CMS concurrent mark)

    • 这个阶段就是从GC Roots的直接关系对象开始遍历整个对象图的过程,这个过程耗时较长但是不需要停顿用户线程,可以与垃圾收集线程一起并发运行
  • 重新标记(CMS remark)

    • 这个阶段则是为了修正并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间通常会比初始标记阶段稍长一些,但也远比并发阶段的时间短。
  • 并发清除(CMS concurrent sweep)

    • 这个阶段,就是清理删除掉标记阶段判断的已经死亡的对象,由于不需要移动存活对象,所以这个阶段也是可以与用户线程同时并发的。

由于在整个过程中耗时最长的并发标记和并行清除阶段中,垃圾收集器线程都可以与用户线程一起工作,所以在总体上来看,CMS收集器的内存回收过程是与线程一起并行执行的。

在这里插入图片描述
优点:并发收集、低停顿。

缺点:

  • 对处理器资源非常敏感。

    • 在并发阶段虽然不会导致用户线程停顿,但却会因为占用一部分线程而导致应用程序变慢,降低总吞吐量。
  • 无法处理“浮动垃圾”。

    • 因为无法处理“浮动垃圾”,就有可能出现“Concurrent Mode Failure”失败而导致另一次完全“Stop The Stop”的Full GC的产生。
      在CMS的并发标记和并发清理阶段,用户线程还是继续运行的,程序在运行自然就还有伴随着有新的垃圾对象不断产生,但这一部分垃圾对象是出现在标记过程以后的,CMS无法在当次收集中处理掉它们,就只好期待下一次垃圾收集时再清理掉。这就是“浮动垃圾”。
  • 会产生内存碎片。

    • 因为CMS是基于“标记 - 清除”算法来实现的收集器。从垃圾回收中就了解到垃圾回收中的“标记 - 清除”算法就会产生内存碎片。内存碎片过多的话,就会导致给大对象分配带来了很大的麻烦。就会不得不提前触发Full GC。
三色标记算法

为了提高JVM的垃圾回收性能,从CMS垃圾收集器开始,引入了并发标记的感念。引入并发标记的过程就会带来一个问题,在业务执行的过程中,会对现有的引用关系链出现改变。

三色标记中的三色:
黑色:表示该对象已经被标记过了,且该对象下的属性也全部都被标记过了,例如:GCRoots对象
灰色:对象已经被垃圾收集器扫描过了,但是对象中还存在没有扫描的引用(GC需要从此对象中去寻找垃圾)
白色:表示该对象没有被垃圾收集器访问过,即表示不可达。

三色标记的过程

1.初始时,全部对象都是白色的
2. GC Roots直接引用的对象变为灰色
3. 从灰色集合中获取元素;将本对象直接引用的对象标记为灰色;然后将当前的对象标记为黑色。
4. 重复步骤3,直到灰色的对象集合全部变为空
5. 结束后,仍然被标记为白色的对象就是不可达对象,就视为垃圾对象。

当Stop The Word时,对象间的引用是不会发生变化的,因为此时用户线程是中断的,可以轻松完成标记。但是在并发标记的时候,标记期间用户线程还在跑,对象间的引用可能发生变化,多标和漏标的情况就可能会发生。

多标(又叫浮动垃圾)

看图,A为GC Roots的对象,然后向下进行遍历。把A标记为黑色,将B和C标记为灰色。

在这里插入图片描述
此时A取消了对B的引用。 A->B = null

在这里插入图片描述
这个时候A->B之间的引用没有了,B应该为白色,但是因为之前在对A的时候,已经把D标为灰色了了,所以B对象任然会被当做存活对象遍历下去。
最终结果:这部分对象仍然会被标记为存活对象,本轮GC是不会回收他们的内存。这部分因为并发而造成的本应该回收但是没有回收的对象就称为“浮动垃圾”,我们稍微一想,浮动垃圾是不会影响应用程序的重要性,只需要等到下一轮GC到来就会被回收了。

另外一点: 针对并发标记开始后产生的新对象,通常做法是直接标记为黑色,本轮不进行清除。即使这些对象会变成垃圾对象,这也算浮动垃圾的一部分。

漏标(错杀)

假设GC线程遍历到了B,发生了以下操作:

在这里插入图片描述
断开B和D之间的引用关系,新增了A和D之间的引用关系了。

此时B到D之间的引用消失,A生成了新的对D的引用。但是此时GC线程已经走过了A,因为B已经没有了对D的引用,所以不会遍历到D,D也就不会标志为灰色,同时A已经标志为黑色了,不会再被遍历,那么也就导致D一直是白色的,最后被当成垃圾处理,这显然与事实不符。这是绝对不被允许的。

这个问题是比较致命的,如果错杀了,就会出现运行结果不符合预期的情况。这个是绝对不能发生的。

解决漏标(错杀)问题

先分析一下发生漏标的具体原因有二:

  • 灰色指向白色的引用全部断开(在这就行一个原始快照)
  • 黑色指向白色的引用被建立(增量更新)

那么仔细想一想,只要打破上面任何一个问题,就可以解决漏标(错杀)的问题。

写屏障:

  1. 写屏障 + SATB

    • 当对象B的引用发生变化时,利用写屏障,将B原来的引用对象记录下来,这样可以尝试保留开始时的对象图,保证标记依然按照原本的路线走
  2. 写屏障 + 增量更新

    • 当对象A的引用发生变化时,利用写屏障,将A新的引用对象D记录下来
      即当有新的引用插入进来时,记录下新的引用
    • 这种思路不要求保留原始对象图,而是针对新的引用记录下来等待遍历,即增量更新

读屏障:
读屏障针对第一步,当读取引用对象的时候,一律记录下来,显然这种方法非常保守,但是安全。

将记录下的再引用遍历就是了。

在现代的垃圾回收器当中可达性分析算法的垃圾回收器几乎都借鉴了三色标记法的思想

在Java HotSpot VM中
CMS采用的是:写屏障 + 增量更新
G1采用的是:写屏障 + SATB

G1(Garbage First)收集器

Garbage First(简称G1)收集器是垃圾收集器计数发展历史上的里程碑式的成果。G1垃圾回收器是在Java7 update 4 之后引入的一个新的垃圾回收器,是当今收集器计数发展的最前沿成果。
它开创了收集器面向局部收集的设计思路和基于Region的内存布局形式。是一款面向服务端应用的垃圾收集器。

因为在后世发展中,所对应的业务越来越庞大、复杂、用户越来越多,没有GC就不能保证应用程序正常进行,而经常造成STW的GC又跟不上实际的需求,所以才会不断地尝试对GC进行优化。

与此同时,为了适应现在不断扩大的内存和不断增加的处理器数量,进一步降低暂停时间(pause time),同时为了兼顾良好的吞吐量。
官方给G1设定的目标就是在延迟可控的情况下获得尽可能高的吞吐量,所以才会担当起“全功能收集器”的重任与期望。

虽然G1任然保留新生代和老年代的概念,但新生代和老年代不再是固定的了,它们都是一系列区域(不需要连续)的动态集合。G1收集器之所以能建立可预测的停顿时间模型,是因为它将Region作为单词回收的最小单位,即每次收集到的内存空间都是Region大小的整数倍,这样可以有计划地避免在整个Java堆中进行全区域的垃圾收集。
在Region中还有一类特殊的Humongous区域,专门用来存储大对象。G1认为大小超过一个Region容量的一半的对象即可以判定为大对象。而对于那些超过整个Region容量的超级大对象,就会被存放在N个连续的Humongous Region之中,G1的大多数行为都把Humongous Region作为老年代的一部分来进行看待。

这种使用Region划分内存空间,以及具有优先级的区域回收方式,保证了G1收集器在有限的时间内获取尽可能高的收集效率。

在这里插入图片描述
因为 G1 是一个并行回收器,它把堆内存分割为很多不相关的区域(Region)、(物理上不连续的)。使用不同的 Region 来表示 Eden、幸存者0区,幸存者1区,老年代等。

G1 GC有计划地避免在整个Java堆中进行全区域的垃圾收集。G1跟踪各个 Region里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的 Region。

由于这种方式的侧重点在于回收垃圾最大量的区间(Region),所以我们给G1还有一个名字:垃圾优先(Garbage First)。

G1(Garbage-First)是一款面向服务端应用的垃圾收集器,主要针对配备现在的多核 CPU 及大容量内存的机器,以极高概率满足GC停顿时间的同时,还兼具高吞吐量的性能特征。

G1收集器可以 “ 建立可预测的停顿时间模型 ”,它维护了一个列表用于记录每个 Region 回收的价值大小(回收后获得的空间大小以及回收所需时间的经验值),这样可以保证G1收集器在有限的时间内可以获得最大的回收效率。

如果我们不去计算用户线程运行过程中的动作(比如使用写屏障维护记忆集的操作),G1收集器的运行过程大致可划分为以及4个步骤

  1. 初始标记(Initial Marking)
    仅仅只是标记一下GC Roots能直接关联到的对象,并且修改TAMS指针的值,让下一个阶段用户线程并发运行的时候,能正确地在可用的Region中分配新对象。
    这个阶段需要停顿线程,但耗时很短,而且是借用进行Minor GC的时候同步完成时,所以G1收集器在这个阶段实际并没有额外的停顿。

  2. 并发标记(Concurrent Marking)
    从GC Root开始对堆中的对象进行可达性分析,递归扫描整个堆里的对象图,找出要回收的对象,这阶段耗时较长,但可与用户程序并发执行。
    当对象图扫描完成之后,还有重新处理SATB记录下的并发时有引用变动的对象。

  3. 最终标记(Final Marking)
    对用户线程做另一个短暂的暂停,用于处理并发阶段结束后人遗留下的最后那少量的SATE记录。

  4. 筛选回收(Live Data Counting and Evacuation)
    负责更新Region的统计数据,对各个Region的回收价值和成本进行排序,根据用户所期望的停顿时间来制定回收计划,可以自由选择任意多个Region构成回收集,然后把决定回收那一部分Region的存活对象复制到空的Region中,再清理掉整个旧的Region的全部空间。这里的操作涉及存回对象的移动,是必须暂停用户线程,由多条收集器线程并行完成的

从上述阶段完全可以看出,G1收集器除了并发标记以外,其余阶段也要完全暂停用户线程的,换言之,它并非纯粹地追求低延迟,官方给它设立的目标是在延迟可控的情况下获得尽可能高的吞吐量,所以才坦当器“全功能收集器”的重任和期望。

在这里插入图片描述
适用场景:
要求尽可能可控GC停顿时间;
内存占用较大的应用。可以用 -XX:+UseG1GC 使用 G1 收集器
jdk9 默认使用 G1 收集器。

基于LTspice的共模电感差模/共模插入损耗仿真指南 在电源与EMC设计领域,插入损耗是衡量滤波器件抑制噪声能力的核心指标。共模电感作为EMI滤波器中最常见的元器件之一,其差模与共模插入损耗特性直接影响整体滤波方案的合规性。然而,仅依赖规格书曲线往往难以覆盖真实应用中的阻抗条件,借助SPICE仿真工具进行电路级验证已成为工程师常用的手段。LTspice凭借其轻量、高效的交流分析能力,能够帮助设计者快速搭建共模电感等效模型,并通过调整耦合系数、寄生电容等参数,还原漏感与谐振行为。本文以Wurth Elektronik的WE 744821201型号为例,从基本测试 阅读详情

相关推荐

从光斑图到锤形优化:一次搞定Zemax双胶合透镜的像散与场曲问题

本文详细解析了Zemax双胶合透镜设计中像散与场曲问题的诊断与优化方法。从光斑图分析到锤形优化技巧,提供了完整的光学设计实战指南,帮助设计师快速定位问题并有效改善系统性能,特别适合光学设计工程师和Zemax使用者参考。

weixin_27134495的博客 231

JVM 垃圾回收机制:GC

JVM的垃圾回收机制:GC,是Java提供的对于内存自动回收的机制。

m0_60462557的博客 2696

告别迷茫!手把手教你用Altova XMLSpy编写EtherCAT从站ESI文件(附避坑指南)

本文详细介绍了如何使用Altova XMLSpy高效编写EtherCAT从站ESI文件,涵盖核心架构、通信通道配置、过程数据映射等关键步骤,并提供了常见问题排查与性能优化建议。通过实战案例和避坑指南,帮助工程师快速掌握EtherCAT从站开发技巧,提升工作效率。

weixin_42570484的博客 395

Java JVM 垃圾回收器(四):现代垃圾回收器 之 Shenandoah GC

CMS 之前:Serial GC、Parallel GC,称为“传统”或“经典”垃圾回收器,特点是全停顿、简单高效但不适合低延迟场景。CMS 及以后:CMS、G1、ZGC、Shenandoah,称为“现代垃圾回收器”,特点是并发、低停顿、适合大堆和多核环境。本质区别现代垃圾回收器的目标是降低应用停顿时间,提高响应性和可扩展性,而传统垃圾回收器更关注实现简单和吞吐量。是 Red Hat 主导开发的 OpenJDK 低延迟垃圾回收器,目标是在任意堆大小下将 GC 停顿时间降到最低。

2401_89221445的博客 1096

最详细的Cadence OrCAD Capture自带元件库的介绍

最详细的Cadence OrCAD Capture自带元件库的介绍 本章节将介绍Cadence OrCAD Capture 软件自带的各类元件库的分类,方便大家在设计时能够快速的选择元件。 AMPLIFIER.OLB 共182个零件,存放模拟放大器,IC,如LM386,MAX457等。 ARITHMETIC.OLB 共182个零件...

LGCPCB的博客 6万+

若依(ruoyivue)实现文档在线查看

1、Kkfileview部署 第一步:从kkfileview官方发行版本下载安装包,安装包下载路径: https://gitee.com/kekingcn/file-online-preview/releases, 第二步:解压kkFileView-x.x.x文件(Windows用.zip包,Linux/MacOS用.tar.gz包) 第三步:打开解压后文件夹的bin目录,运行startup脚本(Windows下以管理员身份运行startup.bat,Linux以root用户运行startup.sh) 第四

weixin_46953697的博客 9680

【JVM GC核心参数】JVM原理,JAVA 各种垃圾回收器的核心参数,JAVA GC回收器参数配置,核心参数说明

【JVM GC核心参数】JVM原理,JAVA 各种垃圾回收器的核心参数,JAVA GC回收器参数配置,核心参数说明。

xzb5566的专栏 1331

Livox-avia+FAST-LIO麻瓜使用教程

激光雷达 SLAM Fastlio 安装

aaaaaaaaaww12的博客 3675

Java-JVM-架构-垃圾回收器(GC)

综上所述,通过选择合适的垃圾收集器、调整堆内存大小、优化对象分配和回收、使用并发垃圾收集器、调整垃圾收集器的参数以及使用分代回收等方法,可以有效地减少垃圾回收的停顿时间,提高应用程序的性能和响应性。举例来说,当一个对象被分配了内存空间,但在使用完毕后,没有被及时释放,即使该对象不再被程序所需要,但由于某些引用仍然存在,垃圾回收器无法识别该对象为垃圾,因此无法回收其占用的内存空间,导致内存泄漏。总的来说,Java 的垃圾回收机制通过追踪对象的引用关系,识别并释放不再被引用的对象,从而实现自动的内存管理。

qq_32186487的博客 1928

Qwen-Image(阿里通义千问)技术浅析(一)

Qwen-Image(阿里通义千问多模态模型)是阿里巴巴推出的视觉-语言多模态大模型,能够理解图像内容并完成复杂的跨模态任务。

m0_75253143的博客 676

AI智算-K8s+vLLM & Ray:DeepSeek-r1 671B 满血版分布式推理部署实践

自从上次发布 【AI-智算】K8s+SGLang实战:DeepSeek-r1:671b满血版多机多卡私有化部署全攻略] 文章后,本次将演示另一个主流推理引擎工具——vLLM,结合K8s、LWS、Ray、Volcano等技术栈,部署DeepSeek-r1 671b 满血版分布式推理集群,并对比其与SGLang推理引擎的性能表现。

Kason_iWiki 1824

Java虚拟机 JVM的GC总结之垃圾回收器

一.Hotspot虚拟机所包含的收集器类型 新生代: Serial收集器 ParNew收集器 Parallel Scavenge收集器 老年代: CMS收集器 Serial Old 收集器 Parallel Old收集器 整堆收集器: G1收集器 Java 虚拟机的垃圾收集器可以分为四大类别:串行收集器、并行收集器、CMS 收集器、G1收集器。 二.Serial串行...

atomatom98的博客 302

软件设计师---程序设计语言

软件设计师---程序设计语言

我从不打没有准备的仗! 2万+

java如何强制gc立即回收,JVM之GC算法的实现(垃圾回收器),

JVM之GC算法的实现(垃圾回收器),上一节:《JVM之GC算法》知道GC算法的理论基础,我们来看看具体的实现。只有落地的理论,才是真理。一、JVM垃圾回收器的结构JVM虚拟机规范对垃圾收集器应该如何实现没有规定,因为没有最好的垃圾收集器,只有最适合的场景。图中展示了7种作用于不同分代的收集器,如果两个收集器之间存在连线,则说明它们可以搭配使用。虚拟机所处的区域则表示它是属于新生代还是老年代收集...

weixin_42394100的博客 2万+

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

迈瑞BS系列全自动生化分析仪LIS协议接口手册(内含HL7和ASTM)

迈瑞BS系列全自动生化分析仪LIS协议接口手册,内含HL7和ASTM)

Java:从GC底层原理到JVM经典垃圾回收器

简单总结JVM的几种垃圾回收器 虽然一直都在用C++很久没碰Java了,但还是心血来潮决定翻一下《深入理解Java虚拟机》,回味并总结一下Java Virtual Machine的垃圾回收机制。毕竟垃圾回收的核心思想是跨越语言的。 基础知识 Java内存区域分为五大部分,其中方法区和堆区是所有线程共享的;虚拟机栈、本地方法栈和程序计数器是每个线程独占一份的。下面简单概括下: 方法区:储存常量、静态变量、类型信息等缓存。JDK8之前常被称为(误称)永久代,因为方法区难以回收,管理机制类似于永久代。当然JD

keke博客 341

pscad IEEE标准系统搭建说明

pscad IEEE标准系统搭建说明,包含典型的IEEE 9节点、14节点、30节点、39节点和118节点系统。内含标准测试系统的原始数据和潮流结果。

Java进阶(垃圾回收GC)——理论篇:JVM内存模型 & 垃圾回收定位清除算法 & JVM中的垃圾回收器

本篇博客介绍JVM的内存模型,对比了1.7和1.8的内存模型的变化;介绍了垃圾回收的语言发展;阐述了定位垃圾的方法,引用计数法和可达性分析发以及垃圾清除算法;然后介绍了Java中的垃圾回收器,由串行、到并行再到并发,最后到G1的演变;最后给出了垃圾回收器的对比和使用指引。

Arya的博客,专注后端领域 9486

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

Java JVM基础:一篇文章彻底搞懂GC(JVM运行时数据区、GC回收算法、垃圾回收器)

Java相较于其他编程语言更加容易学习,这其中很大一部分原因要归功于JVM的自动内存管理机制。对于从事C语言的开发者来说,他们拥有每一个对象的「所有权」,更大的权力也意味着更多的职责,C开发者需要维护每一个对象「从生到死」的过程,当对象废弃不用时必须手动释放其内存,否则就会发生内存泄漏。而对于Java开发者来说,JVM的自动内存管理机制解决了这个让人头疼的问题,不容易出现内存泄漏和内存溢出的问题了,GC让开发者更加专注于程序本身,而不用去关心内存何时分配、何时回收、以及如何回收。

学亮编程手记 2543

深入 JVM 入门核心:垃圾回收器(GC)原理、算法与实战调优全解析(Java 实习生必修课)

垃圾回收器是 JVM 自动内存管理的核心引擎。理解其原理与调优方法,是 Java 开发者从“功能实现”走向“性能卓越”的必经之路。GC 判定原理:可达性分析与 GC Roots;四大回收算法:标记-清除、复制、标记-整理、分代收集;主流回收器演进:从 Serial 到 ZGC,适应不同场景需求;实战调优技巧:日志分析、工具使用、常见问题解决;生产环境实践:框架默认策略与大厂经验。最后寄语不要满足于“程序不崩”,而要追求“流畅如丝”。从今天起,开启 GC 日志,

吾为热爱计科,致力于知识分享的践行者,此博客是我在这条道路上留下的足迹。 998

java_opts gc回收器_JVM之垃圾回收机制(GC)

JVM之垃圾回收机制全解(GC)文章底部有思维导图,较为清晰,可参考导读:垃圾回收是Java体系中最重要的组成部分之一,其提供了一套全自动的内存管理方案,要想掌握这套管理方案,就必须了解垃圾回收器的工作原理。本文介绍了垃圾回收的概念,算法,垃圾回收器及我在工作中遇到的一些关于GC的优化实例。先来简单了解下JVM:-------------------------------------------...

weixin_28331555的博客 393

jdk8默认垃圾回收器_Java虚拟机(JVM)参数和垃圾回收器(GC)

手头有个项目,还在测试阶段,运行了也有将近半年时间了,一直也没去优化JVM配置,这几天呢,正好有空,就准备处理下,也把一些需要的参数整理下,已备后用。JVM参数分类"-"开头的参数:标准参数,所有的JVM实现都必须实现这些参数的功能,而且向后兼容。"-X"开头的参数:非标准参数,也就是只能被部分VM识别,而不能被全部VM识别的参数。"-XX"开头的参数:非稳定参数,随时可能被修改或者移除。标准参...

weixin_39779739的博客 955

Java垃圾回收机制(GC)详解----垃圾回收GC(Garbage Collection)是Java语言的核心技术之一,之前我们曾专门探讨过Java 7新增的垃圾回收器G1的新特性,但在JVM的内部

aukawa的专栏 446

JVM | 垃圾回收器(GC)- Java内存管理的守护者

为了满足不同应用场景的需求,JVM提供了多种垃圾回收器。每种回收器都有其特点和使用场景。接下来,我们将深入了解几种主流的垃圾回收器。Java的垃圾回收器在确保应用性能和稳定性方面发挥了至关重要的作用。从手动管理到自动化管理,内存处理在计算机科学的发展过程中已经走过了漫长的道路。今天,通过JVM的自动垃圾回收机制,开发者可以集中精力编写更高效的代码,而不是手动管理内存。通过我们的讨论,我们了解到了垃圾回收的工作原理、常见的垃圾回收算法、以及如何选择和配置合适的垃圾回收器。

StudyBrainEx 904

JVM调优预备知识:Java GC算法介绍和垃圾回收器

计数器算法&可达性分析 垃圾回收器介绍&1.8自带的PSPO 垃圾回收算法

qq_31564573的博客 505

Java进阶(垃圾回收GC)——理论篇:JVM内存模型 & 垃圾回收定位清除算法 & JVM中的垃圾回收器_java的内存模型以及gc算法

Young区(年轻代区):Young区被划分为三部分,Eden区和两个大小严格相同的Survivor区,其中,Survivor区间中,某一时刻只有其中一个是被使用的,另外一个留做垃圾收集时复制对象用,在Eden区间变满的时候, GC就会将存活的对象移到空闲的Survivor区间中,根据JVM的策略,在经过几次垃圾收集后,任然存活于Survivor的对象将被移动到Tenured区间。通过标记清除算法清理出来的内存,碎片化较为严重,因为被回收的对象可能存在于内存的各个角落,所以清理出来的内存是不连贯的。

2401_84563561的博客 853

JAVA的平凡之路——此峰乃是最高峰JVM-GC垃圾回收器(2)-06

m0、m1都是标记位,只是根据周期不同进行使用,并且根据周期不同还会出现00、01、10、11等特殊含义,1为标记,0为不标记,标记为不回收,cms里面就有一个标记重置的处理,所以为了减少标记位重置的消耗,使用两个标记位交替使用。对象指针在局部变量表中,64位的指针的高位进行对象状态存储,原有gc状态是保存在内存中的对象头中,所以减少了再根据指针查询对象的操作,降低消耗。Serial 对应的老年代回收器,使用的标记整理,单线程老年代回收器。最早的gc回收器,年轻代,使用的是标记复制,单线程年轻代回收器。

qq_37711237的博客 448

一文了解JVM的垃圾回收

当进行垃圾回收的时候,会暂停所有线程去完成垃圾回收。Minor GC :发生在新生代的垃圾回收,当eden区满了会触发,暂停时间较短。Full GC:对新生代和老年代进行完整的垃圾回收,当老年代内存不足时会触发,暂停时间长,要尽量避免。

keygwh的博客 2988
上一篇: Java分析-对象头
下一篇: Java-Jstack-生产问题的排查死锁/泄露/cpu负载
-乌鸦
博客等级 码龄6年 3225粉丝 66原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值