🦊 Java
1、Java基础
1.1 StringBuffer和StringBuilder的区别
1.2 ConcurrentHashMap和TreeMap实现原理
1.3 ArrayList和LinkedList实现原理
1.4 HashSet和TreeSet实现原理
1.5 深拷贝与浅拷贝
1.6 抽象类与接口
2、Java多线程
2.1 并发编程的三大特性
2.2 指令重排
2.3 Volatile原理
2.4 CAS原理
2.5 Java的4种引用级别
2.6 Java中的锁
2.7 Synchronized实现原理
2.8 线程池实现原理
2.9 AQS
2.10 创建线程的方式
2.11 ThreadLocal原理
3、JVM
3.1 判断对象是否存活的方法
3.2 JVM内存结构
3.3 常见的垃圾收集算法有哪些
3.4 指针碰撞和空闲列表
3.5 常见的垃圾收集器有哪些
3.6 内存溢出与内存泄漏的区别
3.7 常用的JVM启动参数有哪些
3.8 反射机制
4、NIO
4.1 概述
5、Spring
5.1 Spring IOC
5.2 Spring AOP
6、SpringBoot
6.1 SpringBoot、SpringCloud的联系与区别
-
+
tourist
register
Sign in
常见的垃圾收集器有哪些
JVM 中常见的垃圾收集器主要包括**串行 GC 收集器**(Serial GC)、**ParNew 收集器**、**并行 GC 收集器**(Parallel GC)、**CMS 收集器**(Most Concurrent Mark and Sweep Garbage Collector)、**G1 收集器**(Garbage First)五种。 ## 1 串行 GC 收集器(Serial GC) Serial 收集器主要包括**Serial 收集器**(用于年轻代)、**Serial Old 收集器**(用于老年代)两种。 ### 2.1 特点 1. 串行 GC**在年轻代使用标记-复制算法,在老年代使用标记-清除-整理算法**。 2. 两者都是**单线程**的垃圾收集器,不能进行并行处理,所以都会触发**全线暂停**(STW),停止所有的应用线程。 ### 2.2 缺点 1. 不能充分利用多核 CPU,因为不管有多少 CPU 内核,JVM 在垃圾收集时都只能使用单个核心。 ### 2.3 启用方法 ``` -XX:+UseSerialGC ``` ## 2 ParNew 收集器 ParNew 收集器是**Serial 收集器**的**多线程**版本,它是许多运行在 Server 模式下的虚拟机中首选的**新生代收集器**,因为除了 Serial 收集器外,目前只有他能与**CMS 收集器**配合工作。 ## 3 并行 GC 收集器(Parallel GC) Parallel 收集器主要包括**Parallel 收集器**(用于年轻代)、**Parallel Old 收集器**(用于老年代)两种。 ### 3.1 特点 1. 并行垃圾收集器这一类组合,**在年轻代使用标记-复制算法,在老年代使用标记-清除-整理算法**。 2. 年轻代和老年代的垃圾回收都会触发**STW**事件,暂停所有的应用线程来执行垃圾收集。 3. 两者在执行标记和复制/整理阶段都使用多个线程,通过并行执行,使得 GC 时间大幅减少。 ### 3.2 优缺点 #### 3.2.1 优点 1. 并行垃圾收集器**适用于多核服务器,主要目标是增加吞吐量**,因为对系统资源的有效使用,能达到更多的吞吐量: 1. 在 GC 期间,所有 CPU 内核都在并行清理垃圾,所以总暂停时间更短。 2. 在两次 GC 周期的间隔期,没有 GC 线程在运行,不会消耗任何系统资源。 #### 3.2.2 缺点 1. 并行垃圾收集器 GC 的所有阶段都不能中断,所以**并行 GC 很容易出现长时间的卡顿**(注:这里说的长时间也很短,一般来说 Minor GC 是毫秒级别,Full GC 是几十或几百毫秒级别)。**如果系统的主要目标是最低的停顿时间/延迟,而不是整体的吞吐量最大,那么就应该选择其他垃圾收集器组合。** ### 3.3 启用方法 ``` -XX:+UseParallelGC -XX:+UseParallelOldGC -XX:+UseParallelGC -XX:+UseParallelOldGC ``` ## 4 CMS 收集器(Most Concurrent Mark and Sweep Garbage Collector) ### 4.1 特点 1. CMS**在年轻代采用并行 STW 方式的标记-复制算法,在老年代使用并发标记-清除算法**。 2. CMS 的设计目标是**避免在老年代垃圾收集时出现长时间的停顿**,主要通过两种手段来达成此目标: 1. 不对老年代进行整理,而是采用**空闲列表**来管理内存空间的回收。 2. 在**标记-清除**阶段的大部分工作和应用线程一起并发执行,在这些阶段没有明显的应用暂停,但他仍然和应用线程争抢 CPU 时间,因此**CMS 会比并行 GC 的吞吐量差一些**。 ### 4.2 优缺点 #### 4.2.1 优点 1. **并发收集,低停顿。** #### 4.2.2 缺点 1. **对 CPU 资源非常敏感,CMS 默认的回收线程数等于(CPU 数量 +3)/4。** 2. **无法处理浮动垃圾**,并发清理阶段用户程序运行产生的垃圾过了标记阶段而无法在本次收集中清理掉,称为浮动垃圾。 3. **基于标记-清除算法会产生大量空间碎片**。 ### 4.3 各个阶段 #### 4.3.1 初始标记(Initial Mark) 1. 这个阶段**会伴随着 STW 暂停**。 2. 初始标记的目标是标记**所有的根对象(**包括**根对象****直接引用****的对象**以及**被年轻代中所有存活对象所引用的对象**)。 ![caf715d0-32ed-11ea-8c11-e7b43c5f4201 (1930×1148)](/media/202105//1621914623.9133608.png) #### 4.3.2 并发标记(Concurrent Mark) 1. 在此阶段,CMS**遍历老年代**,**标记所有存活的对象**,从前一阶段**初始标记**找到的的根对象开始算起。 2. 并发标记阶段**与应用程序同时运行,不用暂停**。 3. **并非所有老年代中存活的对象都在此阶段被标记,因为在标记过程中对象的引用关系还在发生变化**,例如,在下面的示意图中,**当前处理的对象**的一个引用就被应用线程给断开了,即这个部分的对象关系发生了变化。 ![f30d6240-32ed-11ea-aa6e-a7e7fcb8af6c (1894×1112)](/media/202105//1621914623.9183517.png) #### 4.3.3 并发预清理(Concurrent Preclean) 1. 此阶段同样是与应用线程**并发执行**的,**不需要停止应用线程**。 2. 因为前一阶段**并发标记**与应用程序并发运行,可能有一些引用关系已经发生了变化,**如果在并发标记过程中引用关系发生了变化,JVM 会通过“卡片”的方式将发生了改变的区域标记为“脏”区,这就是所谓的“卡片标记”。** 3. **在预清理阶段,这些脏对象会被统计出来,他们所引用的对象也会被标记。此阶段完成后,用于标记的卡片也会被清空。** 4. 这个阶段是为**最终标记**阶段做准备。 ![12b5efe0-32ee-11ea-9390-6160376f1fda (1828×1112)](/media/202105//1621914623.9231107.png) ![3d254780-32ee-11ea-9e4a-871af6a0c6b3 (1922×1084)](/media/202105//1621914623.9533844.png) #### 4.3.4 可取消的并发预清理(Concurrent Abortable Preclean) 1. 这个阶段也是为**最终标记**阶段做准备。 2. 在进入最终标记阶段前,最好能进行一个**Minor GC**,将年轻代清理一遍,这样可以清除大部分年轻代的对象,**尽量缩短重新标记阶段停顿时间**。 3. CMS 还提供了 `CMSScavengeBeforeRemark` 参数,**可以在进入最终标记之前强制进行一次 Minor GC**。 #### 4.3.5 最终标记(Final Remark) 1. **最终标记阶段是此次 GC 事件中的第二次(也是最后一次)STW 停顿。** 2. **本阶段的目标是完成老年代中所有存活对象的标记**,因为之前的预清理阶段是并发执行的,有可能 GC 线程跟不上应用程序的修改速度,**所以需要一次 STW 暂停来处理各种复杂的情况**。 3. 通常 CMS 会尝试在年轻代尽可能空的情况下执行**最终标记**阶段,以免连续触发多次 STW 事件。 #### 4.3.6 并发清除(Concurrent Sweep) 1. 在前面 5 个标记阶段完成之后,老年代中所有的存活对象都被标记了,然后该阶段将**清除所有不使用的对象来回收老年代空间**。 2. **此阶段与应用程序并发执行,不需要 STW 停顿。** ![4b92f970-32ee-11ea-8c11-e7b43c5f4201 (1904×1074)](https://images.gitbook.cn/4b92f970-32ee-11ea-8c11-e7b43c5f4201) #### 4.3.7 并发重置(Concurrent Reset) 1. 此阶段与应用程序并发执行,重置 CMS 算法相关的内部数据,为下一次 GC 循环做准备。 ## 5 G1 收集器(Garbage First) > G1 的全称是 Garbage First,意为垃圾优先,哪一块的垃圾最多就优先清理他。 ### 5.1 特点 1. G1 收集器的最主要的设计目标是**将 STW 停顿的时间和分布,变成可预期且可配置的**。 2. **堆不再分成年轻代和老年代,而是划分为多个(通常是 2048 个)可以存放对象的小块堆区域**(Region)**。** 每个小块可能一会被定义成 Eden 区,一会被指定为 Survivor 区或者 Old 区。在逻辑上**所有的 Eden 区和 Survivor 区合起来就是年轻代,所有的 Old 区拼在一起就是老年代**,如下图所示: ![60da5cb0-32ee-11ea-8c11-e7b43c5f4201 (1896×1142)](/media/202105//1621914623.958074.png) 3. 这样划分之后,使得 G1 不必每次都去收集整个堆空间,而是以增量的方式进行处理:**每次只处理一部分内存块**,称为这次 GC 的回收集,**每次 GC 暂停都会收集所有年轻代的内存块,但是一般只包含部分老年代的内存块**。 ![69d8c2c0-32ee-11ea-8c11-e7b43c5f4201 (1884×1090)](/media/202105//1621914623.9607778.png) 4. 在并发阶段估算每个小堆块存活对象的总数,构件回收集的原则是**垃圾最多的小块会被优先收集**。 5. **G1 适合大内存,需要较低延迟的场景。** ### 5.2 各个阶段 #### 5.2.1 初始标记(Intial Mark) 1. 此阶段只是**标记一下 GC Root 能直接关联到的对象**,并且**修改 TAMS 指针**,让下一阶段用户线程并发执行时能正确的在可用内存区域中分配新对象。 2. 这个阶段需要一次**STW 暂停**,但耗时很短,因为其是在进行**Minor GC**的时候同步完成的,所以 G1 收集器在这个阶段实际上没有额外的停顿。 > TAMS 是什么? > > 要达到 GC 与用户线程并发运行,**必须要解决回收过程中新对象的分配**,所以 G1 为每一个 Region 区域设计了两个名为**TAMS**(Top at Mark Start)的指针,**从 Region 区域划出一部分空间用于记录并发回收过程中的新对象,这样的对象认为他们是存活的,不纳入垃圾回收范围**。 #### 5.2.2 并发标记(Concurrent Mark) 1. 此阶段是**标记所有从 GC Root 可达的存活对象**。 2. 这一阶段**耗时较长,但可与应用程序并发完成**。 3. 并发时**引用变动的对象会产生漏标问题**,G1 中会使用**起始快照**(Snapshot at The Beginning, SATB)算法来解决。 #### 5.2.3 最终标记(Final Mark) 1. 此阶段需要一次**STW 暂停**,用于处理**并发标记**开始时未被标记的存活对象。 #### 5.2.4 筛选回收(Live Data Counting and Evacuation) 1. **负责更新 Region 的统计数据,对各个 Region 的回收价值和成本进行排序,根据用户所期望的停顿时间来制定回收计划。** 2. 可以**自由选择任意多个 Region 构成回收集**,然后**把决定回收的那一部分 Region 的存活对象复制到空的 Region 中**,再**清理掉整个旧 Region 的全部空间**。 3. 这里的操作涉及**存活对象的移动**,因此需要一次**STW 暂停**。 4. 该阶段是**由多个收集器线程并行完成的**。 ### 5.3 记忆集(Remembered Set) #### 5.3.1 特点 > 堆空间通常被分为**年轻代**和**老年代**。由于年轻代的垃圾收集通常很频繁,**如果老年代对象引用了年轻代的对象,那么回收年轻代的话,需要扫描所有从老年代到年轻代的所有引用,所以要避免每次年轻代垃圾回收时扫描整个老年代,减少开销**。 1. **记忆集**(Remembered Set, RSet)是用来**记录从其他 Region 中的对象到本 Region 的引用**,是一种抽象的数据结构。每一个 Region 都有一个 RSet,有了这个数据结构,**在回收某个 Region 的时候,就不必对整个堆内存的对象进行扫描了,他使得部分收集成为了可能**。 2. 对于**年轻代的 Region**,他的 RSet 只保存了**来自老年代的引用**,这是因为**年轻代的回收是针对所有年轻代 Region 的**,没必要画蛇添足,所以说**年轻代 Region 的 RSet 有可能是空的**。 3. 对于**老年代的 Region**,他的 RSet 也只会保存**老年代对它的引用**,这是因为老年代回收之前,会先对年轻代进行回收,这时 Eden 区变空了,而在回收过程中会扫描 Survivor 分区,所以也没必要保存来自年轻代的引用。 #### 5.3.2 缺点 1. RSet 通常会占用很大的空间,大约 5% 或者更高(最高可能 20%)。 2. 不仅是空间方面,很多计算开销也是比较大的。 #### 5.3.3 RSet 辅助 GC 的原理 1. 在做**YGC**的时候,**只需要选定年轻代的 RSet 作为 GC Roots**,这些 RSet 记录了 $Old \rightarrow Young$ 的跨代引用,避免扫描整个老年代。 2. 而在**Mixed GC**的时候,老年代中记录了 $Old \rightarrow Old$ 的 RSet,$Young \rightarrow Young$ 的引用从**Survivor**获取(老年代回收之前,会先对年轻代进行回收,存活的对象放在 Survivor 区),这样也不用扫描全部老年代了。 3. 所以 RSet 的引入大大减少了 GC 的工作量。 ### 5.4 三色标记算法 > 1. 在三色标记算法之前有一个算法叫做**标记-清除算法**(Mark and Sweep),这个算法会设置一个标志位来记录对象是否被引用。最开始,所有的标志位都是 0,如果发现对象是可达的就会置为 1,一步步下去就会呈现一个类似树状的结果。等标记步骤完成后,**会将未被标记的对象统一清理**,再次把所有标记位设置成 0,方便下次清理。 > 2. 这个算法最大的问题是**GC 执行期间需要把整个程序完全暂停,不能实现用户线程和 GC 线程并发执行**。因为在不同标记阶段标记-清除算法的标志位 0 和 1 有不同的含义,那么新增的对象无论标记为什么都有可能被意外删除。对实时性要求高的系统来说,这种需要长时间挂起的标记-清除算法是不可接受的,所以就需要一个算法来**解决 GC 运行程序长时间挂起的问题**,那就是**三色标记算法**。 1. 三色标记算法的最大好处是可以**异步执行**,从而**可以以中断时间极少的代价或者完全没有中断来进行整个 GC**。 2. 三色标记法很简单,首先将对象用三种颜色表示,分别是**黑色**、**灰色**、**白色**: 1. **黑色:** 表示**根对象**,或者**该对象与他引用的对象都已经被扫描过了**。 2. **灰色: 该对象本身已经被标记**,但是**他引用的对象还没有扫描完**。 3. **白色:未被扫描的对象**,如果扫描完所有对象之后,**最终为白色的为不可达对象**,也就是**垃圾对象。** ![在这里插入图片描述](/media/202105//1621914623.9659362.png) ### 5.4 漏标问题 ![在这里插入图片描述](/media/202105//1621914623.9679987.png) 1. 假设此时**对象 A 及其引用的对象都已经扫描完**,那么**对象 A 将会被标记为黑色**。 2. 用户线程**将对象 B 和对象 C 之间的引用断开,将对象 A 指向对象 C,此时由于对象 A 已经被标记为黑色,所以不会再被扫描,因此对象 C 就会被当成垃圾对象,产生漏标问题**。 漏标问题在**CMS**和**G1 收集器**中有着不同的解决方案: 1. **CMS:** 采用**增量更新**(Incremental Update)算法,在**并发标记**阶段如果一个**白色对象被黑色对象引用**时,会**将黑色对象重新标记为灰色**,让垃圾收集器在**重新标记**阶段重新扫描。 2. **G1:** 采用**SATB**(Snapshot at The Beginning),在**初始标记**时做一个快照,当**B 和 C 之间的引用消失**时,把这个引用**推到 GC 的堆栈**,**保证 C 还能被 GC 扫描到**,在**最终标记**阶段**扫描 SATB 记录**。 两种漏标解决方案的对比: 1. **增量更新算法**关注的是**引用的增加**($A \rightarrow C$ 的引用),需要**重新扫描**,**效率低**。 2. **SATB 算法**关注的是**引用的删除**($B \rightarrow C$ 的引用)。 ## 6 垃圾收集器的选择方法 1. 如果系统考虑**吞吐量**优先,**CPU 资源**都用来**最大程度处理业务**,用**Parallel GC**。 2. 如果系统考虑**低延迟**优先,每次**GC 时间尽量短**,用**CMS GC**。 3. 如果系统**堆内存较大**,同时希望整体来看**平均 GC 时间可控**,使用**G1 GC**。 ## 7 参考文献 1. [GC 回收之二:4 种垃圾收集算法及 7 种垃圾收集器](https://blog.csdn.net/clover_lily/article/details/80160726?utm_medium=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.control&dist_request_id=&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.control)。 2. [CMS 垃圾收集器](https://www.cnblogs.com/rumenz/articles/13945546.html)。 3. [G1 垃圾收集器详解](https://blog.csdn.net/u022812849/article/details/107692963)。
ricear
July 11, 2022, 10:04 p.m.
©
BY-NC-ND(4.0)
转发文档
Collection documents
Last
Next
手机扫码
Copy link
手机扫一扫转发分享
Copy link
Markdown文件
share
link
type
password
Update password