部分内容来源:JavaGuide,二哥Java
垃圾回收器快速复习
JDK 8: Parallel Scavenge(新生代)+ Parallel Old(老年代)
JDK8: Serial +Serial Old
JDK 9 ~ JDK22: G1
新生代:标记-复制算法
老年代:标记-整理算法
Serial:单线程,Stop The World
ParNew:Serial的多线程版本
Paralled:ParNew的多功能版,提供了很多参数供用户找到最合适的停顿时间或最大吞吐量(这也是为什么用Paralled而不是ParNew)
CSM:JDK1.4引入,老年代回收器,标记清除法,关注GC停顿时间(Stop The World),并发执行,三色标记法解决Stop The Wrold
标记流程:初始标记,并发标记,重新标记,并发清除
优点:并发+几乎没有Stop The World(几乎不用暂停用户线程)
缺点:CPU资源敏感,标记清除法的内存碎片问题,并行时无法处理用户线程产生的浮动垃圾(垃圾过多会强制回退成Serial回收器进行收集)
三色标记法:可达性分析法的改进,实现垃圾回收线程和用户线程的并发执行,减少STW
白色->灰色->黑色,最后回收白色对象(不可达对象)
优点:并发,高效,减少STW
缺点:漏标问题,标记阶段需扫描大量对象耗时过长
漏标问题的解决方案:增量更新法(添加引用时将引用对象染成灰色),删除插入法(提前将删除前的白色对象染成灰色)
G1:并行,分代收集(物理Region分区,逻辑上分代),可预测的停顿
标记流程:初始标记,并发标记,重新标记,筛选回收
G1 收集器在后台维护了一个优先列表,每次根据允许的收集时间,优先选择回收价值最大的 Region(这也就是它的名字 Garbage-First 的由来)
使用 Region 划分内存空间以及有优先级的区域回收方式
G1的五个属性:
分代收集思想+多个相同大小的Region分配内存
Humongous区专门分配大对象(超过Region的50%)
并行
标记-复制法(避免全堆整理,维护可预测的停顿时间模型)
可预测的停顿模型,G1 在停顿时间上添加了预测机制,用户可以指定期望停顿时间
转移阶段要处理所有存活的对象,耗时会较长
因此,G1 停顿时间的瓶颈主要是标记 - 复制中的转移阶段 STW
ZGC:低延迟垃圾收集器:
三色标记法
并发标记 - 清除 - 整理过程中结合复制算法(优化标记整理算法),ZGC 在标记、转移和重定位阶段几乎都是并发的
跨代引用:
目的-避免全堆扫描,同时确保引用关系的正确性
记忆集:每个年轻代区域都有一个对应的记忆集,用来记录老年代对象中指向该区域的引用
写屏障:在老年代对象产生新引用(或删除引用)时,通过写屏障将这些引用变化记录到记忆集中
根植扫描:根集扫描是在GC启动时(标记初始阶段),扫描根集(GC Roots)以识别老年代引用年轻代对象的情况
CMS 适用场景:
- 低延迟需求:适用于对停顿时间要求敏感的应用程序。
- 老生代收集:主要针对老年代的垃圾回收。
- 碎片化管理:容易出现内存碎片,可能需要定期进行 Full GC 来压缩内存空间。
G1 适用场景:
- 大堆内存:适用于需要管理大内存堆的场景,能够有效处理数 GB 以上的堆内存。
- 对内存碎片敏感:G1 通过紧凑整理来减少内存碎片,降低了碎片化对性能的影响。
- 比较平衡的性能:G1 在提供较低停顿时间的同时,也保持了相对较高的吞吐量
知道JVM的垃圾收集算法吗?说一下
标记-清除算法
标记-清除(Mark-and-Sweep)算法分为“标记(Mark)”和“清除(Sweep)”阶段:首先标记出所有不需要回收的对象,在标记完成后统一回收掉所有没有被标记的对象。
它是最基础的收集算法,后续的算法都是对其不足进行改进得到。这种垃圾收集算法会带来两个明显的问题:
1效率问题:标记和清除两个过程效率都不高。
2空间问题:标记清除后会产生大量不连续的内存碎片。
标记-复制算法(分成一半)
为了解决标记-清除算法的效率和内存碎片问题,复制(Copying)收集算法出现了。
它可以将内存分为大小相同的两块,每次使用其中的一块。
当这一块的内存使用完后,就将还存活的对象复制到另一块去,然后再把使用的空间一次清理掉。这样就使每次的内存回收都是对内存区间的一半进行回收。
虽然改进了标记-清除算法,但依然存在下面这些问题:
●可用内存变小:可用内存缩小为原来的一半。
●不适合老年代:如果存活对象数量比较大,复制性能会变得很差
标记-整理算法
标记-整理(Mark-and-Compact)算法是根据老年代的特点提出的一种标记算法,标记过程仍然与“标记-清除”算法一样
但后续步骤不是直接对可回收对象回收,而是让所有存活的对象向一端移动,然后直接清理掉端边界以外的内存
由于多了整理这一步,因此效率也不高,适合老年代这种垃圾回收频率不是很高的场景
分代收集算法
当前虚拟机的垃圾收集都采用分代收集算法,这种算法没有什么新的思想,只是根据对象存活周期的不同将内存分为几块。一般将 Java 堆分为新生代和老年代,这样我们就可以根据各个年代的特点选择合适的垃圾收集算法。
比如在新生代中,每次收集都会有大量对象死去,所以可以选择”标记-复制“算法,只需要付出少量对象的复制成本就可以完成每次垃圾收集。而老年代的对象存活几率是比较高的,而且没有额外的空间对它进行分配担保,所以我们必须选择“标记-清除”或“标记-整理”算法进行垃圾收集
什么是垃圾回收器
如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。
虽然我们对各个收集器进行比较,但并非要挑选出一个最好的收集器。因为直到现在为止还没有最好的垃圾收集器出现,更加没有万能的垃圾收集器,我们能做的就是根据具体应用场景选择适合自己的垃圾收集器。试想一下:如果有一种四海之内、任何场景下都适用的完美收集器存在,那么我们的 HotSpot 虚拟机就不会实现那么多不同的垃圾收集器了
说一下JDK默认垃圾回收器
- JDK 8: Parallel Scavenge(新生代)+ Parallel Old(老年代)
- JDK8: Serial +Serial Old
- JDK 9 ~ JDK22: G1
新生代和老年代的垃圾回收算法
新生代:标记-复制算法
老年代:标记-整理算法
Serial收集器
Serial(串行)收集器是最基本、历史最悠久的垃圾收集器了。
大家看名字就知道这个收集器是一个单线程收集器了。
它的 “单线程” 的意义不仅仅意味着它只会使用一条垃圾收集线程去完成垃圾收集工作,更重要的是它在进行垃圾收集工作的时候必须暂停其他所有的工作线程( "Stop The World" ),直到它收集结束。
新生代采用标记-复制算法,老年代采用标记-整理算法。
Serial 收集器
虚拟机的设计者们当然知道 Stop The World 带来的不良用户体验,所以在后续的垃圾收集器设计中停顿时间在不断缩短(仍然还有停顿,寻找最优秀的垃圾收集器的过程仍然在继续)。
但是 Serial 收集器有没有优于其他垃圾收集器的地方呢?当然有,它简单而高效(与其他收集器的单线程相比)。
Serial 收集器由于没有线程交互的开销,自然可以获得很高的单线程收集效率。
Serial 收集器对于运行在 Client 模式下的虚拟机来说是个不错的选择
ParNew收集器
ParNew 收集器其实就是 Serial 收集器的多线程版本,除了使用多线程进行垃圾收集外,其余行为(控制参数、收集算法、回收策略等等)和 Serial 收集器完全一样。
新生代采用标记-复制算法,老年代采用标记-整理算法。
ParNew 收集器
它是许多运行在 Server 模式下的虚拟机的首要选择,除了 Serial 收集器外,只有它能与 CMS 收集器(真正意义上的并发收集器,后面会介绍到)配合工作。
并行和并发概念补充:
- 并行(Parallel):指多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态。
- 并发(Concurrent):指用户线程与垃圾收集线程同时执行(但不一定是并行,可能会交替执行),用户程序在继续运行,而垃圾收集器运行在另一个 CPU 上
Parallel Scavenge收集器
Parallel Scavenge 收集器也是使用标记-复制算法的多线程收集器,它看上去几乎和 ParNew 都一样
那么它有什么特别之处呢?
Parallel Scavenge 收集器关注点是吞吐量(高效率的利用 CPU)。
CMS 等垃圾收集器的关注点更多的是用户线程的停顿时间(提高用户体验)。
所谓吞吐量就是 CPU 中用于运行用户代码的时间与 CPU 总消耗时间的比值。
Parallel Scavenge 收集器提供了很多参数供用户找到最合适的停顿时间或最大吞吐量,如果对于收集器运作不太了解,手工优化存在困难的时候,使用 Parallel Scavenge 收集器配合自适应调节策略,把内存管理优化交给虚拟机去完成也是一个不错的选择。
新生代采用标记-复制算法,老年代采用标记-整理算法。
Parallel Old收集器运行示意图
这是 JDK1.8 默认收集器
Serial Old收集器
Serial 收集器的老年代版本,它同样是一个单线程收集器。
它主要有两大用途:一种用途是在 JDK1.5 以及以前的版本中与 Parallel Scavenge 收集器搭配使用,另一种用途是作为 CMS 收集器的后备方案。
Parallel Old收集器
Parallel Scavenge 收集器的老年代版本。使用多线程和“标记-整理”算法。在注重吞吐量以及 CPU 资源的场合,都可以优先考虑 Parallel Scavenge 收集器和 Parallel Old 收集器。
CMS收集器
是JDK1.4引入的
CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。它非常符合在注重用户体验的应用上使用。
CMS(Concurrent Mark Sweep)收集器是 HotSpot 虚拟机第一款真正意义上的并发收集器
它第一次实现了让垃圾收集线程与用户线程(基本上)同时工作。
从名字中的Mark Sweep这两个词可以看出,CMS 收集器是一种 “标记-清除”算法实现的,它的运作过程相比于前面几种垃圾收集器来说更加复杂一些。整个过程分为四个步骤:
- 初始标记: 暂停所有的其他线程,并记录下直接与 root 相连的对象,速度很快 ;
- 并发标记: 同时开启 GC 和用户线程,用一个闭包结构去记录可达对象。但在这个阶段结束,这个闭包结构并不能保证包含当前所有的可达对象。因为用户线程可能会不断的更新引用域,所以 GC 线程无法保证可达性分析的实时性。所以这个算法里会跟踪记录这些发生引用更新的地方。
- 重新标记: 重新标记阶段就是为了修正并发标记期间因为用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段的时间稍长,远远比并发标记阶段时间短
- 并发清除: 开启用户线程,同时 GC 线程开始对未标记的区域做清扫
CMS 收集器
从它的名字就可以看出它是一款优秀的垃圾收集器,主要优点:并发收集、低停顿。但是它有下面三个明显的缺点:
- 对 CPU 资源敏感;
- 无法处理浮动垃圾;
- 它使用的回收算法-“标记-清除”算法会导致收集结束时会有大量空间碎片产生。
CMS 垃圾回收器在 Java 9 中已经被标记为过时(deprecated),并在 Java 14 中被移除
说一下分代收集器CMS
什么是CMS收集器
以获取最短回收停顿时间为目标,采用“标记-清除”算法,分 4 大步进行垃圾收集,其中初始标记和重新标记会 STW,JDK 1.5 时引入,JDK9 被标记弃用,JDK14 被移除,详情可见 JEP 363。
CMS(Concurrent Mark Sweep)垃圾收集器是第一个关注 GC 停顿时间(STW 的时间)的垃圾收集器
之前的垃圾收集器,要么是串行的垃圾回收方式,要么只关注系统吞吐量
为什么CMS能够实现对 GC 停顿时间的控制
CMS 垃圾收集器之所以能够实现对 GC 停顿时间的控制,其本质来源于对「可达性分析算法」的改进,即三色标记算法
在 CMS 出现之前,无论是 Serious 垃圾收集器,还是 ParNew 垃圾收集器,以及 Parallel Scavenge 垃圾收集器,它们在进行垃圾回收期间都需要 Stop the World,无法实现垃圾回收线程与用户线程的并发执行。
(也就是我们的CMS垃圾回收器进行垃圾回收并不是整个过程就是STW,而是部分过程STW,减少了我们的GC的停顿的时间)
也就是我们的CMS实现了并行通过通过三色标记法解决了Stop The World
CMS的四个步骤
CMS 垃圾收集器通过三色标记算法,实现了垃圾回收线程与用户线程的并发执行,从而极大地降低了系统响应时间,提高了强交互应用程序的体验。它的运行过程分为 4 个步骤,包括:
- 初始标记
- 并发标记
- 重新标记
- 并发清除
初始标记
指的是寻找所有被 GCRoots 引用的对象,该阶段需要「Stop the World」
这个步骤仅仅只是标记一下 GC Roots 能直接关联到的对象,并不需要做整个引用的扫描,因此速度很快。
并发标记
指的是对「初始标记阶段」标记的对象进行整个引用链的扫描,该阶段不需要「Stop the World」。 对整个引用链做扫描需要花费非常多的时间,因此通过垃圾回收线程与用户线程并发执行,可以降低垃圾回收的时间。
这也是 CMS 能极大降低 GC 停顿时间的核心原因,但这也带来了一些问题
即:并发标记的时候,引用可能发生变化,因此可能发生漏标(本应该回收的垃圾没有被回收)和多标(本不应该回收的垃圾被回收)了。
重新标记
指的是对「并发标记」阶段出现的问题进行校正,该阶段需要「Stop the World」
正如并发标记阶段说到的,由于垃圾回收算法和用户线程并发执行,虽然能降低响应时间
但是会发生漏标和多标的问题
所以对于 CMS 来说,它需要在这个阶段做一些校验,解决并发标记阶段发生的问题。
并发清除
指的是将标记为垃圾的对象进行清除,该阶段不需要「Stop the World」。 在这个阶段,垃圾回收线程与用户线程可以并发执行,因此并不影响用户的响应时间。
说一下CMS垃圾回收的四大步骤
初始标记
并发标记
重新标记(检验错误)
并发清除
说一下分代收集器CMS的优缺点
优点
并发收集
低停顿(减少了Stop The World)
缺点
①对 CPU 资源非常敏感
因此在 CPU 资源紧张的情况下,CMS 的性能会大打折扣。
默认情况下,CMS 启用的垃圾回收线程数是(CPU数量 + 3)/4
,当 CPU 数量很大时,启用的垃圾回收线程数占比就越小。但如果 CPU 数量很小,例如只有 2 个 CPU,垃圾回收线程占用就达到了 50%,这极大地降低系统的吞吐量,无法接受。
②CMS 采用的是「标记-清除」算法
会产生大量的内存碎片,导致空间不连续,当出现大对象无法找到连续的内存空间时,就会触发一次 Full GC,这会导致系统的停顿时间变长
③CMS 无法处理浮动垃圾
当 CMS 在进行垃圾回收的时候,应用程序还在不断地产生垃圾,这些垃圾会在 CMS 垃圾回收结束之后产生,这些垃圾就是浮动垃圾,CMS 无法处理这些浮动垃圾,只能在下一次 GC 时清理掉
CMS采用的是什么算法?
CMS是老年代回收器
标记清除法
CMS怎么处理浮动垃圾
CMS处理不了浮动垃圾
而且我们的CMS没有stop the world,所以用户线程和垃圾回收线程并行执行的时候,仍然会生成浮动垃圾
所以当我们浮动垃圾过多的时候,我们会退化Serial垃圾回收器,然后我们Stop The World进行垃圾回收
说一下什么是三色标记法
三色标记法解决了什么
三色标记算法其本质是对「可达性分析算法」的改进
在 CMS 出现之前,无论是 Serious 垃圾收集器,还是 ParNew 垃圾收集器,以及 Parallel Scavenge 垃圾收集器,它们在进行垃圾回收期间都需要 Stop the World,无法实现垃圾回收线程与用户线程的并发执行
CMS 垃圾收集器通过三色标记算法,实现了垃圾回收线程与用户线程的并发执行,从而极大地降低了系统响应时间,提高了强交互应用程序的体验
三色标记法简介
三色标记法是一种常用于垃圾回收器(GC)中的标记清除算法的实现方式,用于追踪对象的可达性
通过对对象染色(白、灰、黑)分类,算法能够高效地标记活动对象并清理不可达对象。
三色标记的定义
- 白色:表示对象尚未被访问。初始时,所有对象都是白色。垃圾回收结束时,白色对象即为不可达对象,需被回收。
- 灰色:表示对象已被访问,但其引用的对象未被完全扫描。灰色对象需要进一步扫描。
- 黑色:表示对象已被访问,且其引用的对象已全部扫描完成。黑色对象不会再被重新扫描,也不会被回收。
工作流程
- 初始化阶段:将所有对象染成白色,将根对象(GC Roots)染成灰色
- 扫描阶段:从灰色对象开始扫描:
- 将灰色对象的引用对象染成灰色;
- 将灰色对象自身染成黑色。
- 清理阶段:当没有灰色对象时,白色对象即为不可达对象,回收它们
三色标记法的优点
三色标记法的优点
- 高效处理可达性:三色标记法通过颜色分类避免重复扫描已经处理的对象,提升效率。
- 并发性:三色标记法适合并发垃圾回收,GC线程与用户线程可以同时工作。
- 清晰性:标记过程逻辑清晰,容易实现分阶段操作。
- 让我们不用整个垃圾回收期间都Stop The World
三色标记法的缺点
1. 漂移问题(漏标问题)
- 如果在并发过程中,用户线程修改了灰色对象的引用(删除了对白色对象的引用),导致某些白色对象仍然可达,但被错误地回收。
- 人话:漏标导致了可达对象被错误回收
漏标的详细过程
以下是一个具体的“漏标问题”的场景:
- 初始状态:
- 假设有一个灰色对象
A
,它引用了一个白色对象B
。 - 白色对象
B
暂时还未被扫描,因此仍然是“待回收”的候选。
- 用户线程修改引用:
- 在GC线程对灰色对象
A
进行扫描之前,用户线程删除了A
指向B
的引用。 - 删除后,从
A
不再能够访问到B
。
- GC线程扫描:
- GC线程扫描到
A
时,发现A
的引用列表中已经没有B
,因此不会将B
标记为灰色。 - 此时,
B
依然是白色。
- 错误回收:
- 如果
B
还有其他途径可达(例如另一个对象C
引用了B
),但是GC线程没有标记到B
,就会将B
视为不可达,最终将其回收。 - 这种情况导致了错误回收,因为
B
实际上仍然是可达的。
2. 暂停时间不确定
- 在单线程实现中,标记阶段可能需要扫描大量对象,可能导致GC暂停时间较长。
三色标记法漂移问题的解决方法
漂移问题的解决方法
其实也就是引用或删除时多做几步处理
写屏障:在用户线程修改引用时,通知GC线程,使得GC能够追踪这些引用的变化
- 增量更新法:当从黑色对象添加引用时,将被引用对象染成灰色,保证新对象能被正确标记
- 删除插入法:当删除灰色对象指向白色对象的引用时,提前将删除前的白色对象染成灰色,避免错误回收
读屏障:在用户线程访问引用时进行干预,确保GC线程的对象状态不受影响。
- 更适合实时GC,但实现复杂且开销较大
三色标记法的目的是什么?
三色标记法的主要目的是通过有效地标记节点状态来实现图的遍历、环路检测、依赖关系处理等功能
从而提高算法的效率和可靠性
G1收集器
什么是G1收集器
G1 (Garbage-First) 是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器. 以极高概率满足 GC 停顿时间要求的同时,还具备高吞吐量性能特征.
被视为 JDK1.7 中 HotSpot 虚拟机的一个重要进化特征。它具备以下特点:
- 并行与并发:G1 能充分利用 CPU、多核环境下的硬件优势,使用多个 CPU(CPU 或者 CPU 核心)来缩短 Stop-The-World 停顿时间。部分其他收集器原本需要停顿 Java 线程执行的 GC 动作,G1 收集器仍然可以通过并发的方式让 java 程序继续执行。
- 分代收集:虽然 G1 可以不需要其他收集器配合就能独立管理整个 GC 堆,但是还是保留了分代的概念。
- 空间整合:与 CMS 的“标记-清除”算法不同,G1 从整体来看是基于“标记-整理”算法实现的收集器;从局部上来看是基于“标记-复制”算法实现的。
- 可预测的停顿:这是 G1 相对于 CMS 的另一个大优势,降低停顿时间是 G1 和 CMS 共同的关注点,但 G1 除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为 M 毫秒的时间片段内,消耗在垃圾收集上的时间不得超过 N 毫秒
并发+分代收集思想+可预测的停顿
G1 收集器运作的步骤
- 初始标记
- 并发标记
- 最终标记
- 筛选回收
额外知识
G1 收集器在后台维护了一个优先列表,每次根据允许的收集时间,优先选择回收价值最大的 Region(这也就是它的名字 Garbage-First 的由来)
这种使用 Region 划分内存空间以及有优先级的区域回收方式,保证了 G1 收集器在有限时间内可以尽可能高的收集效率(把内存化整为零)。
从 JDK9 开始,G1 垃圾收集器成为了默认的垃圾收集器
说一下G1垃圾收集器
Region的意思是区域
G1(Garbage-First Garbage Collector)在 JDK 1.7 时引入
在 JDK 9 时成为了默认的垃圾收集器
Young GC、Mixed GC 用标记-复制算法
Full GC用标记-整理法
说一下G1的五个属性
G1 有五个属性:分代、增量、并行、标记整理、STW
①、分代
相信大家还记得我们上一讲中的年轻代和老年代,G1 也是基于这个思想进行设计的。
它将堆内存分为多个大小相等的区域(Region)
每个区域都可以是 Eden 区、Survivor 区或者 Old 区。
可以通过 -XX:G1HeapRegionSize=n
来设置 Region 的大小,可以设定为 1M、2M、4M、8M、16M、32M(不能超过)。
Humongous区专门分配大对象
G1 有专门分配大对象的 Region 叫 Humongous 区,而不是让大对象直接进入老年代的 Region 中。
在 G1 中,大对象的判定规则就是一个大对象超过了一个 Region 大小的 50%,比如每个 Region 是 2M,只要一个对象超过了 1M,就会被放入 Humongous 中
而且一个大对象如果太大,可能会横跨多个 Region 来存放。
G1 会根据各个区域的垃圾回收情况来决定下一次垃圾回收的区域,这样就避免了对整个堆内存进行垃圾回收,从而降低了垃圾回收的时间。
②、增量方式回收垃圾
G1 可以以增量方式执行垃圾回收 这意味着它不需要一次性回收整个堆空间,而是可以逐步、增量地清理。 有助于控制停顿时间,尤其是在处理大型堆时。
③、并行
G1 垃圾回收器可以并行回收垃圾,这意味着它可以利用多个 CPU 来加速垃圾回收的速度,这一特性在年轻代的垃圾回收(Minor GC)中特别明显,因为年轻代的回收通常涉及较多的对象和较高的回收速率。
④、标记整理
在进行老年代的垃圾回收时,G1 使用标记-整理算法。这个过程分为两个阶段:标记存活的对象和整理(压缩)堆空间。通过整理,G1 能够避免内存碎片化,提高内存利用率。
年轻代的垃圾回收(Minor GC)使用 标记-复制算法 ,因为年轻代的对象通常是朝生夕死的。
⑤、STW(Stop The World)
G1 也是基于「标记-清除」算法,因此在进行垃圾回收的时候,仍然需要「Stop the World」。不过,G1 在停顿时间上添加了预测机制,用户可以指定期望停顿时间。
说一下GC中存在的三种模式
G1 中存在三种 GC 模式,分别是 Young GC、Mixed GC 和 Full GC
简单来说:
Young GC
当 Eden 区的内存空间无法支持新对象的内存分配时,G1 会触发 Young GC。
当需要分配对象到 Humongous 区域或者堆内存的空间
(我们指的是当前这个区域的对象总和对总的空间占比,而不是单个空间对这个区域的占比)
占比超过 -XX:G1HeapWastePercent
设置的 InitiatingHeapOccupancyPercent 值时,G1 会触发一次 concurrent marking
它的作用就是计算老年代中有多少空间需要被回收,当发现垃圾的占比达到 -XX:G1HeapWastePercent
中所设置的 G1HeapWastePercent 比例时,在下次 Young GC 后会触发一次 Mixed GC。
Mixed GC
Mixed GC 是指回收年轻代的 Region 以及一部分老年代中的 Region。Mixed GC 和 Young GC 一样,采用的也是复制算法。
在 Mixed GC 过程中,如果发现老年代空间还是不足,此时如果 G1HeapWastePercent 设定过低,可能引发 Full GC。-XX:G1HeapWastePercent
默认是 5,意味着只有 5% 的堆是“浪费”的。
Full GC
如果浪费的堆的百分比大于 G1HeapWastePercent,则运行 Full GC。
G1垃圾收集器的停顿预测模型
在以 Region 为最小管理单元以及所采用的 GC 模式的基础上,G1 建立了停顿预测模型,即 Pause Prediction Model 。这也是 G1 非常被人所称道的特性。
我们可以借助 -XX:MaxGCPauseMillis
来设置期望的停顿时间(默认 200ms)
G1 会根据这个值来计算出一个合理的 Young GC 的回收时间,然后根据这个时间来制定 Young GC 的回收计划
你了解G1垃圾回收器吗?说一下
G1垃圾回收器的属性+特点+基本知识
JDK9
G1垃圾回收器在JDK9的时候取代了我们的CMS垃圾回收器,成为了我们的默认的垃圾回收器
存储空间部分不分代,回收思想分代
G1回收器我们的存储空间不是分代的,也就是我们的Region不是分为新生代,老年代这样的,
而是分为多个大小相等的Region,每个Region都可以是我们的Eden,Surivor,Old区域。
虽然我们的存储空间不是分代的,但是我们的回收思想是分代的
分配大对象的特点:Humongous区
之前我们的分代收集法,我们是大对象放不进新生代我们就直接进入老年代
但我们的G1 有专门分配大对象的 Region 叫 Humongous 区
在 G1 中,大对象的判定规则就是一个大对象超过了一个 Region 大小的 50%,比如每个 Region 是 2M,只要一个对象超过了 1M,就会被放入 Humongous 中
逐步回收+并行回收
G1垃圾回收器 会根据各个区域的垃圾回收情况来决定下一次垃圾回收的区域,这样就避免了对整个堆内存进行垃圾回收,从而降低了垃圾回收的时间。
(因为我们是分成多个相等大小的Region,所以我们上次回收后,我们回收器可以根据回收情况自我决定下次回收的区域,这样避免某些区域重复回收,提高了我们的垃圾回收的效率)
G1垃圾回收器可以并行地进行垃圾回收
用到的垃圾回收算法
Young GC Mixed GC用标记复制法
Full GC用标记整理法
其实标记整理法和标记复制法本质上是一个东西,不过标记整理多了整理这一个步骤导致效率降低,适用于老年代,因为老年代不经常回收效率低一点无所谓,而且标记复制会让使用空间砍半,本来老年代就是要放大部分对象的,所以不使用,效率低一点没事,我们存的东西也多了
新生代的话我们经常进行垃圾回收,所以我们要效率高一点的标记复制法
Stop The World
G1垃圾回收器仍然需要Stop The World,但我们的G1垃圾回收器有停顿预测模型
我们G1有个停顿时间预测机制,我们可以手动指定一个期望停顿的时间,然后我们的G1垃圾回收器会根据这个时间来指定合理的垃圾回收计划
G1进行垃圾回收的三种模式
Young GC
当Eden区域无法存放新对象的时候,我们就进行Young GC
当我们的当前对象+准备放入的区域里对象的占用空间总和 > 设置的XX值,G1就会触发一次Concurrent Marking
Concurrent Marking的作用就是计算老年代中有多少空间需要被回收
Mixed GC
当发现垃圾的占比达到 -XX:G1HeapWastePercent
中所设置的 G1HeapWastePercent 比例时,在下次 Young GC 后会触发一次 Mixed GC
Mixed GC 是指回收年轻代的 Region 以及一部分老年代中的 Region
Mixed GC 和 Young GC 一样,采用的也是复制算法
在 Mixed GC 过程中,如果发现老年代空间还是不足,此时如果 G1HeapWastePercent 设定过低,可能引发 Full GC。-XX:G1HeapWastePercent
默认是 5,意味着只有 5% 的堆是“浪费”的
Full GC
如果浪费的堆的百分比大于 G1HeapWastePercent,则运行 Full GC。
ZGC收集器
与 CMS 中的 ParNew 和 G1 类似,ZGC 也采用标记-复制算法,不过 ZGC 对该算法做了重大改进。
ZGC 可以将暂停时间控制在几毫秒以内,且暂停时间不受堆内存大小的影响
出现 Stop The World 的情况会更少,但代价是牺牲了一些吞吐量。
ZGC 最大支持 16TB 的堆内存。
ZGC 在 Java11 中引入,处于试验阶段。经过多个版本的迭代,不断的完善和修复问题,ZGC 在 Java15 已经可以正式使用了
说一下ZGC垃圾回收器
什么是ZGC
ZGC(The Z Garbage Collector)是 JDK11 推出的一款低延迟垃圾收集器,适用于大内存低延迟服务的内存管理和回收,SPEC jbb 2015 基准测试,在 128G 的大堆下,最大停顿时间才 1.68 ms,停顿时间远胜于 G1 和 CMS。
ZGC 的设计目标是:在不超过 10ms 的停顿时间下,支持 TB 级的内存容量和几乎所有的 GC 功能,这也是 ZGC 名字的由来,Z 代表着 Zettabyte,也就是 1024EB,也就是 1TB 的 1024 倍。
不过,我需要告诉大家的是,上面这段是我胡编的(😂),JDK 官方并没有明确给出 Z 的定义,就像小米汽车 su7,7 也是个魔数,没有明确的定义。
总之就是,ZGC 很牛逼,它的目标是:
- 停顿时间不超过 10ms;
- 停顿时间不会随着堆的大小,或者活跃对象的大小而增加;
- 支持 8MB~4TB 级别的堆,未来支持 16TB。
复制算法
前面讲 G1 垃圾收集器的时候提到过,Young GC 和 Mixed GC 均采用的是复制算法
复制算法主要包括以下 3 个阶段:
①、标记阶段,从 GC Roots 开始,分析对象可达性,标记出活跃对象。
②、对象转移阶段,把活跃对象复制到新的内存地址上。
③、重定位阶段,因为转移导致对象地址发生了变化,在重定位阶段,所有指向对象旧地址的引用都要调整到对象新的地址上。
标记阶段因为只标记 GC Roots,耗时较短。但转移阶段和重定位阶段需要处理所有存活的对象,耗时较长,并且转移阶段是 STW 的
因此,G1 的性能瓶颈就主要卡在转移阶段。
与 G1 和 CMS 类似,ZGC 也采用了复制算法,只不过做了重大优化
ZGC 在标记、转移和重定位阶段几乎都是并发的
这是 ZGC 实现停顿时间小于 10ms 的关键所在。
ZGC是怎么做到的
指针染色+读屏障提高速度,保证对象访问的正确性
ZGC 是怎么做到的呢?
- 指针染色(Colored Pointer):一种用于标记对象状态的技术
- 读屏障(Load Barrier):一种在程序运行时插入到对象访问操作中的特殊检查,用于确保对象访问的正确性
这两种技术可以让所有线程在并发的条件下就指针的颜色 (状态) 达成一致,而不是对象地址
因此,ZGC 可以并发的复制对象,这大大的降低了 GC 的停顿时间
指针染色
在一个指针中,除了存储对象的实际地址外,还有额外的位被用来存储关于该对象的元数据信息
这些信息可能包括:
- 对象是否被移动了(即它是否在回收过程中被移动到了新的位置)
- 对象的存活状态
- 对象是否被锁定或有其他特殊状态
通过在指针中嵌入这些信息,ZGC 在标记和转移阶段会更快
因为通过指针上的颜色就能区分出对象状态,不用额外做内存访问
ZGC仅支持64位系统,它把64位虚拟地址空间划分为多个子空间,如下图所示:
其中,0-4TB 对应 Java 堆,4TB-8TB 被称为 M0 地址空间,8TB-12TB 被称为 M1 地址空间,12TB-16TB 预留未使用,16TB-20TB 被称为 Remapped 空间
当创建对象时,首先在堆空间申请一个虚拟地址,该虚拟地址并不会映射到真正的物理地址
同时,ZGC 会在 M0、M1、Remapped 空间中为该对象分别申请一个虚拟地址,且三个虚拟地址都映射到同一个物理地址
读屏障
当程序尝试读取一个对象时,读屏障会触发以下操作:
- 检查指针染色:读屏障首先检查指向对象的指针的颜色信息
- 处理移动的对象:如果指针表示对象已经被移动(例如,在垃圾回收过程中),读屏障将确保返回对象的新位置
- 确保一致性:通过这种方式,ZGC 能够在并发移动对象时保持内存访问的一致性,从而减少对应用程序停顿的需要
ZGC读屏障如何实现呢?
来看下面这段伪代码,涉及 JVM 的底层 C++ 代码:
// 伪代码示例,展示读屏障的概念性实现
Object* read_barrier(Object* ref) {
if (is_forwarded(ref)) {
return get_forwarded_address(ref); // 获取对象的新地址
}
return ref; // 对象未移动,返回原始引用
}
- read_barrier 代表读屏障
- 如果对象已被移动(is_forwarded(ref)),方法返回对象的新地址(get_forwarded_address(ref))
- 如果对象未被移动,方法返回原始的对象引用
读屏障可能被GC线程和业务线程触发,并且只会在访问堆内对象时触发
访问的对象位于GC Roots时不会触发,这也是扫描GC Roots时需要STW的原因
下面是一个简化的示例代码,展示了读屏障的触发时机
Object o = obj.FieldA // 从堆中读取引用,需要加入屏障
<Load barrier>
Object p = o // 无需加入屏障,因为不是从堆中读取引用
o.dosomething() // 无需加入屏障,因为不是从堆中读取引用
int i = obj.FieldB //无需加入屏障,因为不是对象引用
下图是虚拟地址的空间划分:
不过,三个空间在同一时间只有一个空间有效。ZGC 之所以设置这三个虚拟地址,是因为 ZGC 采用的是“空间换时间”的思想,去降低 GC 的停顿时间
与上述地址空间划分相对应,ZGC实际仅使用64位地址空间的第0-41位,而第42-45位存储元数据,第47-63位固定为0
由于仅用了第 0~43 位存储对象地址,$2^{44}$ = 16TB,所以 ZGC 最大支持 16TB 的堆
至于对象的存活信息,则存储在42-45位中,这与传统的垃圾回收并将对象存活信息放在对象头中完全不同
说一下ZGC的工作过程
ZGC 周期由
三个 STW 暂停:
标记开始
重新映射开始
暂停结束
和四个并发阶段组成:
标记/重新映射
并发引用处理
并发转移准备
并发转移
Stop-The-World 暂停阶段
- 标记开始(Mark Start)STW 暂停:这是 ZGC 的开始,进行 GC Roots 的初始标记。在这个短暂的停顿期间,ZGC 标记所有从 GC Root 直接可达的对象。
- 重新映射开始(Relocation Start)STW 暂停:在并发阶段之后,这个 STW 暂停是为了准备对象的重定位。在这个阶段,ZGC 选择将要清理的内存区域,并建立必要的数据结构以进行对象移动。
- 暂停结束(Pause End)STW 暂停:ZGC 结束。在这个短暂的停顿中,完成所有与该 GC 周期相关的最终清理工作。
并发阶段
- 并发标记/重新映射 (M/R) :这个阶段包括并发标记和并发重新映射。在并发标记中,ZGC 遍历对象图,标记所有可达的对象。然后,在并发重新映射中,ZGC 更新指向移动对象的所有引用。
- 并发引用处理 (RP) :在这个阶段,ZGC 处理各种引用类型(如软引用、弱引用、虚引用和幽灵引用)。这些引用的处理通常需要特殊的考虑,因为它们与对象的可达性和生命周期密切相关。
- 并发转移准备 (EC) :这是为对象转移做准备的阶段。ZGC 确定哪些内存区域将被清理,并准备相关的数据结构。
- 并发转移 (RE) :在这个阶段,ZGC 将存活的对象从旧位置移动到新位置。由于这一过程是并发执行的,因此应用程序可以在大多数垃圾回收工作进行时继续运行。
ZGC 的两个关键技术:指针染色和读屏障,不仅应用在并发转移阶段,还应用在并发标记阶段:
将对象设置为已标记,传统的垃圾回收器需要进行一次内存访问,并将对象存活信息放在对象头中;
而在ZGC中,只需要设置指针地址的第42-45位即可,并且因为是寄存器访问,所以速度比访问内存更快。
你就简单介绍一下垃圾回收器吧
介绍一下默认的垃圾回收器的转变吧
JDK1.8之前
对于64位系统,我们默认是Parallel Scavenge收集器+Parallel Old收集器
对于32位系统,我们默认是Serial收集器+Serial Old收集器
在JDK9以及之后
不管64位还是32位系统,我们默认的垃圾收集器都是G1收集器
其实除了默认的垃圾收集器我们还有一些其他的垃圾收集器
例如我们的ParNew收集器(Serial串行收集器的并发版本)
CMS收集器,ZGC收集器
为什么我们JDK1.8之前用Parallel Scavenge收集器呢
是因为我们的Parallel Scavenge收集器比Serial和ParNew收集器的吞吐量更大
然后你可能会问为什么不用CMS作为默认垃圾回收器?
1.因为CMS是在JDK5被引入的,JDK9被弃用,JDK14被移除
2.因为吞吐量的区别,以及垃圾回收器设计目标的区别
Paralled组合更注重吞吐量,本来Paralled吞吐量就高
而CMS的关注点是用户线程的停顿时间(提高用户体验)
为什么要用G1回收器来代替我们的Parallel回收器
G1垃圾回收器是在JDK7被引入,然后JDK9被当成了默认的垃圾回收器
首先我们就要说一下G1的特点和优点了
1.它有一个停顿预测模型,CMS可以根据设置的预测停顿时间来合理规划GC回收
2.我们之前是存储空间划分成新生代和老年代的。
但是我们的G1是划分为多个相等大小的Region,而我们的垃圾回收思想才是分代回收。
也就是物理上不分代逻辑上分代回收
3.因为我们分为多个Region,所以我们的G1垃圾回收器还有个逐步回收功能
G1垃圾回收器 会根据各个区域的垃圾回收情况来决定下一次垃圾回收的区域,这样就避免了对整个堆内存进行垃圾回收,从而降低了垃圾回收的时间
不同回收器的垃圾回收算法
Serial:新生代采用标记-复制算法,老年代采用标记-整理算法
ParNew(Serial的多线程版本):新生代采用标记-复制算法,老年代采用标记-整理算法
Parallel Scavenge:新生代采用标记-复制算法,老年代采用标记-整理算法
CMS:标记-清除算法之三色标记法
G1:Young GC、Mixed GC 用标记-复制算法、Full GC用标记-整理法
ZGC:标记-清除算法之三色标记法,并发标记 - 清除 - 整理过程中结合复制算法
什么是跨代引用
在现代垃圾回收算法中,跨代引用(Cross-Generational References)是指不同代的对象之间的引用关系
垃圾回收器通常将堆内存划分为年轻代(Young Generation)、老年代(Old Generation),甚至还有永久代(PermGen)或元空间(Metaspace),以优化内存管理和回收效率
然而,当一个代的对象引用另一个代的对象时,这种跨代引用可能会带来问题
尤其是在回收年轻代时,如何快速识别被老年代引用的对象是一个关键问题
跨代引用的问题
- 年轻代GC的高效性依赖于对象分代:
- 年轻代通常回收频率高,但扫描范围小(只扫描年轻代)。
- 如果老年代的对象引用年轻代的对象,GC需要特别处理以避免误判为垃圾。
- 全堆扫描的开销:
- 如果垃圾回收器为了追踪跨代引用,需要扫描整个老年代寻找对年轻代的引用,会导致性能大幅下降。
跨代引用该如何解决(记忆集,写屏障,根集扫描)
解决方法
解决跨代引用问题的关键在于避免全堆扫描,同时确保引用关系的正确性。
以下是常见的技术:
1. 记忆集(Remembered Set, RSet)
记忆集是一种记录跨代引用的机制,主要用于跟踪哪些老年代的对象引用了年轻代的对象。
- 定义:
-
- 每个年轻代区域都有一个对应的记忆集,用来记录老年代对象中指向该区域的引用。
- 实现方式:
-
- 通常使用卡表(Card Table)来实现。内存被划分成固定大小的“卡页”(Card Page),每个卡页对应一个字节的卡表条目。
- 当老年代对象对年轻代对象产生引用时,标记对应的卡表条目为“脏”(Dirty)。
- 优点:
-
- 垃圾回收时,GC只需要扫描记忆集中记录的卡页,而不需要扫描整个老年代,显著提高效率。
2. 写屏障(Write Barrier)
写屏障是垃圾回收器用于捕获引用关系变化的一种机制,通常在跨代引用的维护中起到核心作用。
作用:
- 在老年代对象产生新引用(或删除引用)时,通过写屏障将这些引用变化记录到记忆集中。
常见策略:
- 增量更新:当一个老年代对象引用了一个年轻代对象时,立即将老年代对象所在的卡页标记为“脏”。
- 删除插入:当老年代对象的引用被移除时,将其之前指向的年轻代对象重新标记为可能可达。
优点:
- 写屏障的开销较小,能够快速捕获跨代引用的变化
3. 根集扫描(GC Roots)
根集扫描是在GC启动时,扫描根集(GC Roots)以识别老年代引用年轻代对象的情况。
适用场景:
- 主要用于初始标记阶段。
- 当跨代引用较少时,根集扫描可以简化处理。
说一下跨代引用处理的具体流程
跨代引用处理的具体流程(以HotSpot为例)
- 分代内存模型:
-
- 堆内存分为年轻代和老年代。
- 垃圾回收器在回收年轻代(Minor GC)时需要考虑哪些对象被老年代引用。
- 写屏障捕获引用变化:
-
- 当老年代对象的字段被赋值为年轻代对象时,写屏障会将这一变化记录到记忆集中。
- GC触发时扫描记忆集:
-
- 当触发Minor GC时,GC线程会扫描记忆集,只处理被标记的“脏”卡页,而非整个老年代。
- 标记和清理:
-
- GC会通过记忆集找到被老年代引用的年轻代对象,标记这些对象为存活。
- 剩余未标记的年轻代对象将被清理回收。
优缺点分析
优点:
- 避免全堆扫描:记忆集和写屏障减少了GC扫描范围,大幅提升性能。
- 分代模型效率高:年轻代GC更加快速,老年代GC的频率更低。
- 灵活适配:记忆集的设计可根据实际需求调整大小和精度。
缺点:
- 写屏障的开销:虽然小,但在写操作频繁的场景中,写屏障仍然会引入一定的性能成本。
- 记忆集维护复杂:需要额外的内存空间存储记忆集,并对其进行维护。
- 误报问题:卡表可能记录一些不相关的“脏”卡页,需要GC进一步验证。
垃圾回收算法的那些阶段会stop the world
我们拿我们的G1垃圾回收器来举例子
标记 - 复制算法应用在 CMS 新生代(ParNew 是 CMS 默认的新生代垃圾回收器)和 G1 垃圾回收器中。标记 - 复制算法可以分为三个阶段:
- 标记阶段,即从 GC Roots 集合开始,标记活跃对象;
- 转移阶段,即把活跃对象复制到新的内存地址上;
- 重定位阶段,因为转移导致对象的地址发生了变化,在重定位阶段,所有指向对象旧地址的指针都要调整到对象新的地址上。
下面以 G1 为例,通过 G1 中标记 - 复制算法过程(G1 的 Young GC 和 Mixed GC 均采用该算法),分析 G1 停顿耗时的主要瓶颈。G1 垃圾回收周期如下图所示:
G1 的混合回收过程可以分为标记阶段、清理阶段和复制阶段。
标记阶段停顿分析
- 初始标记阶段:初始标记阶段是指从 GC Roots 出发标记全部直接子节点的过程,该阶段是 STW 的。由于 GC Roots 数量不多,通常该阶段耗时非常短。
- 并发标记阶段:并发标记阶段是指从 GC Roots 开始对堆中对象进行可达性分析,找出存活对象。该阶段是并发的,即应用线程和 GC 线程可以同时活动。并发标记耗时相对长很多,但因为不是 STW,所以我们不太关心该阶段耗时的长短。
- 再标记阶段:重新标记那些在并发标记阶段发生变化的对象。该阶段是 STW 的
清理阶段停顿分析
- 清理阶段清点出有存活对象的分区和没有存活对象的分区,该阶段不会清理垃圾对象,也不会执行存活对象的复制。该阶段是 STW 的
复制阶段停顿分析
- 复制算法中的转移阶段需要分配新内存和复制对象的成员变量。转移阶段是 STW 的,其中内存分配通常耗时非常短,但对象成员变量的复制耗时有可能较长,这是因为复制耗时与存活对象数量与对象复杂度成正比。对象越复杂,复制耗时越长。
四个 STW 过程中,初始标记因为只标记 GC Roots,耗时较短。
再标记因为对象数少,耗时也较短。清
理阶段因为内存分区数量少,耗时也较短。
转移阶段要处理所有存活的对象,耗时会较长。
因此,G1 停顿时间的瓶颈主要是标记 - 复制中的转移阶段 STW
垃圾回收器CMS和G1的区别
区别一:使用的范围不一样:
CMS 收集器是老年代的收集器,可以配合新生代的 Serial 和 ParNew 收集器一起使用
G1 收集器收集范围是老年代和新生代。不需要结合其他收集器使用
区别二:STW 的时间:
CMS 收集器以最小的停顿时间为目标的收集器。
G1 收集器可预测垃圾回收的停顿时间(建立可预测的停顿时间模型)
区别三: 垃圾碎片
CMS 收集器是使用 “标记 - 清除” 算法进行的垃圾回收,容易产生内存碎片
G1 收集器使用的是 “标记 - 整理” 算法,进行了空间整合,没有内存空间碎片
区别四:垃圾回收过程不同
区别五: CMS 会产生浮动垃圾
CMS 产生浮动垃圾过多时会退化为 serial old,效率低,因为在上图的第四阶段,CMS 清除垃圾时是并发清除的,这个时候,垃圾回收线程和用户线程同时工作会产生浮动垃圾,也就意味着 CMS 垃圾回收器必须预留一部分内存空间用于存放浮动垃圾
而 G1 没有浮动垃圾,G1 的筛选回收是多个垃圾回收线程并行 gc 的,没有浮动垃圾的回收,在执行‘并发清理’步骤时,用户线程也会同时产生一部分可回收对象,但是这部分可回收对象只能在下次执行清理时才会被回收。如果在清理过程中预留给用户线程的内存不足就会出现‘Concurrent Mode Failure’, 一旦出现此错误时便会切换到 SerialOld 收集方式
什么情况下使用CMS?什么情况下使用G1?
CMS 适用场景:
- 低延迟需求:适用于对停顿时间要求敏感的应用程序。
- 老生代收集:主要针对老年代的垃圾回收。
- 碎片化管理:容易出现内存碎片,可能需要定期进行 Full GC 来压缩内存空间。
G1 适用场景:
- 大堆内存:适用于需要管理大内存堆的场景,能够有效处理数 GB 以上的堆内存。
- 对内存碎片敏感:G1 通过紧凑整理来减少内存碎片,降低了碎片化对性能的影响。
- 比较平衡的性能:G1 在提供较低停顿时间的同时,也保持了相对较高的吞吐量
G1回收器的特色是什么?
G1 的特点:
G1 最大的特点是引入分区的思路,弱化了分代的概念
合理利用垃圾收集各个周期的资源,解决了其他收集器、甚至 CMS 的众多缺陷。
G1 相比较 CMS 的改进:
算法:G1 基于标记 -- 整理算法,不会产生空间碎片,在标记 - 清除算法执行后,被回收对象的内存会分散留下许多不连续小块内存,即内存碎片。而 G1 的标记 - 整理算法通过移动存活对象,让内存连续,所以不会产生碎片,所以在分配大对象时,不会因无法得到连续的空间,而提前触发一次 FULL GC (内存碎片过多,就会导致我们没有足够的连续内存分配大对象)
停顿时间可控:G1 可以通过设置预期停顿时间(Pause Time)来控制垃圾收集时间避免应用雪崩现象
并行与并发:G1 能更充分的利用 CPU 多核环境下的硬件优势,来缩短 stop the world 的停顿时间
GC只会对堆进行回收吗
JVM 的垃圾回收器不仅仅会对堆进行垃圾回收,它还会对方法区进行垃圾回收
- 堆(Heap):堆是用于存储对象实例的内存区域。大部分的垃圾回收工作都发生在堆上,因为大多数对象都会被分配在堆上,而垃圾回收的重点通常也是回收堆中不再被引用的对象,以释放内存空间
- 方法区(Method Area):方法区是用于存储类信息、常量、静态变量等数据的区域。虽然方法区中的垃圾回收与堆有所不同,但是同样存在对不再需要的常量、无用的类信息等进行清理的过程