垃圾回收算法和垃圾回收器
对于JVM的垃圾回收算法有复制算法、标记清除、标记整理。
用阳哥的话就是:这些算法只是天上飞的理念,是一种方法论,但是真正的垃圾回收还需要有落地实现,所以垃圾回收器应运而生。
JVM回收的区域包括方法区和堆,jvm对于不同区域不同的特点采用分代收集算法,比如因为所有的对象都是在Eden区进行分配,并且大部分对象的存活时间都不长,都是“朝生夕死”的,每次新生代存活的对象都不多,所以新采取复制算法;而jvm默认是新生代的对象熬过15次GC才能进入老年代,所以老年代的对象都是生命周期比较长的,采用标记清除或者标记整理算法。
那么对于这些算法的实现都有什么呢?
新生代:serial、ParNew、Parallel
老年代:Serial Old、Parallel Old、CMS
全堆:G1
并且他们的搭配组合如下:
垃圾回收器
jvm的垃圾回收器大体上的分类主要包括四种:串行、并行、并发(CMS)和G1。
串行垃圾回收器(Serial):它为单线程环境设计并且只使用一个线程进行垃圾回收,会暂停所有的用户线程。所以不适合服务器环境。
并行垃圾回收器(Parallel):多个垃圾回收线程并行工作,此时用户线程是暂停的,适用于科学计算/大数据处理等弱交互场景。
并发垃圾回收器(CMS):用户线程和垃圾收集线程同时执行(不一定是并行,可能交替执行),不需要停顿用户线程。互联网公司多用它,适用于对响应时间有要求的场景。
G1垃圾回收器:G1垃圾回收器将堆内存分割成不同的区域然后并发的对其进行垃圾回收。
默认的垃圾回收器
平时我们没有配置什么jvm参数,程序也能正常执行,那么JVM默认的垃圾回收器是什么呢?
那么如何查看默认的回收器呢?有很多方式,这里简单列举几种:
1.命令行方式:
1
|
java -XX:+PrintCommandLineFlags -version |
可以看到jdk8默认的是使用的Parallel并行回收器。
2、jvm参数设置
在JVM运行之前加入参数同样可以查看,其实这两种方式是差不多的
3.jps+jinfo
先使用jps查看java进程号,在使用jinfo查看该进程的配置
Serial收集器
Serial是一个单线程收集器,在进行垃圾收集的时候必须停下所有的工作(Stop The World) 。
串行收集器是最古老,最稳定以及效率高的收集器,只使用一个线程去回收但其在进行垃圾收集过程中可能会产生较长的停顿(
Stop-The-World状态)。
虽然在收集垃圾过程中需要暂停所有其他的工作线程,但是它简单高效,对于限定单个CPU环境来说,没有线程交互的开销可以获得最高的单线程垃圾收集效率,因此Serial垃圾收集器依然是java虛拟机运行在Client模式下默认的新生代垃圾收集器。
对应JVM参数是: -XX:+UseSerialGC
开启后会使用: **Serial(Young区用) + Serial Old(Old区用)**的收集器组合:表示新生代、老年代都会使用串行回收收集器,新生代使用复制算法,老年代使用标记-整理算法。
ParNew收集器
ParNew是Serial收集器的升级版,将单线程进行垃圾回收升级为多线程进行垃圾回收,但是依旧会Stop The World。
ParNew收集器其实就是Serial收集器新生代的并行多线程版本,最常见的应用场景是配合老年代的CMS GC工作,其余的行为和
Serial收集器完全一样,ParNew垃圾收集器在垃圾收集过程中同样也要暂停所有其他的工作线程。它是很多java虚拟机运行在Server
模式下新生代的默认垃圾收集器。
常用对应JVM参数: -XX:+UseParNewGC
启用ParNew收集器,只影响新生代的收集,不影响老年代
开启。上述参数后,会使用: ParNew(Young区用) + Serial Old的收集器组合,新生代使用复制算法,老年代采用标记-整理算法。
但是,ParNew+Tenured这样的搭配,java8已经不再被推荐。
Parallel收集器
Parallel Scavenge收集器类似ParNew也是一个新生代垃圾收集器,使用复制算法,也是一个并行的多线程的垃圾收集器,俗称吞吐
量优先收集器。一句话:串行收集器在新生代和老年代的并行化
首先先科普下什么是吞吐量:
吞吐量(Thoughput)=运行用户代码时间(运行用户代码时间+垃圾收集时间),也即比如程序运行100分钟,垃圾收集时间1分钟,
吞吐量就是99%)。
Parallel收集器重点关注的是:
可控制的高吞吐量意味着高效利用CPU的时间,它多用于在后台运算而不需要太多交互的任务。
**自适应调节策略也是Parallel Scavenge收集器与ParNew收集器的-一个重要区别。**自适应调节策略:虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间(-XX:MaxGCPauseMillis)或最大的吞吐量。
常用JVM参数: -XX:+UseParallelGC或-XX:+UseParallelOldGC(可互相激活)使用Parallel Scanvenge收集器
开启该参数后:使用Parallel收集器+Parallel Old的组合。新生代使用复制算法,老年代使用标记-整理算法。
Serial Old收集器
SerialOlid是Serial垃圾收集器老年代版本,它同样是个单线程的收集器,使用标记-整理算法,这个收集器也主要是运行在Client默
的java虚拟机默认的年老代垃圾收集器。
在Server模式下,主要有两个用途(了解,版本已经到8及以后): .
1.在JDK1.5之前版本中与新生代的Parallel Scavenge收集器搭配使用。 ( Parallel Scavenge + Serial Old )
2.作为老年代版中使用CMS收集器的后备垃圾收集方案。
Parallel Old收集器
Parallel Old收集器是Parallel Scavenge的老年代版木,使用多线程的标记-整理算法,Parallel Old收集器在JDK1.6才开始提供。
在JDK1.6之前,新生代使用Parallel Scavenge收集器只能搭配年老代的Serial Old收集器,只能保证新生代的吞吐量优先,无法保
证整体的吞吐量。在JDK1.6之前(Parallel Scavenge + Serial Old )
Parallel Old正是为了在年老代同样提供吞吐量优先的垃圾收集器,如果系统对吞吐量要求比较高,JDK1.8后可以优先考虑新生代
Parallel Scavenge和年老代Parallel Old收集器的搭配策略。在 JDK1.8及后(Parallel Scavenge + Parallel Old )
JVM常用参数:
-XX:+UseParallelOldGC使用Parallel Old收集器,设置该参数后,新生代Parallel+老年代Parallel Old。
CMS(Concurrent Mark Sweep)
CMS收集器(Concurrent Mark Sweep: 并发标记清除)是一种以获取最短回收停顿时间为目标的收集器。
适合应用在互联网站或者B/S系统的服务器上,这类应用尤其重视服务器的响应速度,希望系统停顿时间最短。
CMS非常适合堆内存大、CPU核数多的服务器端应用,也是G1出现之前大型应用的首选收集器。
Concurrent Mark Sweep并发标记清除,并发收集低停顿,并发指的是与用户线程一起执行。
启该收集器的JVM参数: -XX:+UseConcMarkSweepGC 开启该参数后会自动将-XX:+UseParNewGC打开
开启该参数后,使用ParNew(Young区用) + CMS(Old区用) + Serial Old的收集器组合,Serial Old将作为CMS出错的后备收集器
CMS收集器的运行过程分为下列4步:
**初始标记:**标记GC Roots能直接关联的对象。速度很快但是存在Stop The World。
**并发标记:**进行GC Roots Tracing 的过程,找出存活对象且用户线程可并发执行。
**重新标记:**为了修正并发标记期间因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录。仍然存在Stop The World问题。
**并发清除:**对标记的对象进行清除回收。
**CMS优点:**并发收集低停顿
缺点:
1.浮动垃圾:由于CMS并发清理阶段用户线程还在运行着,伴随程序运行自然会有新垃圾产生,这部分垃圾得标记过程之后,所以CMS无法在当收集中处理掉他们,只好留待下一次GC清理掉,这一部分垃圾称为浮动垃圾。在jdk1.5默认设置下,CMS收集器当老年代使用了68%的空间就会被激活,可以通过-XX:CMSInitialOccupancyFraction的值来提高触发百分比,在jdk1.6中CMS启动阈值提升到了92%,要是CMS运行期间预留的内存无法满足程序的需要,就会出现”Concurrent Mode Failure“,然后降级临时启用Serial Old收集器进行老年代的垃圾收集,这样停顿时间就很长了。所以-XX:CMSInitialOccupancyFraction设置太高容易导致大量”Concurrent Mode Failure“。
2.有空间碎片:CMS是一款基于“标记-清除”算法实现的,所以会产生空间碎片。为了解决这个问题,CMS提供了-XX:UseCMSCompactAtFullCollection开发参数用于开启内存碎片的合并整理,由于内存整理是无法并行的,所以停顿时间会变长。还有-XX:CMSFullGCBeforeCompaction,这个参数用于设置多少次不压缩Full GC后,跟着来一次带压缩的(默认为0)。
3.对CPU资源敏感。在并发标记和并发清除阶段虽然不会停止用户线程,但是会因为占用一部分cpu资源进行垃圾回收导致用户程序变慢。
CMS默认启动的回收线程数是(cpu数量+3)/4。所以CPU数量少会导致用户程序执行速度降低较多。
G1收集器
G1适用于全堆,既可以在新生代使用和老年代使用。G1与之前的收集器有很大的不同,是从不同的角度去设计的。
回想下之前的垃圾收集器的特点:
1.年轻代和老年代都是各自独立的连续的内存块。
2.年轻代Eden+from+to使用复制算法
3.老年代的收集必须扫描全部老年代内存空间。
4.都是以尽可能少而快速地执行GC为设计原则
G1收集器的设计目标是取代CMS收集器,它同CMS相比,在以下方面表现的更出色:
1、G1是一个有整理内存过程的垃圾收集器,不会产生很多内存碎片。
2、G1的Stop The World(STW)更可控,G1在停顿时间上添加了预测机制,用户可以指定期望停顿时间。
CMS垃圾收集器虽然减少了暂停应用程序的运行时间,但是它还是存在着内存碎片问题。于是,为了去除内存碎片问题,同时又保留
CMS垃圾收集器低暂停时间的优点,JAVA7发布了一个新的垃圾收集器——G1垃圾收集器。
G1是在2012年才在jdk1.7u4中可用。 oracle官方计划在jdk9中将G1变成默认的垃圾收集器以替代CMS。它是一 款面向服务端应用的收
器,主要应用在多CPU和大内存服务器环境下,极大的减少垃圾收集的停顿时间,全面提升服务器的性能,逐步替换java8以前的CM:
集器。
主要改变是Eden,Survivor和Tenured等内存区域不再是连续的了,而是变成了一个个大小一样的region ,每个region从1M到32M不等。- - 个region有可能属于Eden, Survivor或 者Tenured内存区域。
底层原理
G1的最大好处是化整为零,避免全内存扫描,只需要按照区域来进行扫描即可。
G1收集器大致可分为如下步骤:
**初始标记:**仅标记GC Roots能直接到的对象,并且修改TAMS(Next Top at Mark Start)的值,让下一阶段用户程序并发运行时,能在正确可用的Region中创建新对象。(需要线程停顿,但耗时很短。)
**并发标记:**从GC Roots开始对堆中对象进行可达性分析,找出存活对象。(耗时较长,但可与用户程序并发执行)
**最终标记:**为了修正在并发标记期间因用户程序执行而导致标记产生变化的那一部分标记记录。且对象的变化记录在线程Remembered Set Logs里面,把Remembered Set Logs里面的数据合并到Remembered Set中。(需要线程停顿,但可并行执行。)
**筛选回收:**对各个Region的回收价值和成本进行排序,根据用户所期望的GC停顿时间来制定回收计划。(可并发执行)
总结
到此这篇关于JVM的7种垃圾回收器(小结)的文章就介绍到这了,更多相关JVM 垃圾回收器内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!