3.5 经典垃圾收集器
3.5.1 Serial收集器
单线程收集,适合在客户端
3.5.2 ParNew
是serial的多线程版本,除了serial外,只有他能和CMS配合使用
并行-并发概念
3.5.3 Parallel Scavenge
以上三款都是【标记-复制】实现的
1.目标:Parallel Scavenge 收集器的特点是它的关注点与其他收集器不同, CMS 等收集器的关注点是尽可能地缩短垃圾收集时用户线程的停顿时间,而 Parallel Scavenge 收集器的目标则是达到一个可控制的吞吐量( Throughput)。
吞吐量定义:
2.三个重要参数
1)控制最大垃圾收集停顿时间的-XX: MaxGCPauseMillis
2)直接设置吞吐量大小的-XX:GCTimeRatio
3)自适应调整吞吐量-XX:+UseAdaptiveSizePolicy
3.5.5 Parall Old
是Parallel Scavenge的老年代版本,支持多线程并发收集,基于【标记-整理】
3.5.6 CMS(Concurrent Mark Sweep)
基于标记-清除
1.分为四个阶段
- 初始标记
仅仅只是标记一下 GC Roots 能直接关联到的对象,速度很快。 - 并发标记
从 GC Roots 的直接关联对象开始遍历整个对象图的过程,这个过程耗时较长但是不需要停顿用户线程,可以与垃圾收集线程一起并发运行。 - 重新标记
为了修正并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录(详见 3.4.6 节中关于增量更新的讲解),这个阶段的停顿时间通常会比初始标记阶段稍长一些,但也远比并发标记阶段的时间短。 - 并发清除
清理删除掉标记阶段判断的已经死亡的对象,由于不需要移动存活对象,所以这个阶段也是可以与用户线程同时并发的。
初始标记和重新标记需要STW。
2.三个明显的缺点
1)占用了一部分线程(或者说处理器的计算能力)而导致应用程序变慢,降低总吞吐量。
2)无法处理“浮动垃圾”(Floating Garbage)
在 CMS 的并发标记和并发清理阶段,用户线程是还在继续运行的,程序在运行自然就还会伴随有新的垃圾对象不断产生,但这一部分垃圾对象是出现在标记过程结束以后, CMS 无法在当次收集中处理掉它们,只好留待下一次垃圾收集时再清理掉。这一部分垃圾就称为“浮动垃圾”。
3)会有大量空间碎片产生
3.5.7 Garbage First
Garbage First(简称 G1)收集器是垃圾收集器技术发展历史上的里程碑式的成果,它开创了收集器面向局部收集的设计思路和基于 Region 的内存布局形式。
G1 是一款主要面向服务端应用的垃圾收集器,基于**【标记-整理】**实现。
1.模型
停顿时间模型
2.目标
能够支持指定在一个长度为 M 毫秒的时间片段内,消耗在垃圾收集上的时间大概率不超过 N 毫秒这样的目标,这几乎已经是实时 Java(RTSJ)的中软实时垃圾收集器特征了。
3.具体实现
面向堆内存任何部分来组成回收集(Collection Set,一般简称 CSet)进行回收,衡量标准不再是它属于哪个分代,而是哪块内存中存放的垃圾数量最多, 回收收益最大,这就是 G1 收集器的 Mixed GC 模式。
堆被划分为一组大小相等的堆区域,每个区域都是连续的虚拟内存范围。G1 执行一个并发的全局标记阶段来确定整个堆中对象的活跃度。标记阶段完成后,G1 知道哪些区域大部分是空的。它首先在这些区域收集,这通常会产生大量的可用空间。这就是为什么这种垃圾收集方法被称为 Garbage-First 的原因。顾名思义,G1 将其收集和整理活动集中在堆中可能充满可回收对象的区域,即垃圾。G1 使用停顿预测模型来满足用户定义的停顿时间目标,并根据指定的停顿时间目标选择要收集的区域数。
G1 识别为可以回收的区域使用疏散进行垃圾收集。G1 将对象从堆的一个或多个区域复制到堆上的单个区域,并在此过程中整理和释放内存。这种疏散在多处理器上并行执行,以减少暂停时间并提高吞吐量。因此,每次垃圾回收时,G1 都会在用户定义的暂停时间内持续工作以减少碎片。这超出了前两种方法的能力。CMS(并发标记扫描)垃圾收集不进行压缩。ParallelOld 垃圾收集仅执行整个堆压缩,这会导致相当长的暂停时间。
需要注意的是,G1 不是实时收集器。它以高概率但不是绝对确定地满足设定的暂停时间目标。根据之前收集的数据,G1 会估计在用户指定的目标时间内可以收集多少个区域。因此,收集器有一个相当准确的区域收集成本模型,并且它使用该模型来确定要收集哪些区域以及要收集多少区域,同时保持在暂停时间目标内。
4.特点
- 用 Region 划分内存空间,以及具有优先级的区域回收方式,保证了 G1 收集器在有限的时间内获取尽可能高的收集效率。
- 并发+并行
可以使用多个CPU来减少STW的停止时间,而且还是多线程并发执行的 - 可预测的停顿
能让用户指定在GC上所消耗的时间,不让他超过一定的时间。
5.步骤
- 初始标记
- 并发标记
- 最终标记
- 筛选回收
除了并发标记其余都要STW
6.新理念
不再追求把Java堆全部清理干净,只要收集的速度能跟得上对象分配的速度,那一切就能运作得很完美。这种新的收集器设计思路从工程实现上看是从 G1 开始兴起的,所以说 G1 是收集器技术发展的一个里程碑。
3.5.8 几种垃圾收集器比较
参考:https://blog.csdn.net/doubututou/article/details/109069092
G1选择详细说明:
https://docs.oracle.com/javase/8/docs/technotes/guides/vm/G1.html#use_cases
- 超过50%的存活对象占用着堆内存
- 对象分配速度上升变化非常快
- 不希望长时间垃圾收集或者整理暂停
3.5.9 个人补充
第一点:GC事件分类:
根据垃圾收集回收的区域不同,垃圾收集器主要分为:
- Young GC
- Old GC
- Full GC
- Mixed GC
- Young GC
新生代内存的垃圾收集事件称为Young GC(又称Minor GC),当JVM无法为新对象分配在新生代内存空间时总会触发Young GC。
比如Eden区占满时,新对象分配频率越高,Young GC的频率就越高。
Young GC每次都会引起Stop-The-World,暂停所有的应用程序,停顿时间相对老年代GC造成的停顿,几乎忽略不计
- Old GC、Full GC、Mixed GC
Old GC:只清理老年代空间的GC事件,只有CMS的并发收集是这个模式
Full GC:清理整个堆的GC事件,包括新生代、老年代、元空间等。
Mixed GC:清理整个新生代以及部分老年代的GC,只有G1有这个模式。
**以上个人理解:**CMS之前的那几种老年代垃圾收集器,他们都是并行操作的,也就是说占用了所有的工作线程来操作,当老年代满了的时候,就会触发Old GC,会STW,又由于Young GC时间很短,所以就Full GC了。但是CMS收集行为是并发的,部分状态是可以和用户线程同时进行的,这些状态不会STW,所以就没有必要和Young GC放一起清理。对于G1,Young GC的时候主要对Eden区进行GC,当老年代空间达到阀值会触发Mixed GC,选定所有的新生代Region,根据全局并发标记阶段(下面介绍到)统计得出收集收益高的若干老年代 Region。
第二点:元空间:
java8中移除了永久代,新增了元空间的概念。原来的方法区是逻辑划分中的一个区域,对应hotspot jdk6中的永久代,可以说永久代是方法区在hotspot的一个具体实现,但是从jdk7以后方法区就“四分五裂了”,不再是在单一的一个去区域内进行存储。
java8中继承了一些jdk7中的改变:符号引用存储在native heap中,字符串常量和静态类型变量存储在普通的堆区中,这个影响了String的intern()方法的行为,这里不做intern的详述。
而在java8中移除了永久代,新增了元空间,其实在这两者之间存储的内容几乎没怎么变化,而是在内存限制、垃圾回收等机制上改变较大。元空间的出现就是为了解决突出的类和类加载器元数据过多导致的OOM问题,而从jdk7中开始永久代经过对方法区的分裂后已经几乎只存储类和类加载器的元数据信息了,到了jdk8,元空间中也是存储这些信息,而符号引用、字符串常量等存储位置与jdk7一致,还是“分裂”的方法区。
符号引用没有存在元空间中,而是存在native heap中,这是两个方式和位置,不过都可以算作是本地内存,在虚拟机之外进行划分,没有设置限制参数时只受物理内存大小限制,即只有占满了操作系统可用内存后才OOM。
个人理解:类和类加载器元数据存放在永久代,也就是元空间的前生,元空间的引入就是为了解决这两类信息过多导致的OOM问题。
第三点:内存比例
Survivor与Eden区比例:1:8
年轻代:老年代是 1:2