垃圾回收器
图中展示了7种作用于不同分代的收集器,如果两个收集器之间存在连线,则说明它们可以搭配使用。
虚拟机所处的区域则表示它是属于新生代还是老年代收集器。
新生代收集器:Serial、ParNew、Parallel Scavenge
老年代收集器:CMS、Serial Old、Parallel Old
整堆收集器: G1
Serial/Serial Old
最基本,最悠久的收集器,单线程进行垃圾收集工作,在进行垃圾收集时会 stop the world,即停止一切工作线程,直到完成收集任务,简单而高效,没有复杂的线程交互,一般情况下停止的时间也不会太久,但这停止的时间仍然是一个问题,后续所有的垃圾收集器其优化时间的部分主要就是在减少stio the world的时间。主要在Client(客户端)使用。
ParNew
新生代的收集器。ParNew其实就是多线程版的Serial,在单核或核数线程数不多的情况下,其性能不会比Serial高,因为其需要进行线程间的交互,但是在多核多线程的情况下,还是能达到一定情况下的垃圾回收速度的提升,也就是减少了stop the world 的时间。
Parallel Scavenge
新生代的收集器。Parallel Scavenge 也是一个并行的多线程收集器,和其他垃圾收集器不同的是,它更关注吞吐量(运行用户代码的时间/运行用户代码的时间+垃圾回收时间),而其他收集器更注重让垃圾回收时线程停顿时间更短。
(即一个在乎的是一段时间内用在垃圾回收上时间的总长短,一个在乎每次垃圾回收的时间长短。)停顿时间短适合需要实时交互的系统,而吞吐量高可以高效利用cpu时间,尽快完成运算任务,适合在后台运算不需要太多实时交互的任务。
Parallel Scavenge收集器使用两个参数控制吞吐量:
-
XX:MaxGCPauseMillis 控制最大的垃圾收集停顿时间
-
XX:GCRatio 直接设置吞吐量的大小
Serial Old
老年代的收集器。serial的老年代版的收集器,单线程收集器,使用标记整理算法,主要意义是给Client(客户端)使用,在server模式下,主要有两大用途。
-
在jdk1.5及之前的版本中搭配Parallel Scavenge使用
-
作为CMS收集器的后备方案,在并发收集发闪送Concurrent Mode Failure时使用。
Parallel Old
老年代的收集器。Parallel Scavenge的老年代版本。使用多线程和标记整理算法。在jdk1.6中才开始提供,在注重吞吐量和cpu资源敏感的场合中,可以使用Parallel Scavenge+Parallel Old 的收集器组合。
CMS(Concurrent Mark Sweep)收集器
cms是一种追求最短回收停顿时间的收集器,适合要求快速的服务响应速度,希望系统停顿时间最短的场景。
其核心思路就是,把垃圾回收分为几个步骤,将几个步骤中可以不需要stop the world的提取出来并发执行,这样一来,stop the world中做的内容就少了,而stop the world的时间也就少了。
cms基于标记-清除算法。
cms的回收过程基本上可以分为四个步骤
-
初始标记 ---stop the world
-
并发标记
-
重新标记 ---stop the world
-
并发清除
四个步骤具体做了什么
-
初始标记 ---stop the world
标记一下GC Roots能直接关联到的对象,速度很快。
-
并发标记
进行GC Roots Traacing的过程,也就是根据GC Roots进行可达性分析,筛选出垃圾对象。
-
重新标记 ---stop the world
修正并发标记期间因用户程序继续运作而导致标记产生变化的那一部分对象的标记记录。就停顿时间来说,该阶段比初始标记稍长,远远比并发标记短。
-
并发清除
清除垃圾对象。
优点:
由于垃圾回收中并发标记和并发清除这两个步骤较为耗时,而在cms中,这两个步骤都是可以与用户线程一起并发执行的,所以cms垃圾收集器进行垃圾回收时所带来的系统停顿时间大大降低。
缺点:
-
cms收集器对cpu资源敏感,即要占用较多的线程数或核心来执行自己的运算,吞吐量不高。
-
无法处理浮动垃圾,所谓浮动垃圾,是cms由于在并发清理阶段是与用户线程并发执行的,所以会在清理的适合产生新的垃圾,而这些垃圾没有被标记也就不会被回收,只能等待下次gc。
-
可能出现Concurrent Mode Failure 失败。由于在垃圾清理时用户线程还在执行,所以cms无法像其他垃圾收集器那样,等到内存快满的时候才进行垃圾回收,因为如果这样,很有可能在垃圾回收的时候因为用户线程的操作导致内存不足,这就出现 Concurrent Mode Failure 失败,如果cms运行期内预留的内存无法满足重新需求,出现Concurrent Mode Failure 失败,jvm就会启动后备方案:临时启用Serial Old来重新进行老年代的垃圾收集,这样停顿时间就更长了。
-
空间碎片的产生。由于cms是基于标记-清除算法的,这就导致收集结束可能会产生大量的空间碎片,当出现由于空间碎片问题而导致将要出现Full GC时,cms收集器就会开启内存碎片的合并整理过程,这个过程不是并发的,也就导致出现这种情况时停顿的时间变得更长。
G1(Garbage-First)
特点简述:
-
单独一个G1收集器就可以管理整个堆的内存,无需配合其他收集器。
-
并发并行,尽可能减少了stop the world 的时间。
-
采用分代算法,大方向看是标记-整理算法,小区间看是复制算法,故而不会产生空间碎片。
-
将整个堆分为一个个Region,通过对每个Region回收价值的估算来决定回收顺序,获得较高的垃圾回收效率。
-
可预测的停顿,支持设置在一定时间内消耗在垃圾回收上的时间不得超过多少毫秒。
确定垃圾对象
引用计数算法
给对象添加一个引用计数器,每当有一个地方引用它时,计数器加1,引用失效时,计数器减1,当计数器为0时,该对象就被判定为垃圾对象。
缺点:无法解决循环引用问题。即A引用了B,B引用了A,但AB都没有彼此之外的其他被引用处,这就导致这两个对象实际上我们无法使用也就是垃圾对象,但他们的计数器却不为0,因此无法被回收。
可达性分析
通过一系列的GC Roots的对象作为起始点,从这些节点开始向下搜索,搜索走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连接时,则证明该对象是不可用的。
java中作为GC Roots的对象有以下几种:
-
虚拟机栈(栈帧中的本地变量表)中引用的对象
-
方法区中类静态属性引用的对象
-
方法区中常量引用的对象
-
地方方法栈中JNI引用的对象
可达性分析标记过程:
一个对象被回收,至少经过两次标记过程。
-
第一次标记:将经过可达性分析后,对GC Roots不可达的对象进行标记。
-
第二次标记:对第一次标记的对象进行第二次标记,主要是看是否需要执行其finalize方法,假如finalize没有被覆写或者已经被执行过,那么就判断为不需要执行,直接进行第二次标记。如果需要执行finalize,则将其放入F-Queue队列中,后续会执行其finalize方法(不保证执行完毕,为了防止执行该方法时太过缓慢乃至死循环导致内存回收系统失效),如果在finalize中拯救了自己,即将自己与GC Roots建立引用链,那么就会被移除即将回收的集合。否则依旧被回收。
finalize:该方法由于执行不稳定,结果不确定,消耗大,故而完全不推荐使用该方法实现任何功能。
垃圾回收算法
标记清除算法:垃圾回收算法最基础的算法
![]() |
|
复制算法:解决了标记清除法内存碎片的问题
![]() |
|
标记整理算法:结合标记清除和复制算法优点的优化算法
![]() |
|
分代收集算法:
无论是哪种垃圾收集算法都无法适用于所有类型(长生命周期,短生命周期,大对象,小对象)的对象进行垃圾回收,故而针对不同类型的对象,JVM采用不同的垃圾回收算法,称为分代收集算法。
例如:在新生代采用复制算法,在老年代采用标记清除算法。
分区收集算法:
分区算法将整个堆空间划分为连续的大小不同的小区域,对每个小区域都单独进行内存使用和垃圾回收,这样做的好处是可以根据每个小区域内存的大小灵活使用和释放内存。
分区收集算法可以根据系统可接受的停顿时间,每次都快速回收若干个小区域的内存,以缩短垃圾回收时系统停顿的时间,最后以多次并行累加的方式逐步完成整个内存区域的垃圾回收。如果垃圾回收机制一次回收整个堆内存,则需要更长的系统停顿时间,长时间的系统停顿将影响系统运行的稳定性。
优点:停顿时间短且可控