如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。java虚拟机规范中对垃圾收集器应该如何实现并没有任何规定,因此不同的厂商、不同的版本的虚拟机所提供的垃圾收集器都有可能会有很大的区别,并且一般都会提供参数供用户根据自己的应用特点和要求组合出各个年代所使用的收集器。
相关系列博客:
上图中展示了不同年龄代的收集器,其中serial、parnew和parallel scavenge收集器作用于新生代,cms、parallel old 和 serial old作用于老年代,g1在新生代和老年代都可以使用。不同的收集器之间如果有连线,则说明他们可以相互搭配使用。
并行:指的是多条垃圾收集线程一起公共,但是此时用户工作线程仍处于等待状态。
并发:指的是用户线程和垃圾收集线程同时工作,也有可能是交替执行,用户程序在继续执行,而垃圾收集程序运行与另一个cpu上。
吞吐量:吞吐量就是cpu用于运行用户代码的时间与cpu总消耗时间的比值,即吞吐量 = 运行用户代码时间 /(运行用户代码时间 + 垃圾收集时间)。虚拟机总共运行了100分钟,其中垃圾收集花掉1分钟,那吞吐量就是99%。
serial收集器是一款串行执行的收集器,它是历史最悠久,也是最基本的收集器,采用复制算法实现的新生代收集器。在jdk1.3以前,serial收集器是新生代唯一的选择。它是一个单线程执行的收集器,工作时只会知用一个cpu或线程区执行,更重要的是serial在工作期间必须停掉所有的用户线程,直至垃圾收集完成,这一过程我们称之为“stop the world”。这项工作是由虚拟机自动执行和自动完成的,用户在不知情的情况下停掉了所有的线程,这对于一个最求响应速度来说简直是无法接受的。下图展示了serial收集器在工作时的运行流程:
由于serial收集器的工作模式是单线程的,自然就没有了多线程环境下线程切换带来的性能开销,所以该收集器在单线程环境下更加简单高效。
parnew是serial收集器的多线程版本,也是新生代收集器。parnew收集器和serial收集器除了多线程工作外几乎是相同的,包括所有控制参数、收集算法、stop the world,对象分配规则,回收策略等都是一样的。运行流程如下图:
虽然与serial收集器相比仅仅多了多线程特性外,没有其它的创新之处,但是它却是许多server模式下的虚拟机新生代收集器的首选,原因在于目前为止只有serial和parnew两个新生代收集器能够与性能优异的cms配合使用。关于cms介绍将在下文展开描述。
parallel scavenge也是一款使用复制算法的新生代收集器。该收集器与其它收集器不同的是,它关注的目标是达到一个可控制的吞吐量,而cms等收集器的关注点则是尽可能地减少用户线程地停顿时间,提高用户体验。parallel scavenge收集器提供了两个参数用于精确控制吞吐量,分别是控制最大垃圾收集停顿时间的-xx:maxgcpausemillis参数以及直接设置吞吐量大小的-xx:gctimeratio参数。也因此,parallel scavenge 被成为“吞吐量优先”收集器。
停顿时间越短就越适合与用户交互较多地程序,这样用户体验才更好。而高吞吐量则可以让出更多的cpu资源给用户线程,让程序更快的完成运算任务,更适合后台运算较多而不需要与用户交互的程序。
自适应调节策略是parallel scavenge收集器的特点,也是与parnew收集器的区别。parallel scavenge通过打开-xx:+useadaptivesizepolicy的设置,就不需要手动地调节新生代(-xmn)大小,eden和survivor区的比例(-xx:survivorratio)、晋升老年代对象年龄(-xx:pretenuresizethreshold)等细节参数,而是根据当前系统运行情况来确定这些参数,从而提高程序地吞吐量和缩短停顿时间,这一过程称之为gc自适应的调节策略(gc ergonomics)。
另外值得注意的一点是,parallel scavenge无法已cms配合使用,如果新生代选择了parallel scavenge收集器,那么老年代的收集器只能选用serial old或者parallel old来配置使用。
serial old是serial收集器的老年代版本,也是单线程工作的,使用的是“标记-整理”算法。
该收集器主要用于client模式下的虚拟机使用,如果在server模式下可以与parallel scavenge收集器配合使用;作为cms收集器的后备预案,在并发收集发生concurrent mode failure时使用。运行流程如下:
parallel old是parallel scavenge的老年代版本,也是一个并行收集器,使用“标记-整理”算法。该收集器在jdk1.6后对外提供使用,parallel scavenge 和 parallel old配合使用的话,更加适合应用于高吞吐量和cpu敏感资源的场合。下面是这两个收集器配合使用的运行流程:
cms(concurrent mark sweep)是一个并发收集器,使用了“标记-清除”算法来实现的。该收集器最求的更短的停顿时间,从而提升用户体验,因此也非常符合使用在网站、b/s系统的服务端的应用。
cms收集器的工作流程大概可以分为以下4个步骤:
由于标记和清除阶段可以和用户线程一起工作,因此几乎可以把cms收集器的工作是并发的:
cms是一款优秀的收集器,它的主要优点是低停顿,并发收集,因此也被成为并发低停顿收集器(concurrent low pause collector)。
当然,cms收集器也有一定的缺点,主要包括一下几点:
运行示意图如下:
g1(garbage-first)是一款面向服务端应用的垃圾收集器,jdk 7 update4 后开始进入商用。hotspot开发团队赋予它的使命是未来可以替换掉jdk 1.5中发布的cms收集器。之前提供的收集器都是仅作用于新生代或者是老年代,但是g1收集器可以作用于新生代和老年代,因为使用g1收集器是java heap的内存结构有很大的不同,它将整个java堆划分为多个大小相等的独立区域(region),虽然还保留有新生代和老年代的概念,但是他们已经没有了物理上的隔阂了,它们都是region的一部分的集合。
g1(garbage-first)收集器是当今收集器技术发展的最前沿成果之一,与其他收集器相比,g1收集器具有以下特征:
g1收集器之所以能够建立可预测的停顿时间模型,因为他能够有计划地避免在整个java堆中进行全区域的垃圾收集。g1跟踪各个region里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需要的经验值),在后台维护一个优先表,每次根据允许的收集时间,优先回收价值最大的region。这种使用region划分内存空间以及有优先级的区域回收方式,保证了g1收集器在有限的时间内可以获取尽可能高的收集效率。
在g1收集器中,region之间的对象引用以及其他收集器中的新生代和老年代之间的对象引用,虚拟机都是使用remembered set来避免全堆扫描的。g1中每个region都有一个与之对应的remembered set,虚拟机发现程序在对reference类型的数据进行写操作时,会产生一个write barrier暂时中断写操作,检查reference引用的对象是否处于不同的region中,如果是,便通过cardtable把相关引用信息记录到被引用对象所属的region中的remembered set之中。当进行内存回收时,在gc根节点的枚举范围中加入remembered set即可保证不对全堆扫描,也不会有遗漏。
如果不计算维护remembered set的操作,g1收集器的运作大致分为以下几个步骤:
执行流程如下图:
收集器 | 串行、并行or并发 | 新生代/老年代 | 算法 | 目标 | 适用场景 |
---|---|---|---|---|---|
serial | 串行 | 新生代 | 复制算法 | 响应速度优先 | 单cpu环境下的client模式 |
serial old | 串行 | 老年代 | 标记-整理 | 响应速度优先 | 单cpu环境下的client模式、cms的后备预案 |
parnew | 并行 | 新生代 | 复制算法 | 响应速度优先 | 多cpu环境时在server模式下与cms配合 |
parallel scavenge | 并行 | 新生代 | 复制算法 | 吞吐量优先 | 在后台运算而不需要太多交互的任务 |
parallel old | 并行 | 老年代 | 标记-整理 | 吞吐量优先 | 在后台运算而不需要太多交互的任务 |
cms | 并发 | 老年代 | 标记-清除 | 响应速度优先 | 集中在互联网站或b/s系统服务端上的java应用 |
g1 | 并发 | both | 标记-整理+复制算法 | 响应速度优先 | 面向服务端应用,将来替换cms |
参考资料: 《深入理解java虚拟机-jvm高级特性与最佳实践》 -周志明
如对本文有疑问, 点击进行留言回复!!
解决idea中出现“illegal character U+200B” 问题
荐 为什么加了@Transactional注解,事务没有回滚?
Attribute ‘sklearn.linear_model._logistic.LogisticRegression.multi_class‘ must be explicitly set to
Java/Python实现 LeetCode剑指Offer 14-I.剪绳子(动态规划)
网友评论