Java8默认GC收集器
- 新生代:Parallel Scavenge收集器
- 老年代:Parallel Old收集器
parallel:并行,对应的是串行(serial),指的是执行过程
concurrent: 并发,对应的是顺序执行(sequential),指的是同时出发这个动作
使用java -XX:+PrintCommandLineFlags -version命令行在cmd执行,得出以下结果:
-XX:InitialHeapSize=268435456 //初始化堆大小:256M
-XX:MaxHeapSize=4294967296 //最大堆大小:4096M
-XX:+PrintCommandLineFlags //打印那些已被用户或者JVM设置过的详细的XX参数的名称和值
-XX:+UseCompressedClassPointers //类指针压缩
-XX:+UseCompressedOops //普通对象指针压缩
-XX:+UseParallelGC //表示使用Parallel收集器(Parallel Scavenge + Parallel Old)
java version "1.8.0_261"
Java(TM) SE Runtime Environment (build 1.8.0_261-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.261-b12, mixed mode)
其中UseCompressedClassPointers是跟随UseCompressedOops一起开启。
比如Object o = new Object(),开启UseCompressedOops即普通对象指针压缩,会对o的空间进行压缩,由8字节变为4字节,同时开启了
UseCompressedClassPointers类指针压缩,其中Object对象在内存中的布局,包括markword、klass pointer、实例数据和填充对齐(占位符,只为满足JVM规范要求),开启的UseCompressedClassPointers类指针压缩,会压缩klass pointer这部分大小,由8字节压缩到4字节,间接提高内存的利用率。
Parallel Scavenge收集器
概要:新生代收集器,基于标记-复制算法实现,支持并行收集的多线程收集器
主要参数:
- -XX:MaxGCPauseMillis
- -XX:GCTimeRatio
- -XX:+UseAdaptiveSizePolicy
MaxGCPauseMillis(关注垃圾回收的停顿时间)
该参数是将内存回收所花费的时间控制在该值范围内,该值是一个大于0的毫秒数。但是值越小并不代表收集的越快,而是以牺牲吞吐量和新生代空间为代价换取的:系统会把新生代调得小一些,收集300M的速度肯定要比500M来得快,所以间接的提高了GC发生的频率,原来10秒收集一次,每次停顿100ms,现在5秒收集一次,每次停顿70ms,虽然停顿时间有所下降,但是系统的吞吐量却降了下来。
GCTimeRatio(关注吞吐量)
该参数是将垃圾收集时间和用户代码运行时间按比例分配,该值是(0,100)的整数,相当于吞吐量的倒数,譬如参数设置为19,则允许最大的垃圾收集时间占总时间的5%,即1/(1+19),默认值为99,即允许最大1%的垃圾收集时间。
这里涉及到吞吐量的概念:
所以Parallel Scavenge收集器又叫“吞吐量优先收集器”。上面两个例子的吞吐量即为95%和99%。
UseAdaptiveSizePolicy
该参数是一个开关,激活后,不需要人工设置新生代大小(-Xmn)、Eden与Survivor区的比例(-XX:SurvivorRatio)、晋升老年代对象大小(-XX:PretenureSizeThreshold)等细节参数,虚拟机会根据当前系统的运行状况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或者最大的吞吐量。这种调节方式称为垃圾收集的自适应调节策略(GC Ergonomics)。
打开此开关后,只需把最基本的内存参数设置好(如-Xmx设置最大堆),然后使用-XX:MaxGCPauseMillis或者-XX:GCTimeRatio给虚拟机设立一个优化目标就行了,这个也是区别于ParNew收集器的一个重要特性。
Parallel Old收集器
概要:老年代收集器(Parallel Scavenge收集器的老年代版本),基于标记-整理算法实现,支持多线程并发收集,从jdk6开始支持。
经典GC收集器归纳
新生代收集器:
- Serial收集器
- ParNew收集器
- Parallel Scavenge收集器
老年代收集器:
- Serial Old收集器
- Parallel Old收集器
- CMS收集器
其他收集器:
- Garbage First收集器(G1)
Serial收集器
概要:最基础的、历史最悠久的收集器,基于标记-复制算法实现,单线程,在垃圾收集时,必须暂停其他所有工作线程,直到它收集结束。
适用于客户端模式下的虚拟机,简单高效,没有线程交互的开销,它是所有收集器里额外内存消耗最小的,对于内存资源受限的环境,迄今为止,它依然是HotSpot虚拟机运行在客户端模式下的默认新生代收集器。
ParNew收集器
概要:ParNew收集器是Serial收集器多线程并行版本,是服务端模式下的HotSpot虚拟机,尤其是JDK7之前的遗留系统中首选的新生代收集器。目前除了Serial收集器,只有ParNew收集器能与CMS收集器配合工作。
自JDK9开始,ParNew+CMS的组合不再是官方推荐的服务端模式下的收集器解决方案了,而是被G1取代。
各版本默认垃圾收集器:
| JDK版本 | 新生代收集器 | 老年代收集器 |
|---|---|---|
| 7 | Parallel Scavenge | Parallel Old |
| 8 | Parallel Scavenge | Parallel Old |
| 9 | G1 | G1 |
Parallel Scavenge收集器
Serial Old收集器
概要:Serial收集器的老年代版本,基于标记-整理算法,单线程,主要提供与客户端模式下的HotSpot虚拟机使用
Parallel Old收集器
CMS收集器
概要:CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。基于标记-清除算法实现,支持并发处理,目前很大一部分的Java应用集中在互联网网站或者基于浏览器的B/S系统的服务端上,这类应用通常都会较为关注服务的响应速度,希望系统停顿时间尽可能短,以给用户带来良好的交互体验。CMS收集器就非常符合这类应用的需求。
收集过程:
- 初始标记(CMS initial mark)
- 并发标记(CMS concurrent mark)
- 重新标记(CMS remark)
- 并发清除(CMS concurrent sweep)
其中初始标记和重新标记仍然需要“Stop The World”。
- 初始标记仅仅只是标记一下GC Roots能直接关联到的对象,虽然需要“Stop The World”,但速度很快;
- 并发标记就是从GC Roots的直接关联对象开始遍历整个对象图的过程,这个过程是并发过程,虽然耗时较长,但是不需要停顿用户线程;
- 重新标记是为了修正并发标记期间,因用户线程继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间通常会比初始标记阶段稍微长一些,但也远比并发标记阶段的时间短;
- 并发清除是为了清理删除掉标记阶段判断的已经死亡的对象,由于不需要移动存活对象,这个阶段也是可以和用户线程并发执行的。
由于耗时最长的并发标记和并发清除阶段中,垃圾收集器线程都可以与用户线程一起工作,所以从总体上来说,CMS收集器的内存回收过程是与用户线程一起并发执行的。
优点:
- 并发收集
- 低停顿
缺点:
- 占用线程资源,尤其是核心数不多的CPU,在垃圾收集时,会降低用户程序的执行速度;
- CMS会产生“浮动垃圾”,由于并发标记和并发清理阶段,用户线程仍然继续在运行,所以会有新的垃圾对象不断产生,但这一部分是在标记之后产生的,要到下一次垃圾收集才会处理,此次不会处理。“浮动垃圾”有可能会导致CMS收集器在收集过程中出现“Con-current Mode Failure”失败进而导致另一次完全“Stop The World”的Full GC的产生。因为与用户线程并发处理,还需要预留足够的内存空间给用户线程使用,JDK5默认是CMS收集器当老年代使用了68%的空间就会被激活,到了JDK6变成92%,这个参数过高将有可能面临另一种风险:CMS运行期间预留的内存无法满足程序分配新对象的需要,就会出现一次“并发失败”(Concurrent Mode Failure),这个时候虚拟机会启动后备预案:冻结用户线程的执行,临时启用Serial Old收集器来重新进行老年代的垃圾收集,大大拉长了垃圾收集时间。
- CMS是基于“标记-清除”算法实现的,这意味着当垃圾收集结束时,会产生很多空间碎片,当空间碎片过多,会导致老年代虽然有很多剩余空间,但是无法找到足够大的连续空间来分配当前对象,而不得不提前触发一次Full GC的情况发生。
Garbage First收集器
概要:简称(G1),面向局部收集和基于Region的内存布局形式,主要面向服务端应用的垃圾收集器,G1被称为“全功能的垃圾收集器”(Fully-Featured Garbage Collector)。JDK9及以上版本的HotSpot虚拟机默认在服务端模式下的垃圾收集器为G1,取代了之前“Parallel Scavenge+Parallel Old”的组合。
主要回收思想:
在此之前的收集器,包括CMS在内,垃圾收集的目标范围要么是在整个新生代(Minor GC),要么就是在整个老年代(Major GC),再要么就是整个Java堆(Full GC)。G1跳出以前的分代收集的思想,基于Region的内存布局形式,它可以面向堆内存任何部分来组成回收集(Collection Set,一般简称CSet)进行回收,衡量的标准也从属于哪个分代变为哪块内存中存放的垃圾数量最多,回收收益最大,这就是G1的Mixed GC模式。 收集过程:
- 初始标记(Initial Marking)
- 并发标记(Concurrent Marking)
- 最终标记(Final Marking)
- 筛选回收(Live Data Counting and Evacuation)
其中“初始标记”和“最终标记”仍然需要“Stop The World”。
- 初始标记(Initial Marking):仅仅只是标记一下GC Roots能直接关联到的对象,并且修改TAMS指针的值,让下一阶段用户线程并发运行时,能正确地在可用的Region中分配新对象。这个阶段需要停顿用户线程,但耗时很短,而且是借用进行Minor GC的时候同步完成的,所以G1收集器在这个阶段实际并没有额外的停顿。
- 并发标记(Concurrent Marking):从GC Roots开始对堆中对象进行可达性分析,递归扫描整个堆里的对象图,找出要回收的对象,这个阶段比较耗时,但可以与用户线程并发执行。当对象图扫描完成以后,还要重新处理SATB记录下的在并发时有引用变动的对象。
- 最终标记(Final Marking):对用户线程做另一个短暂的停顿,用于处理并发阶段结束后仍然遗留下来的最后那少量的SATB记录。
- 筛选回收(Live Data Counting and Evacuation):负责更新Region的统计数据,对各个Region的回收价值和成本进行排序,根据用户所期望的停顿时间来制定回收计划,可以自由选择任意多个Region构成回收集,然后把决定回收的那一部分Region的存活对象复制到空的Region中,在清理掉整个旧Region的全部空间。这里操作涉及到存活对象的移动,必须暂停用户线程,由多条收集线程并行完成。
除了并发标记阶段,其他都要暂停用户线程,并非纯粹地追求低延迟,而是要求在延迟可控的情况下尽量获得高吞吐量,所以才能担当起“全功能收集器”的重任与期望。
设置不同的停顿时间,可以让G1收集器在不同的应用场景中取得关注吞吐量和关注延迟之间的最佳平衡。
默认值200ms,设置的停顿时间太短,会导致垃圾回收不及时,最后引发Full GC反而降低性能,一般情况一两百或者两三百毫秒都比较合理。
相较于CMS收集器,G1收集器会更占内存,但是从局部来看是基于“标记-复制”算法,从全局来看是基于“标记-整理”算法,这两种算法都不容易产生内存碎片,这种特性有利于程序长时间运行,也减少了在程序为大对象分配内存时因找不到连续内存空间而提前触发下一次收集。
参考文献:《深入理解Java虚拟机JVM高级特性与最佳实践第3版 华章原创精品 - 周志明》