本文主要是介绍【JVM】自动内存管理机制《五》---垃圾收集器(索命黑白无常回收垃圾对象),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
目录
导读
垃圾收集器有哪些
Serial收集器——单线程收集器
特点:
应用场景:
设置参数
ParNew收集器——Serial收集器的多线程版本
特点
应用场景
设置参数
为什么只有ParNew能与CMS收集器配合
Parallel Scavenge收集器
特点
应用场景
设置参数
Serial Old收集器
特点
应用场景
Parallel Old收集器
特点
应用场景
设置参数
CMS(Concurrent Mark Sweep)收集器
特点
应用场景
CMS收集器运作过程
设置参数
缺点
CMS&Parallel Old
G1 收集器
特点
应用场景
具体什么情况下应用G1垃圾收集器比CMS好,可以参考以下几点(但不是绝对):
建议:
设置参数
工作流程
总结
导读
上篇博客讲完啦:垃圾收集算法,今天接着讲垃圾收集算法的实现:垃圾收集器。jvm的自动内存管理机制就是由这个垃圾收集器来实现的。前面讲了垃圾收集器能做什么,现在讲下垃圾收集器都有哪些
JVM规范对于垃圾收集器的应该如何实现没有任何规定,因此不同的厂商、不同版本的虚拟机所提供的垃圾收集器差别较大,这里只看HotSpot虚拟机。就像没有最好的算法一样,垃圾收集器也没有最好,只有最合适。我们能做的就是根据具体的应用场景选择最合适的垃圾收集器。
内存回收机制
* 对象存活判定算法
* 哪些内存需要回收
* 垃圾收集算法
* 垃圾收集器(对垃圾收集算法的实现)
内存分配与回收策略
* 原则
垃圾收集器有哪些
正如每种算法都有存在的原因,该串行收集器也有存在的原因。每种垃圾收集器都有它存在意义,优点和缺点。所以我们需要选择真正适合我们系统的垃圾收集器!上图中连线的就是可以互相搭配使用的收集器。
我们先明确一个观点:虽然我们是在对各个收集器进行比较,但并非为了挑选出一个最好的收集器。因为直到现在为止还没有最好的收集器,更没有万能的收集器,如果有完美的收集器存在,那么HotSpot虚拟机就没必要实现那么多不同的收集器啦,所以我们选择的只能是具体应用最合适的收集器。
Serial收集器——单线程收集器
它的 “单线程” 的意义不仅仅意味着它只会使用一条垃圾收集线程去完成垃圾收集工作,更重要的是它在进行垃圾收集工作的时候必须暂停其他所有的工作线程( “Stop The World” :将用户正常工作的线程全部暂停掉),直到它收集结束。
正如每种算法都有存在的原因,该串行收集器也有存在的原因:因为简单而高效(与其他收集器的单线程比),对于限定单个CPU的环境来说,没有线程交互的开销,专心做GC,自然可以获得最高的单线程效率。串行收集器的缺点很明显,虚拟机的开发者当然也是知道这个缺点的,所以一直都在缩减Stop The World的时间。
收集器运行过程如下图:
特点:
- 针对新生代的收集器;
- 采用复制算法;
- 单线程收集;
- 进行垃圾收集时,必须暂停所有工作线程,直到完成;
即会"Stop The World";
应用场景:
- 依然是HotSpot在Client模式下默认的新生代收集器;
- 也有优于其他收集器的地方:
- 简单高效(与其他收集器的单线程相比);
- 对于限定单个CPU的环境来说,Serial收集器没有线程交互(切换)开销,可以获得最高的单线程收集效率;
- 在用户的桌面应用场景中,可用内存一般不大(几十M至一两百M),可以在较短时间内完成垃圾收集(几十MS至一百多MS),只要不频繁发生,这是可以接受的
设置参数
添加该参数来显式的使用串行垃圾收集器:
"-XX:+UseSerialGC"
ParNew收集器——Serial收集器的多线程版本
ParNew收集器其实就是Serial收集器的多线程版本,除了使用多线程进行垃圾收集外,其余行为(控制参数、收集算法、回收策略等等)和Serial收集器完全一样。
是许多运行在Server模式下的虚拟机的首要选择,除了Serial收集器外,目前只有它能与CMS收集器配合工作。
CMS收集器是一个被认为具有划时代意义的并发收集器,因此如果有一个垃圾收集器能和它一起搭配使用让其更加完美,那这个收集器必然也是一个不可或缺的部分了。
收集器运行过程如下图:
特点
- 除了多线程外,其余的行为、特点和Serial收集器一样;
- 如Serial收集器可用控制参数、收集算法、Stop The World、内存分配规则、回收策略等;
- Serial收集器共用了不少代码;
应用场景
在Server模式下,ParNew收集器是一个非常重要的收集器,因为除Serial外,目前只有它能与CMS收集器配合工作;
但在单个CPU环境中,不会比Serail收集器有更好的效果,因为存在线程交互开销。
设置参数
指定使用CMS后,会默认使用ParNew作为新生代收集:
"-XX:+UseConcMarkSweepGC"
强制指定使用ParNew:
"-XX:+UseParNewGC"
指定垃圾收集的线程数量,ParNew默认开启的收集线程与CPU的数量相:
"-XX:ParallelGCThreads"
为什么只有ParNew能与CMS收集器配合
CMS是HotSpot在JDK1.5推出的第一款真正意义上的并发(Concurrent)收集器,第一次实现了让垃圾收集线程与用户线程(基本上)同时工作; CMS作为老年代收集器,但却无法与JDK1.4已经存在的新生代收集器Parallel Scavenge配合工作;因为Parallel Scavenge(以及G1)都没有使用传统的GC收集器代码框架,而另外独立实现;而其余几种收集器则共用了部分的框架代码;
Parallel Scavenge收集器
它的关注点是:吞吐量(如何高效率利用CPU),
而CMS等垃圾收集器的关注点更多的是:用户线程的停顿时间(提高用户体验)。
所谓吞吐量就是CPU中用于运行用户代码的时间与CPU总消耗时间的比值。
运行示意图
Parallel Scavenge收集器提供了很多参数供用户找到最合适的停顿时间或最大吞吐量,如果对于收集器运作不太了解的话,不进行手工优化,可以选择把内存管理优化交给虚拟机去完成。
特点
- 新生代收集器;
- 采用复制算法;
- 多线程收集;
- CMS等收集器的关注点是尽可能地缩短垃圾收集时用户线程的停顿时间;而Parallel Scavenge收集器的目标则是达一个可控制的吞吐量(Throughput);
应用场景
- 高吞吐量为目标,即减少垃圾收集时间,让用户代码获得更长的运行时间;
- 当应用程序运行在具有多个CPU上,对暂停时间没有特别高的要求时,即程序主要在后台进行计算,而不需要与用户进行太多交互;
- 例如,那些执行批量处理、订单处理(对账等)、工资支付、科学计算的应用程序;
设置参数
Parallel Scavenge收集器提供两个参数用于精确控制吞吐量:
控制最大垃圾收集停顿时间
"-XX:MaxGCPauseMillis"
- 控制最大垃圾收集停顿时间,大于0的毫秒数;
- MaxGCPauseMillis设置得稍小,停顿时间可能会缩短,但也可能会使得吞吐量下降;因为可能导致垃圾收集发生得更频繁;
设置垃圾收集时间占总时间的比率
"-XX:GCTimeRatio"
设置垃圾收集时间占总时间的比率,0 < n < 100的整数;
GCTimeRatio相当于设置吞吐量大小;
垃圾收集执行时间占应用程序执行时间的比例的计算方法是: 1 / (1 + n) 。
例如,选项-XX:GCTimeRatio=19,设置了垃圾收集时间占总时间的5% = 1/(1+19);默认值是1% = 1/(1+99),即n=99;
垃圾收集所花费的时间是年轻一代和老年代收集的总时间;
如果没有满足吞吐量目标,则增加代的内存大小以尽量增加用户程序运行的时间;
GC自适应的调节策略(GC Ergonomics)
另外还有一个参数:
"-XX:+UseAdptiveSizePolicy"
开启这个参数后,就不用手工指定一些细节参数,如:
- 新生代的大小(-Xmn)、Eden与Survivor区的比例(-XX:SurvivorRation)、晋升老年代的对象年龄(-XX:PretenureSizeThreshold)等;
- JVM会根据当前系统运行情况收集性能监控信息,动态调整这些参数,以提供最合适的停顿时间或最大的吞吐量,这种调节方式称为GC自适应的调节策略(GC Ergonomics);
- 这是一种值得推荐的方式:
- (1)、只需设置好内存数据大小(如"-Xmx"设置最大堆);
- (2)、然后使用"-XX:MaxGCPauseMillis"或"-XX:GCTimeRatio"给JVM设置一个优化目标;
- (3)、那些具体细节参数的调节就由JVM自适应完成;这也是Parallel Scavenge收集器与ParNew收集器一个重要区别;
以上介绍的是新生代的垃圾收集器,以下是老年代的垃圾收集器
Serial Old收集器
Serial收集器的老年代版本,它同样是一个单线程收集器。
特点
- 针对老年代;
- 采用"标记-整理-压缩"算法(Mark-Sweep-Compact);
- 单线程收集;
Serial/Serial Old收集器运行示意图在前面有。
运行示意图:
应用场景
- 主要用于Client模式;
- 而在Server模式有两大用途:
- (A)、在JDK1.5及之前,与Parallel Scavenge收集器搭配使用(JDK1.6有Parallel Old收集器可搭配Parallel Scavenge收集器);
- (B)、作为CMS收集器的后备预案,在并发收集发生Concurrent Mode Failure时使用;
Parallel Old收集器
Parallel Scavenge收集器的老年代版本。
使用多线程和“标记-整理”算法。在注重吞吐量以及CPU资源的场合,都可以优先考虑 Parallel Scavenge收集器和Parallel Old收集器。在JDK1.6才有的。
特点
- 针对老年代;
- 采用"标记-整理-压缩"算法;
- 多线程收集;
Parallel Scavenge/Parallel Old收集器运行示意图如下:
应用场景
- JDK1.6及之后用来代替老年代的Serial Old收集器;
- 特别是在Server模式,多CPU的情况下;
- 这样在注重吞吐量以及CPU资源敏感的场景,就有了Parallel Scavenge(新生代)加Parallel Old(老年代)收集器的"给力"应用组合;
设置参数
指定使用Parallel Old收集器:
"-XX:+UseParallelOldGC"
CMS(Concurrent Mark Sweep)收集器
CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。它非常适合在注重用户体验的应用上使用。
特点
- 针对老年代
- 基于"标记-清除"算法(不进行压缩操作,会产生内存碎片)
- 以获取最短回收停顿时间为目标
- 并发收集、低停顿
- 需要更多的内存
CMS是HotSpot在JDK1.5推出的第一款真正意义上的并发(Concurrent)收集器;第一次实现了让垃圾收集线程与用户线程(基本上)同时工作;
应用场景
- 与用户交互较多的场景;(如常见WEB、B/S-浏览器/服务器模式系统的服务器上的应用)
- 希望系统停顿时间最短,注重服务的响应速度;
- 以给用户带来较好的体验;
CMS收集器运作过程
从名字中的Mark Sweep这两个词可以看出,CMS收集器是一种 “标记-清除”算法实现的,它的运作过程相比于前面几种垃圾收集器来说更加复杂一些。整个过程可分为四个步骤:
- 初始标记: 暂停所有的其他线程,初始标记仅仅标记GC Roots能直接关联到的对象,速度很快;
- 并发标记
- 并发标记就是进行GC Roots Tracing的过程;
- 同时开启GC和用户线程,用一个闭包结构去记录可达对象。但在这个阶段结束,这个闭包结构并不能保证包含当前所有的可达对象。因为用户线程可能会不断的更新引用域,所以GC线程无法保证可达性分析的实时性。所以这个算法里会跟踪记录这些发生引用更新的地方;
- 重新标记: 重新标记阶段就是为了修正并发标记期间因为用户程序继续运行而导致标记产生变动的那一部分对象的标记记录(采用多线程并行执行来提升效率);需要"Stop The World",且停顿时间比初始标记稍长,但远比并发标记短;
- 并发清除: 开启用户线程,同时GC线程开始对为标记的区域做清扫,回收所有的垃圾对象;
由于整个过程耗时最长的并发标记和并发清除过程收集器线程都可以与用户线程一起工作。所以总体来说,CMS的内存回收是与用户线程一起“并发”执行的。
CMS收集器运行示意图如下:
设置参数
指定使用CMS收集器
"-XX:+UseConcMarkSweepGC"
缺点
(一)对CPU资源敏感
(二)无法处理浮动垃圾
(三)产生大量内存碎片
CMS&Parallel Old
总体来看,CMS与Parallel Old垃圾收集器相比,CMS减少了执行老年代垃圾收集时应用暂停的时间;
但却增加了新生代垃圾收集时应用暂停的时间、降低了吞吐量而且需要占用更大的堆空间;
(原因:CMS不进行内存空间整理节省了时间,但是可用空间不再是连续的了,垃圾收集也不能简单的使用指针指向下一次可用来为对象分配内存的地址了。相反,这种情况下,需要使用可用空间列表。即,会创建一个指向未分配区域的列表,每次为对象分配内存时,会从列表中找到一个合适大小的内存区域来为新对象分配内存。这样做的结果是,老年代上的内存的分配比简单实用碰撞指针分配内存消耗大。这也会增加年轻代垃圾收集的额外负担,因为老年代中的大部分对象是在新生代垃圾收集的时候从新生代提升为老年代的。)当新生代对象无法分配过大对象,就会放到老年代进行分配。
G1 收集器
最新、技术最前沿的垃圾收集器。使用的垃圾收集算法:
- 对于新生代:复制算法
- 对于老年代:标记 - 整理算法
特点
- 并行
用户线程 & 垃圾收集线程同时进行。即在进行垃圾收集时,用户还能工作 - 多线程
即使用 多条垃圾收集线程(GC
线程) 进行垃圾收集
并发 & 并行 充分利用多CPU
、多核环境下的硬件优势 来缩短 垃圾收集的停顿时间
- 垃圾回收效率高
G1
收集器是 针对性 对Java
堆内存区域进行垃圾收集,而非每次都对整个Java
堆内存区域进行垃圾收集。- 即
G1
收集器除了将Java
堆内存区域分为新生代 & 老年代之外,还会细分为许多个大小相等的独立区域(Region
),然后G1收集器会跟踪每个Region
里的垃圾价值大小,并在后台维护一个列表;每次回收时,会根据允许的垃圾收集时间 优先回收价值最大的Region
,从而避免了对整个Java堆内存区域进行垃圾收集,从而提高效率。 - 因为上述机制,
G1
收集器还能建立可预测的停顿时间模型:即让 使用者 明确指定一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得从超出N毫秒。即具备实时性
- 即
- 分代收集
- 同时应用在 内存区域的新生代 & 老年代
- 不会产生内存空间碎片
- 从整体上看,
G1
收集器是基于 标记-整理算法实现的收集器 - 从局部上看,是基于 复制算法 实现
- 上述两种算法意味着
G1
收集器不会产生内存空间碎片。
- 从整体上看,
应用场景
服务器端虚拟机的内存区域(包括 新生代 & 老年代)
- 面向服务端应用,针对具有大内存、多处理器的机器;
- 最主要的应用是为需要低GC延迟,并具有大堆的应用程序提供解决方案;
- 如:在堆大小约6GB或更大时,可预测的暂停时间可以低于0.5秒;
(实践:对账系统中将CMS垃圾收集器修改为G1,降低对账时间20秒以上)
- 如:在堆大小约6GB或更大时,可预测的暂停时间可以低于0.5秒;
具体什么情况下应用G1垃圾收集器比CMS好,可以参考以下几点(但不是绝对):
- 超过50%的Java堆被活动数据占用;
- 对象分配频率或年代的提升频率变化很大;
- GC停顿时间过长(长于0.5至1秒);
建议:
- 如果现在采用的收集器没有出现问题,不用急着去选择G1;
- 如果应用程序追求低停顿,可以尝试选择G1;
- 是否代替CMS只有需要实际场景测试才知道。(如果使用G1后发现性能还没有使用CMS好,那么还是选择CMS比较好)
设置参数
可以通过下面的参数,来设置一些G1相关的配置。
指定使用G1收集器:
"-XX:+UseG1GC"当整个Java堆的占用率达到参数值时,开始并发标记阶段;默认为45:
"-XX:InitiatingHeapOccupancyPercent"为G1设置暂停时间目标,默认值为200毫秒:
"-XX:MaxGCPauseMillis"设置每个Region大小,范围1MB到32MB;目标是在最小Java堆时可以拥有约2048个Region:
"-XX:G1HeapRegionSize"新生代最小值,默认值5%:
"-XX:G1NewSizePercent"新生代最大值,默认值60%:
"-XX:G1MaxNewSizePercent"设置STW期间,并行GC线程数:
"-XX:ParallelGCThreads"设置并发标记阶段,并行执行的线程数:
"-XX:ConcGCThreads"
工作流程
G1
收集器的工作流程分为4个步骤:
- 初始标记
- 并发标记
- 最终标记
- 筛选回收
- 下面用一张图详细说明工作流程
总结
这篇关于【JVM】自动内存管理机制《五》---垃圾收集器(索命黑白无常回收垃圾对象)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!