目录
1.垃圾回收算法
1.1 标记-清除算法
1.2 复制算法
1.3 标记-整理算法
1.4 分代收集算法
2.垃圾回收器
2.1 熟悉一下垃圾回收的一些名词
2.2 垃圾回收器有哪些?
2.3 Serial收集器
2.4 Parallel Scavenge收集器
2.5 ParNew收集器
2.6 CMS收集器
1.垃圾回收算法
- 标记-复制算法:将内存分为两块,每次使用一块,当内存用完时,将存活的对象复制到另一块,并清理使用过的空间。
- 标记-清除算法:分为标记和清除阶段,标记存活的对象,然后统一回收未标记的对象。
- 标记-整理算法:类似标记-清除算法,但在标记后将存活的对象移动到一端,然后清理边界外的内存。
- 分代收集理论:根据对象的存活周期将内存分为新生代和老年代,并为它们选择合适的垃圾收集算法。
1.1 标记-清除算法
-
标记无用对象,然后进行清除回收。
-
标记-清除算法(Mark-Sweep)是一种常见的基础垃圾收集算法,它将垃圾收集分为两个阶段:
- 标记阶段:标记出可以回收的对象。
- 清除阶段:回收被标记的对象所占用的空间。
-
标记-清除算法之所以是基础的,是因为后面讲到的垃圾收集算法都是在此算法的基础上进行改进的。
-
优点:实现简单,不需要对象进行移动。
-
缺点:标记、清除过程效率低,产生大量不连续的内存碎片,提高了垃圾回收的频率。
-
标记-清除算法的执行的过程如下图所示
1.2 复制算法
-
为了解决标记-清除算法的效率不高的问题,产生了复制算法。它把内存空间划为两个相等的区域,每次只使用其中一个区域。垃圾收集时,遍历当前使用的区域,把存活对象复制到另外一个区域中,最后将当前使用的区域的可回收的对象进行回收。
-
优点:按顺序分配内存即可,实现简单、运行高效,不用考虑内存碎片。
-
缺点:可用的内存大小缩小为原来的一半,对象存活率高时会频繁进行复制。
-
复制算法的执行过程如下图所示
1.3 标记-整理算法
-
在新生代中可以使用复制算法,但是在老年代就不能选择复制算法了,因为老年代的对象存活率会较高,这样会有较多的复制操作,导致效率变低。标记-清除算法可以应用在老年代中,但是它效率不高,在内存回收后容易产生大量内存碎片。因此就出现了一种标记-整理算法(Mark-Compact)算法,与标记-整理算法不同的是,在标记可回收的对象后将所有存活的对象压缩到内存的一端,使他们紧凑的排列在一起,然后对端边界以外的内存进行回收。回收后,已用和未用的内存都各自一边。
-
优点:解决了标记-清理算法存在的内存碎片问题。
-
缺点:仍需要进行局部对象移动,一定程度上降低了效率。
-
标记-整理算法的执行过程如下图所示
1.4 分代收集算法
- 当前商业虚拟机都采用
分代收集
的垃圾收集算法。分代收集算法,顾名思义是根据对象的存活周期
将内存划分为几块。一般包括年轻代
、老年代
和永久代
,如图所示:(后面有重点讲解)
作者:小杰要吃蛋
链接:https://juejin.cn/post/6844904125696573448
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
2.垃圾回收器
2.1 熟悉一下垃圾回收的一些名词
新生代收集(Minor GC/Young GC):只是新生代的垃圾收集
老年代收集(Major GC/Old GC):只是老年代的垃圾收集
混合收集(Mixed GC):收集整个新生代以及部分老年代的垃圾收集,目前只有 G1 GC 会有这种行为
整堆收集(Full GC):收集整个 Java 堆和方法区的垃圾
2.2 垃圾回收器有哪些?
2.3 Serial收集器
Serial(串行)收集器是最基本、历史最悠久的垃圾收集器了。大家看名字就知道这个收集器是一个单线程收集器了。它的 “单线程” 的意义不仅仅意味着它只会使用一条垃圾收集线程去完成垃圾收集工作,更重要的是它在进行垃圾收集工作的时候必须暂停其他 所有的工作线程( "Stop The World" ),直到它收集结束。
新生代采用复制算法,老年代采用标记-整理算法。
虚拟机的设计者们当然知道Stop The World带来的不良用户体验,所以在后续的垃圾收集器设计中停顿时间在不断缩短(仍然还有停 顿,寻找最优秀的垃圾收集器的过程仍然在继续)。 、
但是Serial收集器有没有优于其他垃圾收集器的地方呢?当然有,它简单而高效(与其他收集器的单线程相比)。Serial收集器由于没 有线程交互的开销,自然可以获得很高的单线程收集效率。
Serial Old收集器是Serial收集器的老年代版本,它同样是一个单线程收集器。它主要有两大用途:一种用途是在JDK1.5以及以前的 版本中与Parallel Scavenge收集器搭配使用,另一种用途是作为CMS收集器的后备方案。
2.4 Parallel Scavenge收集器
Parallel收集器其实就是Serial收集器的多线程版本,除了使用多线程进行垃圾收集外,其余行为(控制参数、收集算法、回收策略等 等)和Serial收集器类似。默认的收集线程数跟cpu核数相同,当然也可以用参数(-XX:ParallelGCThreads)指定收集线程数,但是一般 不推荐修改。
Parallel Scavenge收集器关注点是吞吐量(高效率的利用CPU)。CMS等垃圾收集器的关注点更多的是用户线程的停顿时间(提高 用户体验)。所谓吞吐量就是CPU中用于运行用户代码的时间与CPU总消耗时间的比值。 Parallel Scavenge收集器提供了很多参数 供用户找到最合适的停顿时间或最大吞吐量,如果对于收集器运作不太了解的话,可以选择把内存管理优化交给虚拟机去完成也是一 个不错的选择。
新生代采用复制算法,老年代采用标记-整理算法。
Parallel Old收集器是Parallel Scavenge收集器的老年代版本。使用多线程和“标记-整理”算法。在注重吞吐量以及CPU资源的场 合,都可以优先考虑 Parallel Scavenge收集器和Parallel Old收集器(JDK8默认的新生代和老年代收集器)。
2.5 ParNew收集器
ParNew收集器其实跟Parallel收集器很类似,区别主要在于它可以和CMS收集器配合使用。
新生代采用复制算法
2.6 CMS收集器
CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。它非常符合在注重用户体验的应用上使 用,它是HotSpot虚拟机第一款真正意义上的并发收集器,它第一次实现了让垃圾收集线程与用户线程(基本上)同时工作。 从名字中的Mark Sweep这两个词可以看出,CMS收集器是一种 “标记-清除”算法实现的,它的运作过程相比于前面几种垃圾收集 器来说更加复杂一些。整个过程分为四个步骤:
初始标记: 暂停所有的其他线程(STW),并记录下gc roots直接能引用的对象,速度很快。
并发标记: 并发标记阶段就是从GC Roots的直接关联对象开始遍历整个对象图的过程, 这个过程耗时较长但是不需要停顿用户线 程, 可以与垃圾收集线程一起并发运行。因为用户程序继续运行,可能会有导致已经标记过的对象状态发生改变。
重新标记: 重新标记阶段就是为了修正并发标记期间因为用户程序继续运行而导致标记产生变动的那一部分对象的标记记录(主要 是处理漏标问题),这个阶段的停顿时间一般会比初始标记阶段的时间稍长,远远比并发标记阶段时间短。主要用到三色标记里的增量 更新算法(见下面详解)做重新标记。
并发清理: 开启用户线程,同时GC线程开始对未标记的区域做清扫。这个阶段如果有新增对象会被标记为黑色不做任何处理(见下 面三色标记算法详解)。 并发重置:重置本次GC过程中的标记数据。
三色标记 :
在并发标记的过程中,因为标记期间应用线程还在继续跑,对象间的引用可能发生变化,多标和漏标的情况就有可能发生。漏标的问 题主要引入了三色标记算法来解决。
三色标记算法是把Gc roots可达性分析遍历对象过程中遇到的对象, 按照“是否访问过”这个条件标记成以下三种颜色:
黑色: 表示对象已经被垃圾收集器访问过, 且这个对象的所有引用都已经扫描过。 黑色的对象代表已经扫描过, 它是安全存活 的, 如果有其他对象引用指向了黑色对象, 无须重新扫描一遍。 黑色对象不可能直接(不经过灰色对象) 指向某个白色对象。
灰色: 表示对象已经被垃圾收集器访问过, 但这个对象上至少存在一个引用还没有被扫描过。
表示对象尚未被垃圾收集器访问过。 显然在可达性分析刚刚开始的阶段, 所有的对象都是白色的, 若在分析结束的阶段, 仍然是白色的对象, 即代表不可达
CMS是一款优秀的垃圾收集器,主要优点:并发收集、低停顿。但是它有下面几个明显的缺点:
1. 对CPU资源敏感(会和服务抢资源);
2. 无法处理浮动垃圾(在并发标记和并发清理阶段又产生垃圾,这种浮动垃圾只能等到下一次gc再清理了);
3. CMS使用的回收算法“标记-清除”算法会导致收集结束时会有大量空间碎片产生,当然通过参数 XX:+UseCMSCompactAtFullCollection可以让jvm在执行完标记清除后再做整理
4. 执行过程中的不确定性,会存在上一次垃圾回收还没执行完,然后垃圾回收又被触发的情况,特别是在并发标记和并发清理阶 段会出现,一边回收,系统一边运行,也许没回收完就再次触发full gc,也就是"concurrent mode failure",此时会进入stop the world,用serial old垃圾收集器来回收 。