文章目录

  • 一、内存分配策略
    • 1、引用计数算法
    • 2、可达性分析算法
    • 3、标记清除算法
    • 4、标记复制算法
    • 5、标记整理算法
  • 二、收集器
    • 1.Serial收集器
    • 2.ParNew收集器
    • 3.Parallel Scavenge收集器
    • 4.Serial Old收集器
    • 5、Paralle Old收集器
    • 6、CMS收集器
    • 7、G1收集器
    • 8、Shenandoah收集器
    • 8、ZGC收集器
  • 三、总结


一、内存分配策略

1、引用计数算法

在对象中添加一个引用计数器,有地方引用它时,计数器值就加一;引用失效时,计数器值就减一;任何适合当计数器为零的对象就是不可能被使用的。弊端:很难解决对象之间的相互循环引用问题。如果采用的是计数器算法,下图中的堆应该不会回收,因为它们相互引用,尽管对象的值已经null,然而结果却是回收了,可证明两点:一是JVM内部使用的不是引用计数算法来判断对象存活。二是不管有无相互引用,只要是这两个对象为null无任何对象指向它就会被当做垃圾清理掉。

2、可达性分析算法

如果某个对象到GC Roots之间没有任何引用链相连,则为不可达即表明不能再被使用,将会被当做垃圾回收。如下图的object到GC Root是不可达的将会被回收。

在Java中,固定作为GC Roots的对象包含一下几种。在虚拟机栈(栈帧中的本地变量表)中引用的对象,各个线程被调用的方法堆栈中使用到的参数、局部变量表、临时变量等。在方法区中类静态属性引用的对象,比如Java类的引用类型静态变量。在方法区中常量池引用的对象,比如字符串常量池里的引用。本地方法栈中JNI(Native)方法引用的对象。Java虚拟机内部的引用,如基本数据类型对应的Class对象,一些常驻的异常对象(NullPointException、OutOfMemoryError)等,还有系统类加载器。所有被同步锁持有的对象反映Java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等。
除了这些固定的GC Roots集合以外,可能还会有其他对象临时性加入,可能还会出现跨代对象。

引用
        引用强度:强引用>软引用>弱引用>虚引用
         强引用:程序普遍存在的引用赋值,类似于“Object obj=new Object()”这种
         引用关系,只要强引用关系存在,垃圾回收器就不会回收掉对象。
         软引用:还有点用,但不是必须的,在系统溢出异常钱,会把这部分对象列入回收范围之中进行第二次回收,如果回收还没有足够的内存,才会抛出内存溢出异常。
         弱引用:描述那些非必须对象,被弱引用关联的对象在下一次垃圾回收机会被清掉,无论内存是否够,只要发生垃圾收集就会清理被弱引用关联的对象。
虚引用:对象不会受虚引用的控制,虚引用是否存在对对象没任何影响,也无法通过虚引用获得一个对象实例。对象设置弱引用关联唯一的作用就是在回收时可以收到一个系统的通知。

生存还是死亡?
        在可代行分许算法中判定位不可达对象,还处于缓刑阶段,宣告一个对象死亡要经历两个标记过程,可达性分析后发现没有与GC Roots相连接的引用,将会被第一次标记,随后进行筛查,条件是否有必要执行finalize方法。对象可以在finally方法中中自救,重新yu引用链上的对象关联即可(比如让对象把自己赋予给某个变量或者对象的成员变量),第二次标记时就会被移出“即将回收的”集合,可以免一死,否则就只能等死。如果没有重写finalize方法或者finalize方法已经被调用只能等死了。如果将执行finalize()方法,对象将被放在名为F-Queue的队列中,并在稍后有一条虚拟机自动建立的、低调度优先级的Finalizer线程去执行它们的finalize()方法。并且finalize方法只会执行一次,执行一次后面试金牌就失效了。

回收方法区
         方法区(HotSpot虚拟机中的元空间或者永久代)由于垃圾回收条件较为苛刻,方法区垃圾收集性价比比较低。方法区的垃圾收集包括两部分内容:废弃的常量和不再使用的类型,常量池中的常量没有在其他地方再被引用(与堆相似),就会发生内存回收,被确定要回收时,常量也会被清理除常量池,常量池中其他类、方法、字段的符号与此相似。然而回收不再使用的类型条件就比较苛刻了。要满足下列三个条件:
1、该类所有的实例都已经被回收,Java堆中不存在该类及其任何派生子类的实例。
2、加载该类的类加载器都已经被回收
3、该类对性的Java.lang.Class对象没有在任何地方被引用,无法通过在任何地方通过反射访问该类的方法。

Java虚拟机将会对满足上述三个条件的无用类进行回收,就算满足条件也并不一定会对类型进行回收。

3、标记清除算法

算法分为标记和清除两个阶段,标记存活的对象(或者反过来),然后进行清除。
缺点:
1.执行效率不稳定,Java堆中如果包含大对象,则必须要进行大量标记和清除的动作,其执行效率随着对象数量增长而降低。
2.产生内存空间的碎片化,标记清除后会产生大量不连续的内存碎片,空间碎片多再下次程序运行过程中想要分配大对象是无法找到足够的连续内存,从而间接导致提前触发垃圾收集。

4、标记复制算法

将内存按照容量划分为大小相等的两块,每次使用其中一块,当内存用完了就把存活的对象复制到另一块上,把自己已使用过的内存空间一次清理掉。老年代不能选用标志复制算法,无法保证空间50%。

优点:不会产生空间碎片化的问题,只需要移动堆顶指针,按顺序分配。

缺点:1、当存在大量存活对象时,会产生大量的内存间的复制开销
           2、可用内存缩小为原来的一般,空间浪费严重。
半区复制分代策略:把新生代分为一块较大的Eade区和两块较小的Survivor区,每次分配内存只使用Eden和其中一块Survivor区,发生垃圾搜集,将Eden和Survivor中任然存活的对象复制到另一块Survivor空间上,然后清除掉Eden和已用过的那块Survivor空间。,当Survivor空间不足以容纳一次MinorGC之后存活的对象时,就需要其他内存区域(一般是老年代)进行分配担保,分配担保原理和银行担保类似。如果Survivor空间没有足够空间存放上一次新生代收集下来存活的对象,这些对象会通过分配担保机制进入老年代,这对虚拟机来说就是安全的。

5、标记整理算法

(移动式回收算法)

针对老年代存货特征设计出来的算法,首先对存活对象标记,然后让所有存活的对象向内存的空间一段移动,然后清除掉边界外的内存。

标记复制算法是非移动式,然而标记整理算法是移动式,然后移动存活对象,尤其是老年代这种有大量对象存活的区域,移动存活对象将会是一种负重操作。这种对象移动必须通过暂停用户应用程序进行,称为(Stop The World)。如果不移动会产生碎片化问题,分配对象内存时难分配,移动又会影响应用程序的吞吐量。有一种方式就是让虚拟机多数时间采用标记-清除算法,暂时容忍碎片化问题,当碎片化程度影响到对象分配内存时就是用标记-整理算法收集一次。

根节点枚举(快速定位以减少STW的时间)
        所有收集器在根节点枚举这一步骤都是必须要暂停用户线程的(发生Stop The World的问题),根节点枚举始终必须保障一致性的快照中进行(一致性指整个枚举期间执行子系统看起来就像被冻结在某个时间点上(对象引用关系不能在发生改变),不会出现诸如已用关系还在不断变换的情况)当用户线程停下来不需要挨个遍历,只需要在特定的地方检查(在特定的位置如栈和寄存器中那些位置时引用,通过扫描得知信息,不需要一个不漏从方法区等GC Roots开始查找),虚拟机有办法直接得到那些地方是存在对象引用的。HotSpot中使用OoMap的数据结构,一旦类加载动作完成,HotSpot就会把对象内什么偏移量上是什么数据计算出来。

安全点
         在OopMap的协助下,HotSpot能快速完成GC Roots枚举,然而OopMap可能会导致引用关系变化,所以不能在每一条指令都设置OopMap,只有在特定的地方生成OopMap,这些地方称作安全点。强制用户程序执行时必须要到安全点才能够暂停。安全点的选取是以“是以是否能够让程序长时间执行为特征选取”,长时间执行的特征是指令复用(比如方法调用、循环跳转、异常跳转),会选取这些地方作为安全点。

如何保证在垃圾收集发生时让所有线程都跑到最近的安全点然后停顿下来?
        有两种办法
1、抢断式中断
        不需要线程执行的代码主动配合,在垃圾回收时就会把所有用户线程全部中断,如果发现用户线程中断的地方不在安全点上,就会恢复这条线程执行,让他继续跑到安全点上再进行中断。
2、主动式中断
        当垃圾收集需要中断线程的时候,不直接对线程进行操作,而是设置一个标志位,各个线程执行过程会不断地主动轮询这和标志,一旦发现中断标志为真,就在离自己最近的安全点上主动中断挂起。轮询标志地方即为安全点,不仅如此还要加上所有创建对象和需要在Java堆上分配内存的地方,避免垃圾收集时,没有足够内存分配新对象。
为了保证轮询操作足够高效,HotSpot使用内存保护陷阱的方式,把轮询操作精简为一条汇编指令。下图test指令就是就是HotSpot生成的轮询指令,当用户要暂停,就把0x160100内存设置为不可对,执行test指令就会产生一个自陷异常信号,然后在预先注册的异常处理器中挂起实现等待,这样就能通富哦一条汇编指令便完成安全点轮询和触发线程中断了。

安全区域
         安全点机制保证了程序执行时,在不太长的时间就看可以遇到垃圾收集过程的安全点。安全区域用来解决用户线程处于Sleep状态和Blocked状态,线程无法响应虚拟机的中断请求,不能跑到安全的地方挂起自己的情况。
         安全区域保证某一代码片段之中,引用关系不会发生变化,这个区域内开始垃圾收集都是安全的,相当于被扩展延伸的安全点。
执行流程:用户线程进入安全区域的代码,会标识自己已经进入了安全区域,垃圾收集时就不会管在安全区域内的线程,离开安全区域时,它要检查虚拟机是否已经完成了根节点枚举,如果完成线程继续运行;否则必须等待,知道收到允许离开安全区域的信号为止。

记忆集与卡表
         记忆集是用来解决对象跨代引用带来的问题的,是哟中用于记录从非收集区域指向收集区域的指针集合的抽象数据结构,为了节省记忆集的存储和维护成本,下面列举了一些可供范围选择的记录精度。
         字长精度:每个记录精确到一个机器字长(就是处理器的寻址位数,如常见的32位或者64位精度决定了机器访问物理内存地址的指针长度)。
         对象精度:每一个记录精确到一个对象,该对象字段里包含跨代指针。
         卡精度:每个记录精确到一块内存区域,该区域内有对象含有跨代指针。

卡表是用卡精度的方式区实现记忆集,不等同于记忆集,记忆集是一种抽象的数据结构,而卡表是它的具体实。
        字节数组卡表的每一个元素都对应着标识的内存区域中一块特定大小的内存块被称作“卡页”,卡页大小都是以2的N次幂的字节数。卡页内不止一个对象,只要卡页内有一个或以上对象的字段存在着跨代指针,就把对应卡表的数据元素的值标识为“1”,称这个元素变脏,没有则标识为0,在垃圾收集时,只要把变脏的元素筛选出来,就可以得出哪些卡页内存块中包含跨代指针,并把它们加入到GC Roots中一并扫描。

写屏障
        是为了维护卡表的操作(卡表如何变脏,谁爱把它变脏,维护卡表状态的),写屏障可以看做在虚拟机层面对“引用类型赋值”这个动作的AOP操作,在引用赋值时会产生一个环绕通知,在赋值前的部分写屏障叫作写前屏障,在赋值后的则叫做写后屏障。应用写屏障后,虚拟机就会为所有赋值生成相应的指令,一旦收集器为写屏障增加了更新卡表操作,就会产生额外的开销。

卡表在高并发场景还会出现伪共享的问题。
伪共享:现代中央处理器的缓存系统是以缓存行为单位存储的,当多个线程修改互相独立的变量,如果这些变量恰好共享同一个缓存行,就会彼此影响而大致性能降低。
如何避免伪共享问题?
先检查卡表标记,只有当该开标元素未被标记的时候才将其标记变脏

并发的可达性分析
        尽管有OoMap的加持和各种优化下停顿时间已经相对稳定,但随着GC Roots往下遍历对象,Java堆得存储对象越来越多就会引起停顿时间越来越长。

上图白色对象标识未被垃圾收集器访问过,如果可达性分析结束后任然为白色,则表明这个对象是不可达的。
黑色:表明对象已经被垃圾收集器访问过了,且所有对象都被扫描过,无须重新扫描一遍,它是安全存活的。
灰色:表明对象已经被垃圾收集器访问过,但这个对象至少存在一个引用还没有被扫描。
过程黑->灰->白
上图的过程会出现两种情况
1、把原来死亡的对象标记为活的
2、把原来存活的对象标记为死的
赋值器插入了一条或多条从黑色对象到白色对象的新引用;(倒数第二张图)
赋值删除了全部从灰色对象到该白色对象的直接或间接引用;(倒数第一张图)

     如何解决并发时对象消失的问题?有下列两种解决方案增量更新增量更新破坏的是第一个条件(即倒数第二张图),当黑色对象插入新的指向白色对象的引用关系时,就将这个新插入的引用记录下来,等并发扫描结束后,在将这些记录过的引用关系以黑色对象为根重新再扫描一次。(黑色对象一旦新插入了指向白色对象的引用后,它就会变为灰色)。原始快照原始快照破坏的是第二个条件,当灰色对象要删除指向白色对象的引用关系时(倒数第一张图),就将这个引用记录下来,并发扫描结束后,再将这些记录过的引用关系中的灰色为根,重新扫描一次(简单理解按照刚开始扫描那一刻对象的快照来进行,有没有删除引用关系对它没有影响与原来的会一样)。

二、收集器

在学习收集器之前我们要先搞清楚两个概念
并行(如上图所示):并行描述的是多条垃圾收集器线程之间的关系,说明同一时间有多条垃圾收集器的线程在协同工作,通过默认此时用户线程是处于等待状态。(如)

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


吞吐量就是处理器用于运行用户代码的时间与处理器总消耗时间的比值
比如用户代码运行时间加上垃圾收集总共耗费了100min,垃圾收集花掉了1min,则吞吐量就是100-1/100=99%。高吞吐量可以高效地利用处理器资源 ,尽快的完成程序的任务。

1.Serial收集器


Serial收集器
        是一个单线程工作的收集器,不单单只会使用一个处理器或者一条收集线程去完成垃圾收集工作,它在进行垃圾收集时,必须暂停其他工作线程(它就是牛逼一家独大),直到所及结束。
优点:它是收集器里面额外内存消耗最小的,少了线程交互的开销,专心做垃圾收集可获得最好的单线程收集效率。

2.ParNew收集器

ParNew收集器实质上是Serial收集器的多线程并行版本(加强plus),其他行为完全和Serial收集器完全一致。

3.Parallel Scavenge收集器

是一款新生带收集器,许多特性和ParNew相似,CMS等收集器的关注点是尽可能地缩短垃圾收集时用户线程的停顿时间,而PS收集器的目标则是达到一个可控制的吞吐量。

4.Serial Old收集器

SO处理器是Serial收集器的老年代版本,它同样是一个单线程处理器,使用标记-整理算法。
主要有一下两种用途:
1、一种是JDK5以及之前的版本中与PS收集器搭配使用
2、就是作为CMS收集器发生失败时的后备元,在并发收集发生Concurrent Mode Failure使用。

5、Paralle Old收集器

Paralle Old是Parallel Scavenge收集器的老年版本,支持多线程并发收集,基于标记-整理算法实现。PO与PS完成搭配实现“吞吐量”优秀的搭配组合.。

6、CMS收集器

CMS收集器是一种以获取最短回收停顿时间为目标的的收集器,它是基于标记-清除算法来实现的,它的运作过程分为四个步骤。
1、初始标记
初始标记只是仅仅标记一下GC Roots能直接关联到的对象,速度很快;
2、并发标记
并发标记就是从GC Roots的直接关联对象开始遍历整个对象图的过程,耗时较长,与垃圾收集线程一起并发运行;
3、重新标记
重新标记是为了修正并发标记期间,因用户程度继续云讯而导致标记产生变动的那一部分对象的标记记录,比并发标记时间段比初始标记时间长
4、并发清除
清理删除掉标记阶段判断的已死亡的对象,由于不需要移动存活对象,这个阶段可以和用户线程同时并发的。
初始标记、重新标记这两个步骤需要“Stop The World”

尽管初始标记与重新标记过程需要STW,但是从总体上看是与用户线程并发的,因为STW时间很短。

CMS优点:并发收集、低停顿。

CMS缺点:由于平法导致对处理器资源非敏感,在并发阶段会因为占用一部分线程导致应用程序变慢,降低吞吐总量。为了解决这种问题,提出了“增量式并发收集器的CMS收集器变种”,在并发标记、清理的时候让收集器线程、用户线程交替运行。,尽量减少垃圾收集线程独占资源的时间。由于并发会产生浮动垃圾,只能下一次垃圾收集时清理掉。由于垃圾收集阶段用户线程还需要持续运行所以要留足够的空间给用户线程使用(不能像别的线程等待老年代几乎被填满了在进行收集),得预留一部分空间并发收集使用。如果CMS无法预留的内存无法满足程序分配新对象的需要,会出现一次“并发失败”,虚拟机就会采取备胎方法(冻结用户线程,使用Serial Old收集器来重新进行老年代的垃圾收集),就会导致停顿时间变长,CMS是基于标记清除算法的还会产生大量空间碎片产生,从而导致“碎片化”问题,无法分配大对象。CMS得开启Full GC进行清理,但清理过程需要移动存活对象,而移动过程在(shenandoah和ZGC出现前)是无法用户线程和垃圾线程并发的,从而会导致停顿时间变长。

7、G1收集器

它以面向堆内存任何部分来组成回收进行回收,回收时不再是按照划代来回收,而是那块内存中存放的垃圾数量最多,回收收益最大来划分,简称G1收集器的Mixed GC模式。

G1是使用基于Region的堆内存布局来实现这个目标的,将连续的Java堆划分为多个大小相等的独立区域(Region)根据需求区扮演新生代的(Eden空间、Survivor空间)、老年代空间,根据不同角色的Reginon采用不同的策略去处理。
Region中还有一类特殊的Humongous区域,专门用来存储大对象,G1认为只要大小超过一个Region容量一般的对象即可以判断为大对象,而对于超过整个Region容量的超级大对象,将会被存放在N个连续的Humongous Region(G1大多数行为都把Humo Region看成是老年代)之中。

它的理论基础是:每次收集的到的内存空间都是Region大小的整数倍,可以避免对整个Java堆进行垃圾收集。让G1收集器区跟踪各个Region里面垃圾堆“价值”的大小,在后台建立一个优先级列表,优先处理回收价值收益最大的Region。


如何解决Region里面存在的跨Region引用对象问题?
        同样使用记忆集的方式避免全堆作为GC Roots扫描,但比以往复杂的多,每个Region都维护有自己的记忆集,这些记忆集回你记录下别的Region指向自己的指针,并标记这些指针分别在那些卡页的范围之外。G1的记忆集在存储结构的本质上是一种哈希表,Key是别的Region的其实地址,Value是一个集合(存储的是元素卡表的索引号),是双向的卡表结构(不仅记录了谁指向了它,也记录了它指向了谁)。比原来卡表实现更加复杂,同时占用更高的内存。

如何保证并发标记阶段收集线程与用户线程互干扰地运行?
         从上部分可知有两种算法,一种采用增量更新算法(CMS上应用的就是这种)来实现,G1使用的就是原始快照的算法(SATB)来实现的。垃圾堆用户线程的影响还体现在回收过程中新创建对象的内存分配上(具体值并发回收时新对象的产生),G1为每一个Region设计了两个名为TAMS(Top at Mark Start)的指针,把Region的一部分空间划分出来用于并发回收过程新对象的分配,并发回收时新分配对象的地址必须要在这两个指针位置上,而且G1默认在这个地址上的对象被隐式标记过默认存活,回收时不会清理掉。如果内存回收的速度赶不上内存分配的速度,G1收集器也要冻结用户线程,导致Full GC产生长时间STW。

建立了衰减均值为理论基础来实现可靠的停顿预测模型(G1收集器会激励meigeRegion的回收耗时、脏卡等数量信息),衰减平均值代表最近的状态,如果状态月薪越能决定其回收的价值。

G1收集器的运作过程(除了并发标记外,其余过程是要完全暂停用户线程的)


初始标记:仅标记GC Roots能直接关联的对象,并且修改TAMS指针的值,让下一阶段用户线程并发运行时能正确地在Region中分配新对象,需要停顿线程通常借助Minor CG同步完成。

并发标记:从GC Root开始堆堆中对象进行可达性分析,递归扫描整个堆里的对象图,然后找出要回收的对象,这个过程与用户线程是并发的,扫描完成后,还要处理SATB记录下的在并发时有引用变动的对象。

最终标记:对用户线程做另一个短暂的暂停,用于处理并发阶段结束后结束后任然遗留下来最后那少量的SATB记录
筛选回收:负责更新Region的统计数据,对各个Region的回收价值和成本进行排序,自由选择多个Region构成回收机,然后把决定回收的那一部分Region的存活对象复制到空的Region,再清理掉整个Region的全部空间,此过程涉及到存活对象的移动,需要暂停用户线程,由多条收集线程并行完成的。

优点:Region的内存布局、不会产生空间碎片

弱项:用户程序运行过程中G1垃圾收集产生的内存占用和程序运行时的额外执行负载较高(与CMS比较),G1卡表实现复杂,每个Region都需要有卡表会占用空间,要处理老年代代新生代的引用也要处理新生代到老年代的引用(CMS只需要处理老年代到新生代的引用)。

G1使用了前后屏障(前屏障主要英语记录并发时指针的变化情况),CMS写屏障采用的时直接的同步操作,G1采用的时类似于消息队列的结构,前后屏障中要做的事情都放在队列里,然后进行异步处理。


浅色标识必须挂起用户线程,深色标识收集器线程与用户线程是并发工作的,CMS和G1分别使用增量更新和原始快照急速实现了标记阶段的并发,不会因为管理堆得内存变大,要标记的对象变多而导致停顿时间变长。

8、Shenandoah收集器

Shenandoah也是基于Region的堆内存布局,同样有存放大对象的Humongous Region,回收策略也heG1相同(回收价值最大的Region),然而Shenandoah支持并发如上图所示,不会有新生的Region和老的Region存在,没有实现分代,使用连接矩阵(如下图)的方式记录Region的引用关系,如果RegionN有对象指向RegionM,就在表格的N行M列打一个标记,回收时就可以通过以下表的出哪些Region之间产生了跨代引用。

Shenandoah收集器的工作流程划分为以下九个阶段
初始标记:与G1一样,首先标记与GC Roots直接相关联的蚃,这个阶段任然是“STW”的,但是停顿时间与堆大小无关,值与GC Roots的数量有关。

并发标记:与G1一样,遍历对象图,标出全部可达的对象,这个阶段与用户线程是并发的,时间长短取决于堆中存活对象的数量以及对应图的结构复杂程度。

最终标记:与G1一样,处理剩余的SATB扫描,并在这个阶段统计出回收价值最高的Region,将这些Region构成一组回收机。会有一小段的暂停顿。

并发清理:这个阶段用于清理哪些整个区域内一个存活对象都没有找到的Region。

并发回收:并发回收阶段是与其他收集器的核心差异,Shenanadoah要把回收集里面存活的对象复制到其他未被使用的Region之中,由于是并发的引用关系可能会发生变化,移动后可能会存在内存中所有指向该对象的引用还是旧对象的地址,因此Shenandoah通过读屏障和“Brooks Pointers”的转发指针来解决。时间长短取决于回收机的大小。

初始引用更新:把堆中所有指向旧对象的指针的引用修正到复制后的新地址。设置这个阶段并无卵用,只是为了确保所有并发回收阶段中进行的收集线程完成了对象给他们分配的对象移动任务而已(并发引用才是真的更新)。

并发引用更新:真正开始进行引用更新操作,这个阶段是与用户线程一起并发的,时间长短取决于内存中涉及引用数量的多少。按照内存的物理地址顺序,线性地搜索引用类型,把旧的值改为心得的值。

最终引用更新:解决堆中引用更新后,还要修正存在于GC Roots中的引用,于GC Roots的数量相关,是Shenandoah的最后一次停顿。

并发清理:经过并发回收和引用更新后,整个Region已无存活对象,最后调用一次平法清理过程来回收Region的内存空间,供新对象分配使用。


Shenandoah支持并行整理的核心
内存保护陷阱
        通过被移动对象原有内存上设置保护陷阱,一旦用户程序访问到属于旧对象的内存空间就会产生自陷中断,进入预先设置好的处理器中,再由代码逻辑把访问转发到复制后的新对象上,实现移动与用户线程并发。


转发指针
        新方案则是不需要用到内存保护陷阱,而是在原有对象布局结构最前面统一增加一个新的引用字段,在正常不处于并发移动的情况下,该引用指向对象自己。修改时只需修改指针的值。为了避免用户按线程堆对象的变更发生在旧对象上,对转发指针访问操作得采取同步措施,让收集器线程或者用户线程对指针的访问只有其中之一能够成功。

8、ZGC收集器

ZGC收集器是一款基于Region内存布局的,不设分代的(不支持分代),使用读屏障,没有写屏障、使用染色指针和内存多重映射等急速来实现可并发的标记-整理算法的,以延迟为首要目标的一款垃圾收集器。
ZGC也是采用堆内存布局,但是它的Region有动态的区域容量大小,动态穿件和销毁过程。

        ZGC使用染色指针技术,保证对象在被移动的过程或者随时能够得到一些信息(如分代年龄等),将少量的信息存储在指针上的急速,染色指针可以是的某个Region的存活对象被移走之后,这个Region能够被立即释放和重用,利用的是其“自愈”的特性。染色指针将对象引用的变动写在了指针中(写屏障的目的是为了记录对象引用的变动情况),可以大幅度减少垃圾收集过程内存屏障的使用,所以ZGC只使用了读屏障。

Linux、x86-64平台上的ZGC使用了多重映射将多个不同的虚拟机内存地址映射到同一个物理内存的地址上,这是一种多对一映射,将多个不同的虚拟内存地址映射到同一个物理内存地址上,是一种多对医德映射。把染色指针中的标志位看作是地址的分段符,只要将不同的地址段都映射同一块物理内存空间,结经过多重映射转换后,染色指针就可以正常进行寻址了。


ZGC收集器是如何工作的?
全部四个阶段是并发执行的,仅两个阶段中间会存在短暂的停顿小阶段。

并发标记:遍历对象图做可达性分析的阶段,前后也要做类似(G1、Shenandoah的初始标记、最终标记)的短暂停顿,ZGC的标记实在指针上而不是对象上进行的,标记阶段会更新染色指针中的Marked()、Marked1()标志位。

并发预备重分配:需要特定的查询条件统计出收集过程要清理哪些Region,将这些Region组成重分配集,ZGC分Region的目的不是为了做收益优先(G1收益优先)的回收,而是每次都会扫描所有的Region,省去记忆集(像G1收记忆集)里的回收成本。ZGC里的重分配集只是决定了里面的存活对象会被复制到其他的Region,里面的Region随即被释放。

并发重分配:把重分配集中的存活对象复制到新的Region上,并为重分配集中的每个Region维护一个转发表,记录旧对象到新对象的转向关系。 染色指针使得ZGC能够的值一个对象是否处于分配集中,如果用户并发访问重分配集中的对象,这个访问就会被内存屏障截获,根据Region转发记录表将访问转发到心腹之的对象上,并同时修正更新该引用的值,使其指向新对象,称为自愈。Region对象复制后能够迅速释放掉也是染色指针的功劳。复制完毕后可以立即时放点用于新对象的分配(转发表得留着不能释放掉),哪怕堆中还有很多指向这个对象的未更新指针也无关系,因为就指针被使用是可以自愈的

并发重映射:就是修正整个堆中指向重分配集中旧对象的所有引用,但是不一定执行,因为旧引用它是可以自愈的(自愈无非多一次转发和修正操作),重映射清理这些旧引用的本质目的是为了不变慢,把并发重映射要干的活合并到了下一次垃圾收集循环的并发标记阶段完成,一旦所有指针被修改,原来记录新旧对象关系的转发表旧可以释放掉了。

ZGC技术特点NUMA(Non-Uniform Memory Acess,非统一式内存访问架构):ZGC会优先尝试在请求线程当前所处的处理器的本地内存分配对象,保证高效内存访问。

三、总结

本文主要介绍了几种,内存分配策略,和可行性分析算法。HotSpot虚拟机的几种收集器与不同的标记(存储一些额外的、只供收集器或者虚拟机本省使用的数据,如哈希码表等,本质与对象的引用有关系,与对象本身无关,如三色标记)实现方案标记在对象头上(如Serial收集器)、标记在与对象相互独立的数据结构上(G1、Shenandoah使用BitMap的结构存储信息)、ZGC使用的是染色指针


JVM由浅入深系列——详解垃圾收集器与内存分配策略相关推荐

  1. JVM性能调优3_垃圾收集器与内存分配策略__享学课堂

    Stop The World现象 GC收集器和我们GC调优的目标就是尽可能的减少STW的时间和次数. 内存分配与回收策略 对象优先在Eden分配,如果说Eden内存空间不足,就会发生Minor GC ...

  2. 深入理解JVM读书笔记二: 垃圾收集器与内存分配策略

    3.2对象已死吗? 3.2.1 引用计数法 给对象添加一个引用计数器,每当有一个地方引用它的地方,计数器值+1:当引用失效,计数器值就减1;任何时候计数器为0,对象就不可能再被引用了. 它很难解决对象 ...

  3. JVM性能调优2_垃圾收集器与内存分配策略__享学课堂

    判断对象的存活 引用计数法: 优点:快,方便,实现简单: 缺点:对象相互引用时,很难判断对象是否该回收. 可达性分析: 这个算法的基本思路就是通过一系列的称为"GC Roots"的 ...

  4. jvm(3)-垃圾收集器与内存分配策略

    [0]README 0.1)本文部分文字转自:深入理解jvm,旨在学习 垃圾收集器与内存分配策略 的基础知识: [1]垃圾回收概述 1)GC(Garbage Collection)需要完成的3件事情: ...

  5. jvm垃圾收集器与内存分配策略

    2019独角兽企业重金招聘Python工程师标准>>> 垃圾收集器与内存分配策略: 以下参考周志明的<<深入理解jvm高级特性与最佳实践>>. 判断对象是否存 ...

  6. JVM:垃圾收集器与内存分配策略

    垃圾收集器与内存分配策略 1.对象已死吗 1).引用计数算法 引用计数算法:给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1:当引用失效时,计数器值就减1:任何时刻计数器为0的对象就 ...

  7. java eden分配参数,JVM垃圾收集器与内存分配策略,

    垃圾收集器与内存分配策略 对象存活判断 引用计数算法 给对象添加一个计数器,每有一个引用+1,当引用失效-1,若为0则不在被使用. 可达性分析算法 对象是否可到达GC roots 或者说GC root ...

  8. 7种垃圾收集器与内存分配策略,看这一篇就够了

    垃圾收集器与内存分配策略-垃圾收集器 (A).图中展示了7种不同分代的收集器: Serial.ParNew.Parallel Scavenge.Serial Old.Parallel Old.CMS. ...

  9. 垃圾收集器与内存分配策略(五)之垃圾日志与常见参数

    2019独角兽企业重金招聘Python工程师标准>>> 垃圾收集器与内存分配策略(五)--垃圾日志与常见参数 理解GC日志 每个收集器的日志格式都可以不一样,但各个每个收集器的日志都 ...

最新文章

  1. matlab 2010无法运行程序,matalb r2010a安装后打开出现一系列警告,无法运行,哪位大神帮...
  2. linux如何安装VM虚拟机
  3. 【计算机网络(微课版)】第1章 概述 课后习题及答案
  4. 【POJ - 3177】Redundant Paths(边双连通分量,去重边)
  5. 好看好用的花前月下网易云等级代挂程序(支持扫码登录)
  6. 小程序开发工具不显示tobar图标
  7. 使用Xcode打包上传APP
  8. 2022还不错的和平精英画质助手iApp源码+附成品
  9. Java web项目中获取WebRoot目录下的文件
  10. 通过注册表改变“我的文档”等的默认位置
  11. python学习——关于曲线拟合
  12. Ambarella : 一家伟大的视频压缩处理芯片厂商
  13. linux下.txt文件名乱码,Linux下打开txt文件乱码问题解决方案
  14. android代码获取deviceid,获取安卓系统的设备id用getDeviceId()函数
  15. ps切图详解以及上传至蓝湖
  16. Appium 自动化测试 手机操作
  17. Zookeeper——3、使用zkClient操作zookeeper
  18. linux操作系统认手机,Linux移动操作系统postmarketOS已适配200款移动设备 包括手机和平板电脑...
  19. 工控自动化CAD主流电气原理图,多套主流PLC电气图纸
  20. 【图像重建】基于FDK算法实现图像重建附matlab代码

热门文章

  1. 安卓手机开不了机_苹果手机为什么会黑屏、开不了机?
  2. android 关闭硬件加速
  3. 爬虫 某团外卖mtgsig逆向分析
  4. 严重:异常将上下文初始化事件发送到类的侦听器实例.[org.springframework.web.context.ContextLoaderListener] 以解决
  5. PHP调用kaldi,程序员罗杰-JAVA 调用PHP Webservices
  6. HBase系列从入门到精通(三)
  7. Java实现 基础算法 水仙花数
  8. Normalized Frequency(x PI rad/sample)含义
  9. 阿里程序员整理的Python 各种符号,建议收藏,文末附Python资料
  10. JavaScript基础系列之四 面向对象编程