文章目录
必须先了解的知识点
在前面我讲述了垃圾回收的相关算法:垃圾回收
而我们了解的垃圾回收算法都是为了实现垃圾回收器而服务的。垃圾回收器是对垃圾回收算法的具体实现。
上图即为对几种垃圾回收器的使用区域,有线相连的部分表示两种垃圾回收器可以搭配使用。
并行收集与并发收集
并行收集:多条垃圾回收线程并行工作,用户线程处于等待状态
并发收集:用户线程与垃圾收集线程同时工作(可能并行执行也可能交替执行),用户程序仍然在执行,垃圾收集程序运行在另一个CPU上。
吞吐量
- 程序运行用户代码的时间与CPU总消耗时间(用户运行代码时间+垃圾收集时间)的比值。
例:虚拟机总共运行100分钟,垃圾收集2分钟,运行用户代码98分钟,则吞吐量为98%
OopMap
OopMap主要记录栈中的本地变量在堆上对象的引用关系。
当进行垃圾回收时,我们需要对栈中的内存进行扫描,看看哪些位置储存了Reference类型,意味着它所引用对象这次不可以被回收,而栈中的本地变量便存在大量的非Reference类型,不得不扫描整个栈标记出Reference类型,这实际上造成了一种时间和资源的浪费。
最有效的方式是以空间换时间,将代表引用的位置记录下来,GC的时候直接读取出来,不需要一个一个扫描,大多数虚拟机也是采用的这种方法,使用OopMap的数据结构来储存这些信息。
- OopMap存在的主要意义是为了 帮助虚拟机实现准确式GC
安全点
如果每条指令的位置都要记录OopMap的话,消耗会比较大,我们只选用一些引用关系不变的位置来记录OopMap的话,仍然可以达到目的,这种特殊的位置即为 安全点 ,程序执行只有到达安全点时,才可以暂停进行GC,系统并不是任何时候都可以进行GC。
安全点的选定
- 安全点不能非常少,使得GC长时间的等待,也不可能非常多,增大运行时的负载。
- 一般会选用的标准为“是否具有程序长时间执行的特征”,程序的长时间执行最明显的特征就是指令序列的复用,如调用函数、循环跳转、异常跳转等。
当发生GC时,如何让所有线程都跑到“安全点”上,然后停下来?
- 有以下两种中断方式:
(1)抢占式中断:
当发生GC时,让所有的线程中断,如果某些线程中断没有在安全点上,则恢复线程,让它运行到安全点上。(现在基本没有虚拟机使用这种方式进行中断)
(2)主动式中断:当GC发生,需要线程进行中断时,不直接对线程进行操作,而是设置一个标记,各个线程主动去轮询这个标志,发现标志为真时,则线程自己中断挂起,而这个标志和安全点是重合的,所以这个过程刚好是在安全点上发生的。
安全区
安全点机制使得在程序运行不长的时间内就可以遇到安全点,但是如果当线程处于sleep或者blocked状态时,这时线程就无法相应JVM的中断请求。为了解决以上情况,提出了安全区。
- 安全区是指在一段代码中,引用关系不会发生变化,在此段代码任何地方进行GC都是安全的。
- 当代码进入到安全区时,就会标记自己已经进入安全区,当发生GC时就不会管那些已经标记自己进入安全区的线程。当线程离开安全区时,会检查系统是否正在执行GC,如果是则会等到GC完成后在离开安全区域。
Serial (串行) / Serial Old收集器
- Serial收集器是最基本、发展历史最悠久的收集器。
Serial Old收集器是Serial 收集器应用在老年代区域的版本。 - 特点:
(1)单线程
只用一条线程完成垃圾回收
(2)串行收集
在此收集器进行收集的同时,所有的用户线程必须暂停,仅有垃圾回收线程在运行。
(3)效率高
相比较于其他单线程收集器来说它没有线程交互的开销,效率比较高
(4)应用场景
Serial 收集器应用于客户端模式下新生代区域
Serial Old收集器应用于客户端模式下的老年代,如果在服务端模式下,那么它主要还有两大用途:
一种用途是在JDK 1.5以及之前的版本中与Parallel Scavenge收集器搭配使用;
另一种用途就是作为CMS收集器的后备预案,在并发收集发生"Concurrent Mode Failure"时使用。
(5)回收算法
Serial 采用复制算法
Serial Old采用标记-整理算法
- 工作流程:
ParNew收集器
- ParNew 收集器是Serial 收集器的多线程版本,除了使用多条线程进行垃圾回收之外,其余行为以及可用的所有控制参数,回收策略等都和Serial 没有区别。
- 特点:
(1)多线程
采用多线程进行垃圾回收
(2)并行收集
在此收集器进行收集的同时,所有的用户线程必须暂停。
(3)回收算法
与serial 相同,采用复制算法
(4)应用场景
ParNew 收集器是虚拟机运行在服务端模式下的新生代收集器。
(5)效率上相较于Serial 较低,因为存在线程交互开销。在多CPU环境下可以更好的利用资源。
- 工作流程:
【目前除了Serial 收集器只有ParNew可以和CMS收集器配合使用】
Parallel Scavenge / Parallel Old收集器
- Parallel Scavenge / Parallel Old 主要注重吞吐量,又被称为吞吐量收集器,注重点与其他收集器不同,主要目标是达到一个可控制的吞吐量。
- 特点:
(1)多线程
采用多线程进行垃圾回收。
(2)并行收集
在此收集器进行收集的同时,所有的用户线程必须暂停。
(3)收集算法
Parallel Scavenge采用复制算法
Parallel Old采用标记整理算法
(4)应用场景
这类收集器主要适用于多CPU、服务端模式下,以高吞吐量为目标、不需要和用户进行太多交互的场景。
Parallel Scavenge收集器是虚拟机运行在Server模式下的新生代垃圾收集器。
Parallel Old JDK1.6及之后用来代替老年代的Serial Old收集器
- 工作流程:
CMS收集器
- 并发标记清理(Concurrent Mark Sweep,CMS)收集器也称为并发低停顿收集器(Concurrent Low Pause Collector)或低延迟(low-latency)垃圾收集器,它以获取最短回收停顿时间为目标,第一次实现了垃圾收集线程和用户线程基本上同时工作。
- 特点:
(1)多线程
采用多线程进行垃圾回收。
(2)并发收集
当垃圾回收线程运行时,用户线程可以一起工作。
(3)回收算法
采用标记-清除算法。
(4)应用场景
应用于老年代,注重响应速度,希望系统停顿时间最短。
- 工作流程:
(1)初始标记:单线程执行,标记与GC roots直接关联的节点。时间非常短,需要暂停用户线程
(2)并发标记:遍历之前标记到的关联节点,继续向下标记所有存活节点。时间较长,但不停顿,不能保证标记出所有的存活对象。
(3)重新标记:重新遍历并发标记期间修改过的引用关系对象。时间介于初始标记与并发标记之间,通常不会很长。需要暂停用户线程
(4)并发清理:直接清除之前标记的垃圾,其他用户线程仍然可以工作。清理之后,将该线程占用的CPU切换给用户线程 - 缺点:
(1)对CPU资源十分敏感
在并发阶段,它虽然不会导致用户线程停顿,但是会因为占用了一部分线程(或者说CPU资源)而导致应用程序变慢,总吞吐量会降低。
CMS默认启动的回收线程数是(CPU数量+3)/ 4,也就是当CPU在4个以上时,并发回收时垃圾收集线程不少于25%的CPU资源,并且随着CPU数量的增加而下降。但是当CPU不足4个时,CMS对用户程序的影响就可能变得很大。
(2)无法处理浮动垃圾
由于CMS并发清理阶段用户线程还在运行着,伴随程序运行自然就还会有新的垃圾不断产生,这一部分垃圾出现在标记过程之后,CMS无法在当次收集中处理掉它们,只好留待下一次GC时再清理掉。这一部分垃圾就称为“浮动垃圾”。也是由于在垃圾收集阶段用户线程还需要运行,那也就还需要预留有足够的内存空间给用户线程使用,因此CMS收集器不能像其他收集器那样等到老年代几乎完全被填满了再进行收集,需要预留一部分空间提供并发收集时的程序运作使用。要是CMS运行期间预留的内存无法满足程序需要,就会出现一次“Concurrent Mode Failure”失败,这时虚拟机将启动后备预案:临时启用Serial Old收集器来重新进行老年代的垃圾收集,这样停顿时间就很长了。
(3) 产生大量内存碎片
CMS是一款基于“标记—清除”算法实现的收集器,这意味着收集结束时会有大量空间碎片产生。
空间碎片过多时,将会给大对象分配带来很大麻烦,往往会出现老年代还有很大空间剩余,但是无法找到足够大的连续空间来分配当前对象,不得不提前触发一次Full GC。
G1收集器
- G1(Garbage-First)是JDK7-u4才推出商用的收集器,是一款面向服务端应用的垃圾收集器 ,将新生代、老年代的划分取消了,G1将堆划分为若干个区域,但是仍然属于分代收集器。
Humongous区域。 如果一个对象占用的空间超过了分区容量50%以上,G1收集器就认为这是一个巨型对象。这些巨型对象,默认直接会被分配在年老代,但是如果它是一个短期存在的巨型对象,就会对垃圾收集器造成负面影响。为了解决这个问题,G1划分了一个Humongous区,它用来专门存放巨型对象。如果一个H区装不下一个巨型对象,那么G1会寻找连续的H分区来存储。为了能找到连续的H区,有时候不得不启动Full GC。 - 特点:
(1)并行与并发
G1 能充分利用多CPU、多核环境下的硬件优势,使用多个CPU来缩短“Stop The World”停顿时间
并行:使用多个CPU来缩短Stop-The-World停顿的时间,
并发:也可以并发让垃圾收集与用户程序同时进行
(2)分代收集
能独立管理整个GC堆(新生代和老年代),而不需要与其他收集器搭配;
能够采用不同方式处理不同时期的对象;
(3)空间整合
整体采用”清除—整理“算法,局部看(两个区域之间)采用复制算法,不会产生内存碎片。
(4)可预测停顿
- G1收集器之所以能建立可预测的停顿时间模型,是因为它可以有计划地避免在整个Java堆中进行全区域的垃圾收集。
- G1跟踪各个Region里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需时间的经验值),在后台维护一个优先列表,
- 每次根据允许的收集时间,优先回收价值最大的Region,这样就保证了在有限的时间内尽可能提高效率。(这也就是Garbage-First名称的来由)。
- 这种使用Region划分内存空间以及有优先级的区域回收方式,保证了G1收集器在有限的时间内可以获取尽可能高的收集效率。
(5)应用场景
面向服务端应用的垃圾收集器,针对具有大内存、多处理器的机器;最主要的应用是为需要低GC延迟,并具有大堆的应用程序提供解决方案
- 工作流程:
前两步标记过程都和CMS相同 - 最终标记:
修正并发标记时因用户程序继续运行导致标记产生变动的那部分对象的标记记录,将这些对象记录的数据更新到Remember Set中。(G1中每个Region都会维护一个Remember Set用来记录不同Region中对象的引用情况。Remember Set的作用就类似于OopMap,用来避免全堆扫描。) - 筛选回收:
(1)首先排序各个Region的回收价值和成本;
(2)然后根据用户期望的GC停顿时间来制定回收计划;
(3)最后按计划回收一些价值高的Region中垃圾对象;
(4)回收时采用"复制"算法,从一个或多个Region复制存活对象到堆上的另一个空的Region,并且在此过程中压缩和释放内存;
(5)可以并发进行,降低停顿时间,并增加吞吐量
Minor GC 和 Full GC的区别
- Minor GC : 发生在新生代,由于java对象大多朝生夕灭,存活时间非常短,所以Minor GC发生十分频繁。
- Full GC:发生在老年代,速度比Minor GC慢10倍以上,发生了Full GC至少伴随发生一次Minor GC。