垃圾回收算法以及常见垃圾收集器简介

垃圾回收算法以及常见垃圾收集器简介

前言

例如:作为java程序员JVM会帮你做垃圾回收,那JVM是通过怎样的一个方式回收的呢?常见的垃圾回收算法以及垃圾收集器有哪些呢?它们又是如何工作的呢?

一、垃圾收集算法

1.1 分代收集理论

根据年轻代或者老年代的不同,选着不同的垃圾收集算法。年轻代一般选用复制算法,老年代一般选用标记清除算法或者标记整理算法。

1.2 标记-复制算法

内存整理前:先把内存分为已使用(保留内存)和未使用(可用内存、可回收内存、存活对象(通过gc root可达性算法判断哪些对象是存活的))两部分,(从eden园区到survior区);
内存整理后:将已使用(保留内存)和未使用(可用内存、可回收内存、存活对象)复制并交换位置,并将未使用中的可回收内存回收掉;
在这里插入图片描述
缺点:每次内存回收都是对内存空间的一半进行回收,浪费空间。年青代使用复制算法,老年代没有使用复制算法。

1.3 标记-清除算法

标记已存活的对象,清除那些没有标记(未存活)的对象。
在这里插入图片描述
缺点:1.效率问题,如果标记的对象太多,效率不高;2.空间问题,标记清除后会产生大量不连续的碎片(内存空间);

1.4 标记-整理算法

标记已存活的对象,让存活的对象向可回收的对象移动(不清除垃圾对象,把非垃圾对象直接挪到垃圾对象,像赋值一样),如果可回收的对象没有存活对象过来,那么直接清除可回收对象;(写屏障方式同步修改内存地址)
在这里插入图片描述

二、垃圾收集器

在这里插入图片描述
如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。
常见的垃圾收集器有Serial、ParNew、Parallel、CMS、Serial Old、Parallel Old、G1、ZGC。
为什么要有这么多垃圾收集器呢?因为现在还没有一种可以在任何场景下使用的收集器,我们要根据具体的场景去选着合适自己的垃圾收集器。

2.1 Serial收集器

serial(串行)收集器是最基本、历史最悠久的垃圾收集器了。它是单线程垃圾收集器,它在垃圾收集的过程中会暂停其他所有的工作线程(“Stop The World” 简称STW)。年轻代采用复制算法,老年代采用标记-整理算法。能回收的内存区域几十兆到几百兆之间,不是很大,现在已经不用了。使用参数-XX:+UseSerialGC -XX:+UseSerialOldGC
在这里插入图片描述
缺点:效率低,在STW时会给用户带来不良的用户体验;
优点:简单高效(与其他单线程的收集器相比);
Serial Old收集器是Serial收集器的老年代版本,它同样是一个单线程收集器。它主要有两大用途:一种用途是在JDK1.5 以及以前的版本中与Parallel Scavenge收集器搭配使用,另一种用途是作为CMS收集器的后备方案。

2.2 Parallel Scavenge收集器

Parallel收集器其实就是Serial收集器的多线程版本,除了使用多线程进行垃圾收集外,其余行为(控制参数、收集算法、回收策略等等)和Serial收集器类似。默认的收集线程数跟cpu核数相同,当然也可以用参数(-XX:ParallelGCThreads)指定收集线程数,但是一般不推荐修改。 Parallel Scavenge收集器关注点是吞吐量(高效率的利用CPU),它STW时间比较长,用户体验不好。CMS等垃圾收集器的关注点更多的是用户线程的停顿时间(提高用户体验)。所谓吞吐量就是CPU中用于运行用户代码的时间与CPU总消耗时间的比值。新生代采用复制算法,老年代采用标记-整理算法。 使用参数-XX:+UseParallelGC(年轻代),-XX:+UseParallelOldGC(老年代)
在这里插入图片描述
Parallel Old收集器是Parallel Scavenge收集器的老年代版本。使用多线程和“标记-整理”算法。在注重吞吐量以及CPU资源的场合,都可以优先考虑 Parallel Scavenge收集器和Parallel Old收集器(JDK8默认的新生代和老年代收集器),Parallel收集器与CMS(只能在老年代使用)收集器没有办法配合使用。

2.3 ParNew收集器

ParNew收集器其实跟Paralle收集器很类似(ParNew用于新生代),(ParNew+CMS)区别主要用于它可以和CMS(用于老年代)收集器配合使用。新生代采用复制算法,老年代采用标记-整理算法。 使用参数-XX:+UseParNewGC。
在这里插入图片描述

2.4 CMS收集器

CMS(采用标记清除算法)(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器,它非常注重用户体验
运行过程大致如下:

2.4.1 初始标记

暂停所有其他的线程(STW,如果没有STW机制的话在初始标记阶段就会有不断的对象产生,初始标记做不完),记录gc roots直接能引用的对象,速度很快;

2.4.2 并发标记

不需要停顿应用线程,应用程序线程与CMS线程同时运行,有可能会导致已经标记过的对象状态发生改变;(如果内存变大了,整个堆内存就会变大,标记的时间也会变长,STW时间也会变长,用户体验不好。) 会产生多标、漏标的问题(下面会有解决办法)。

2.4.3 重新标记

也会STW,重新标记那些并发标记里状态发生改变的对象;(三色标记中的增量更新)

2.4.4 并发清理

没有标记过的对象(垃圾对象)都清除掉,应用线程与CMS线程同时运行(因为应用线程也运行着,会产生新的对象,没有被标记过,这些引用会被标记为黑色,黑色不会被扫描,会变成浮动垃圾下一轮再被清理(三色标记))。

2.4.5 并发重置

清理以上步骤标记过的对象,便于下一次重新打标记。
在这里插入图片描述
如果ParNew收集器在执行过程中的时间是1s,CMS收集器在执行过程中的时间会大于1s,为什么?因为CMS收集器在执行过程中会把资源(CPU)分一部分给应用线程去执行,但是没有STW(用户会在初始标记和重新标记里感受到STW),用户几乎没有感觉等待很长时间。

CMS收集器的优点:并发收集、低停顿。
CMS收集器的缺点:
(1).会和CPU抢资源(应用线程和CMS线程会同时运行);
(2).浮动垃圾(在并发标记和并发清理时会产生新的垃圾,只能等到下一次gc来处理了);
(3).采用标记-清除算法,会产生大量的空间碎片,但是JVM可以采用XX:+UseCMSCompactAtFullCollection参数可以让jvm标记清除再做整理;
(4).在并发标记和并发清理过程中会出现一边回收,一边运行的情况,也许没有回收完就再次触发full gc(会出现上一次垃圾回收还没执行完,垃圾又被触发的情况),就是并发失败(concurrent mode failure),会stop the world,用Serial old收集器来回收。

2.4.6 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.5 G1垃圾收集器

2.5.1 G1垃圾收集器简介

JDK9默认垃圾器变成G1,JDK9的话CMS还是可以使用但是不推荐使用了,JDK8也可以使用G1,但是不推荐使用。G1收集器(-XX:+UseG1GC),G1(Garbage-First)是一款面向服务器的垃圾收集器,主要针对配备多颗处理器以及大容量内存的机器,还具备高吞吐量特性。
在这里插入图片描述

G1将java堆内存划分为大小相等的独立区域(Region),JVM最多可以有2048个Region。如果一个为4G(4096M)的内存,平均每个Region为2M,当然也可以用参数"-XX:G1HeapRegionSize"手动指定Region大小,但是推荐使用默认的计算方式。
G1保留了年轻代和老年代的概念,但不再是物理隔离了,它们都是(可以不连续)Region集合,比如一个Region可能之前是年轻代,如果Region进行了垃圾回收,之后可能又变成了老年代。
默认年轻代对堆内存的占比是5%,如果堆大小为4096M,那么年轻代占据200MB左右的内存,对应大概是100个 Region,可以通过“-XX:G1NewSizePercent”设置新生代初始占比,在系统运行中,JVM会不停的给年轻代增加更多的Region,但是最多新生代的占比不会超过60%,可以通过“-XX:G1MaxNewSizePercent”调整。年轻代中的Eden和 Survivor对应的region也跟之前一样,默认8:1:1,假设年轻代现在有1000个region,eden区对应800个,s0对应100 个,s1对应100个。
G1跟其他垃圾收集器不同的地方是对大对象的处理,G1有专门分配大对象的Region叫做Humongous区,不是将对象直接进入老年代,在G1中如果一个大对象超过了Region的50%就会被放入Humongous区,如果一个对象的内存是1.5M,会被直接放入Humongous中,如果一个对象的内存是6M,那么就会放入连续三个Humongous区中。
在这里插入图片描述

2.5.2 G1收集器运行过程

初始标记:暂停所有的其他线程,并记录下gc roots直接引用的对象,速度很快(会STW);
并发标记:同CMS的并发标记;
最终标记:同CMS的重新标记(会STW);
筛选回收(与CMS并发清理差不多,但会STW):根据用户所期望的GC停顿STW时间(可以JVM参数:-XX:MaxGCPauseMills指定)来指定回收,比如老年代有1000个region都满了,但是因为根据预期的停顿时间,本次回收可能是200毫秒(如果参数-XX:MaxGCPauseMills不设置的话默认是200ms),通过计算得知,可能回收其中的800个region就刚好需要200毫秒,那么就回收800个region(Collection Set,要回收的集合),剩下的200个region放到下一次垃圾回收时再回收,尽量把GC停顿时间控制在我们制定的时间范围之内。不管是年轻代还是老年代,回收算法主要用的是复制算法(G1从整体上来看是标记整理算法),将一个region中存活的对象复制到另外一个region中,这种不会像CMS那样回收完因为有很多内存碎片还需要整理一次,G1采用复制算法回收几乎不会有太多内存碎片。G1收集器在后台维护了一个优先列表,每次根据允许的收集时间,优先选择回收价值最大的region,比如一个region花200ms能回收10M垃圾,另外一个region花50ms能回收10M垃圾,在回收时间有限的情况下,G1当然会优先选择后面这个region回收。
在这里插入图片描述

2.5.3 G1收集器的特点

并行与并发:G1能充分利用CPU、多核环境下的硬件优势,使用多个CPU(CPU或者CPU核心)来缩短Stop- The-World停顿时间。部分其他收集器原本需要停顿Java线程来执行GC动作,G1收集器仍然可以通过并发的方式 让java程序继续执行。
分代收集:虽然G1可以不需要其他收集器配合就能独立管理整个GC堆,但是还是保留了分代的概念。
空间整合:与CMS的“标记–清理”算法不同,G1从整体来看是基于“标记整理”算法实现的收集器;从局部上来看是基于“复制”算法实现的。
可预测的停顿:这是G1相对于CMS的另一个大优势,降低停顿时间是G1 和 CMS 共同的关注点,但G1 除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间片段(通过参数"-XX:MaxGCPauseMillis"指定)内完成垃圾收集。
毫无疑问,可以由用户指定期望的停顿时间是G1收集器很强大的一个功能,设置不同的期望停顿时间,可使得G1在不同应用场景中取得关注吞吐量和关注延迟之间的最佳平衡。不过,这里设置的“期望值”必须是符合实际的,不能异想天开,毕竟G1是要冻结用户线程来复制对象的,这个停顿时间再怎么低也得有个限度。 它默认的停顿目标为两百毫秒,一般来说,回收阶段占到几十到一百甚至接近两百毫秒都很正常, 但如果我们把停顿时间调得非常低,譬如设置为二十毫秒,很可能出现的结果就是由于停顿目标时间太短,导致每次选出来的回收集只占堆内存很小的一部分,收集器收集的速度逐渐跟不上分配器分配的速度,导致垃圾慢慢堆积。很可能一开始收集器还能从空闲的堆内存中获得一些喘息的时间,但应用运行时间一长就不行了,最终占满堆引发Full GC反而降低性能,所以通常把期望停顿时间设置为一两百毫秒或者两三百毫秒会是比较合理的。

2.5.4 G1垃圾收集分类

YoungGC:YoungGC并不是说现有的Eden区放满了就会马上触发,G1在触发YoungGC时会判断一下触发的时间是否接近设定的时间(默认是200ms),如果没有接近的话不会马上触发YoungGC,会将Eden区继续加空间(默认是5%,最大不会超过60%),如果接近的话才会触发YoungGC。
MixedGC:老年代占用堆内存的比例达到45%(可以用参数-XX:InitiatingHeapOccupancyPercent设置)时,会触发MixedGC,会回收所有的Young和部分Old(根据期望的GC停顿时间确定old区垃圾收集的优先顺序)以及大对象区(Humongous区)。(MixedGC时会回收G1的Humongous区)
FullGC:在做复制算法(对内存空间要求比较高)的过程中,需要把各个region中存活的对象拷贝到别的region里去,拷贝过程中发现没有足够的空的region能够承载拷贝对象就会触发一次Full GC,触发FullGC会导致停止系统程序,然后采用单线程进行标记、清理和压缩整理,好空闲出来一批region来供下一次MixedGC使用,这个过程是非常耗时的。

2.5.5 G1收集器参数配置

-XX:+UseG1GC:使用G1收集器;
-XX:ParallelGCThreads:指定GC工作的线程数量;
-XX:G1HeapRegionSize:指定分区大小(1MB~32MB,且必须是2的N次幂),默认将整堆划分为2048个分区
-XX:MaxGCPauseMillis:目标暂停时间(默认200ms);
-XX:G1NewSizePercent:新生代内存初始空间(默认整堆5%);
-XX:G1MaxNewSizePercent:新生代内存最大空间;
-XX:TargetSurvivorRatio:Survivor区的填充容量(默认50%),Survivor区域里的一批对象(年龄1+年龄2+年龄n的多个年龄对象)总和超过了Survivor区域的50%,此时就会把年龄n(含)以上的对象都放入老年代;
-XX:MaxTenuringThreshold:最大年龄阈值(默认15);
-XX:InitiatingHeapOccupancyPercent:老年代占用空间达到整堆内存阈值(默认45%),则执行新生代和老年代的混合收集(MixedGC),比如我们之前说的堆默认有2048个region,如果有接近1000个region都是老年代的region,则可能就要触发MixedGC了;
-XX:G1MixedGCLiveThresholdPercent(默认85%) region中的存活对象低于这个值时才会回收该region,如果超过这个值,存活对象过多,回收的的意义不大;
-XX:G1MixedGCCountTarget:在一次回收过程中指定做几次筛选回收(默认8次),在最后一个筛选回收阶段可以回收一会,然后暂停回收,恢复系统运行,一会再开始回收,这样可以让系统不至于单次停顿时间过长;
-XX:G1HeapWastePercent(默认5%): gc过程中空出来的region是否充足阈值,在混合回收的时候,对Region回收都是基于复制算法进行的,都是把要回收的Region里的存活对象放入其他Region,然后这个Region中的垃圾对象全部清理掉,这样的话在回收过程就会不断空出来新的Region,一旦空闲出来的Region数量达到了堆内存的5%,此时就会立即停止混合回收,意味着本次混合回收就结束了。

2.5.6 G1收集器使用场景

1.50%以上的堆被存活对象占用;
2.对象分配和晋升的速度变化非常大;
3.垃圾回收时间特别长,超过1秒;
4.8GB以上的堆内存(建议值);
5.停顿时间是500ms以内;

2.7 ZGC收集器

2.7.1 ZGC简介

ZGC是一款JDK11中新加入的具有实验性质的低延迟垃圾收集器,ZGC可以说源自于Azul System公司开的C4(Concurrent Continuously Compacting Collector)收集器。
在这里插入图片描述

2.7.2 ZGC目标

在这里插入图片描述
1.支持TB量级别的堆;
2.最大GC停顿时间不超10ms;
3.奠定未来GC特性的基础;
4.最糟糕的情况下吞吐量会降低15%;
不分代(暂时):我们都知道以前的垃圾回收器之所以分代,是因为源于“大部分对象朝生夕死”的假设,事实上大部分系统的对象分配行为也确实符合这个假设,那为什么ZGC就不分代呢?因为分代实现起来麻烦,作者就先实现出一个比较简单可用的单代版本,后续会优化。

2.7.3 ZGC内存布局

ZGC收集器是一款基于Region内存布局的, 暂时不设分代的, 使用了读屏障、 颜色指针等技术来实现可并发的标记-整理算法的,以低延迟为首要目标的一款垃圾收集器。
ZGC的Region可以具有如图3-19所示的大、中、小三类容量:
小型Region(Small Region):容量固定为2MB,用于放置小于256KB的小对象。
中型Region(Medium Region):容量固定为32MB,用于放置大于等于256KB但小于4MB的对象。
大型Region(Large Region):容量不固定,可以动态变化,但必须为2MB的整数倍,用于放置4MB或以上的大对象。每个大型Region中只会存放一个大对象, 这也预示着虽然名字叫作“大型Region”, 但它的实际容量完全有可能小于中型Region,最小容量可低至4MB。大型Region在ZGC的实现中是不会被重分配(重分配是ZGC的一种处理动作,用于复制对象的收集器阶段,稍后会介绍到)的,因为复制一个大对象的代价非常高昂。
在这里插入图片描述

2.7.4 颜色指针

之前的垃圾回收器的GC信息都保存在对象头上,而ZGC垃圾收集器的GC信息保存在指针中。
在这里插入图片描述
每个对象有一个64位指针,这64位被分为:
18位:预留给以后使用;
1位:Finalizable标识,此位与并发引用处理有关,它表示这个对象只能通过finalizer才能访问;
1位:Remapped标识,设置此位的值后,对象未指向relocation set中(relocation set表示需要GC的
Region集合);
1位:Marked1标识;
1位:Marked0标识,和上面的Marked1都是标记对象用于辅助GC;
42位:对象的地址(所以它可以支持2^42=4T内存):
为什么有2个mark标记?
每一个GC周期开始时,会交换使用的标记位,使上次GC周期中修正的已标记状态失效,所有引用都变成未标记。
GC周期1:使用mark0, 则周期结束所有引用mark标记都会成为01。
GC周期2:使用mark1, 则期待的mark标记10,所有引用都能被重新标记。
通过对配置ZGC后对象指针分析我们可知,对象指针必须是64位,那么ZGC就无法支持32位操作系统,同样的也就无法支持压缩指针了(CompressedOops,压缩指针也是32位)

颜色指针的三大优势:
1.一旦某个Region的存活对象被移走之后,这个Region立即就能够被释放和重用掉,而不必等待整个堆中所有指向该Region的引用都被修正后才能清理,这使得理论上只要还有一个空闲Region,ZGC就能完成收集。
2.颜色指针可以大幅减少在垃圾收集过程中内存屏障的使用数量,ZGC只使用了读屏障。
3.颜色指针具备强大的扩展性,它可以作为一种可扩展的存储结构用来记录更多与对象标记、重定位过程相关的数据,以便日后进一步提高性能。

2.7.5 读屏障

ZGC在并发重分配过程中,在复制算法后将原来的region(里的对象)移动到另外一个region,涉及到新的地址需要更新,什么时候更新呢?这个过程是一个惰性过程,它不是一直在更新,当我去堆里拿一个老的对象值时,拿到的是一个引用,这个引用在做并发标记时颜色指针已经将颜色改过了,在拿老引用过程中把颜色指针那部分也拿过来了,在拿的时候会加一个读屏障判断一下这个指针是否被修改过,如果被修改过了,它内部会将老的引用,挪到已经被更新过的地址里。 通过转发表记录从旧对象到新对象的转向关系。

2.7.6 ZGC存在问题以及解决方案

ZGC最大的问题是浮动垃圾。ZGC的停顿时间是在10ms以下,但是ZGC的执行时间还是远远大于这个时间的。假如ZGC全过程需要执行10分钟,在这个期间由于对象分配速率很高,将创建大量的新对象,这些对象很难进入当次GC,所以只能在下次GC的时候进行回收,这些只能等到下次GC才能回收的对象就是浮动垃圾。
解决方案
目前唯一的办法是增大堆的容量,使得程序得到更多的喘息时间,但是这个也是一个治标不治本的方案。如果需要从根 本上解决这个问题,还是需要引入分代收集,让新生对象都在一个专门的区域中创。

2.7.7 ZGC参数设置

启用ZGC比较简单,设置JVM参数即可:-XX:+UnlockExperimentalVMOptions 「-XX:+UseZGC」。调优也并不难, 因为ZGC调优参数并不多,远不像CMS那么复杂。它和G1一样,可以调优的参数都比较少,大部分工作JVM能很好的自动完成。下图所示是ZGC可以调优的参数:
在这里插入图片描述

三、垃圾收集底层算法实现

3.1 三色标记

在gc过程中或者说是并发标记过程中,主要是三种颜色
黑色:表示这个对象的所有引用都被垃圾收集器扫描过,它是存活(非垃圾)的对象;
灰色:表示这个对象至少还有一个引用没有被垃圾收集器扫描过;
白色:表示这个对象没有被垃圾收集器访问过(没有gc root引用的对象,不可达对象);
在这里插入图片描述

3.2 多标-浮动垃圾

多标由于应用线程没有停止(栈线程执行完毕,没有STW机制,就是栈线程里的局部变量表、操作数栈、动态链接、方法出口的那个指针没有指向堆内存)会出现多标(非垃圾对象被标记为垃圾对象)的垃圾对象,多标(浮动垃圾)等下一次垃圾回收时再被清除。

3.3 漏标-读写屏障

在这里插入图片描述
漏标:A->D,开始没有后面新增,B->D,开始有后面去掉。A不会重新扫描了,B重新扫描又扫不到D;D(将来会被删掉)就漏标了(阿里巴巴可能会被问到),D不是垃圾对象被标记成垃圾对象了这会导致程序有严重bug。

增量更新:在并发标记的过程中,把赋值(新增的引用)用一个集合存起来,(在重新标记时就是解决并发标记时多标或者漏标的问题,找到新增记录的引用重新扫描);简单理解:新增的引用源头以及引用的对象都记录在一个集合(底层是C++)里面,把源头的引用重新标记为黑色。(黑色对象一旦新插入了指向白色对象的引用之后,它就变成灰色对象了)
原始快照(Snapshot At The Beginning,SATB):(没听明白 在听一遍)在赋值之前将老的引用以快照的形式保存到一个集合里,在重新标记的过程中,集合里面的引用全部标记为黑色,在这一轮里就不会被回收(变成浮动垃圾了,在下一轮会被回收)。
不管是增量更新还是原始快照都是通过写屏障的方式记录的。
写屏障:在新增引用或者减少引用都是通过赋值的方式实现的,不管是增量更新还是原始快照引用的记录都要收集到集合里面去,要么在赋值之前做要么在赋值之后做(代码的操作屏障), 通过写屏障的方式记录到集合里面的
在赋值前后做些处理(有点像AOP),(写屏障)写前操作,(写屏障)写后操作(增量更新)。
CMS是写屏障+增量更新实现漏标处理的。(写操作异步处理,提升性能)

为什么G1使用原始快照,CMS使用增量更新实现漏标处理?
因为CMS在增量更新时还会继续扫描跟节点对象的引用,G1在原始快照时不会再去深度扫描对象了,只是简单标记一下,等到下一轮GC在深度扫描。(G1因为有很多不同的region,CMS就一块老年代区域,重新深度扫描的话G1的代价会比CMS高)。

四、记忆集与卡表

记忆集(Remember Set):为了解决年轻代的对象的引用被老年代引用着,在年青代的内存专门设置这些被老年代引用的对象(跨代引用不会很多),把这些对象的集合叫着记忆集。
卡表(Cardtable):卡表与记忆集就像Java语言中HashMap与Map的关系。用卡表实现了记忆集,在老年代里划分一块一块的内存区域(卡页),或者叫做页内存card(大概512个字节),如果这个页内存(card)里面有一个对象是引用年轻代里某一个对象,那么把这个卡页标记为dirty。卡表(Cardtable)底层实现就是一个数组,数组里面每一个元素对应着的内存区域叫做页内存(卡页)。卡表底层也是通过写屏障来实现的。卡页在老年代,卡表在年轻代(堆)。

五、如何选择垃圾收集器

1.优先调整堆的大小让服务器自己来选择;
2. 如果内存小于100M,使用串行收集器;
3. 如果是单核,并且没有停顿时间的要求,串行或JVM自己选择;
4. 如果允许停顿时间超过1秒,选择并行或者JVM自己选;
5. 如果响应时间最重要,并且不能超过1秒,使用并发收集器;
6. 4G以下可以用parallel,4-8G可以用ParNew+CMS,8G以上可以用G1,几百G以上用ZGC;

六、安全点与安全区域

安全点:在做GC过程中不是想做就立刻做GC,当用户线程在做GC之前会判断一下标志是什么(比如0和1,1表示达到安全点的位置,0没有达到,所有用户线程都会轮询看一下这个标志)当用户线程标志都为1时,就会被挂起,当所有用户线程标志都为1时(都到达安全点时),就会触发GC。
安全点的位置主要有以下几种:
1.方法返回之前;
2.调用某个方法之后;
3.抛出异常的位置;
4.循环的末尾;
安全区域:是针对一个正在执行的线程而定的,比如一个线程处于sleep状态,它就不能响应JVM的中断请求,再运行到Safe Point上。因此JVM引入了Safe Region,它是指在一段代码块中,引用关系不会发生变化,这个区域内的任意地方开始GC都是安全的。

总结

例如:常见的垃圾回收算法标记-复制算法、标记-清除算法、标记-整理算法,常见的垃圾收集器Serial、Parallel、ParNew、CMS、G1、ZGC,主要是CMS、G1、ZGC这三种垃圾收集器运行过程、在并发标记过程中出现的漏标问题以及对应的解决方法。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值