垃圾收集器
2018-11-19 本文已影响0人
简书徐小耳
垃圾收集器的工作区域如下图:
垃圾收集器 是按照标记存活对象实现的,而标记死亡对象 应该需要扫描所有对象,标记存活对象只需要从gcroot出发即可吧
垃圾收集器.png
注重响应速度的是parNew+cms而注重吞吐量的时候parallel scavenge+parallel old
连线代表两种收集器可以搭配使用
目前最好的垃圾收集器,听说是官方最新出的zgc可惜本人并没有研究
-
这些收集器如果可以在某代里面工作,则都是在新生代采用复制算法,在老年代采用标记整理算法。
-
并行在这边的意思是几条垃圾收集线程可以并行工作,但是用户线程却stw.因为暂停了用户线程所以这个时候垃圾收集线程可以尽情的享受cpu,cpu多少核就用多少垃圾线程
-
并发则是用户线程和垃圾收集线程可以交替或者并行执行
serial收集器
- 是新生代的单线程收集器,其不仅仅会只使用一个cpu或一个线程去收集垃圾,在收集垃圾过程中,会出现stw现象。
优点是简单而高效(高效是与其他收集器的单线程比较),缺点是正常生产环境的stw无法容忍
parNew收集器(用在新生代)
- 其本质是serial收集器的多线程版本
- 因为cms是老年代最好的选择,而能与cms配合的新生代收集器只有serial和parNew,所以我们默认都选择parNew
parallel scavenge收集器(新生代)
- 其与parNew基本相似都是并行的多线程,但是前者更关注如何提高系统的响应速度,后者则关注如何提高吞吐量。
- 吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)
- 响应时间与垃圾收集时间相反
serial old收集器(老年代)
- 与serial相似都是单线程,只是垃圾收集算法不一样而已,其还会在cms发生concurrent mode failure时候作为后备垃圾收集器去进行工作。
parallel old收集器(老年代)
- 与parallel Scavenge收集器的老年代版本,其也是主要注重吞吐量
CMS收集器(老年代)
- 基于标记-清除算法
- 以获取最短垃圾回收停顿时间为目标的收集器。
- 我们之前的标记都是标记对象不能被GC root 关联的,在这边我们标记的是可被GC Root 引用链找到的
- 之所以可以并发,是因为gc线程并不会占用所有的cpu,占用的公式(cpu数量+3)/4
四个步骤:
初始标记
- 需要STW,初始标记只是标记下GC Root能直接关联到的对象,并不会进行深入最终是否还有其他对象被GC roots 间接引用。
并发标记
- 进行GC Roots tracing的过程
重新标记
- 需要STW,修正并发标记期间,因为用户程序继续运行而导致标记产生变动的那一部分对象的标记记录。则重新标记只是针对初始化标记和并发标记被标记到对象后期又可以被GC Roots引用的对象。
并发清除
- 清除无用对象
浮动垃圾:在标记过程的时候产生的垃圾就是浮动垃圾。因为老年代需要预留一点内存给用户线程,所以会在一个阈值的时候触发cms垃圾收集器,如果预留的内存无法满足程序运行就会触发concurrent mode failure。这个时候虚拟机将启动serial old 来进行老年代的垃圾收集。
缺点:
- 对cpu资源非常敏感,cpu数量较大的时候 不会抢占太多资源
- 无法处理浮动垃圾,从而导致concurrent mode failure,进而引起full gc。
- 碎片化,不过已经提供了压缩选择去除碎片,即进行了多次fullgc后进行压缩
G1收集器(年轻代和老年代)
- 基于标记-整理,不会出现碎片
- 可以精准的控制停顿
- 可以在不吸收吞吐量的前提下完成低停顿的内存回收,是因G1将整个java堆划分为多个大小固定的独立区域(region),并跟踪这些区域里面的垃圾堆积成都,在后台维护一个优先列表,每次根据允许收集事件,优先回收垃圾最多的区域。区域划分和优先级确保了G1的高效率
内存分配和回收策略
-
对象优先在eden区域,当eden区域没有足够的空间时候会进行minor gc
-
大对象直接进入老年代,避免了在enden区域和survivor去之间发生大量的内存拷贝
-
通过对象年龄计数器,多次存活的对象进入老年代
-
如果survivor空间中相同年龄所有对象大小总和大于survivor空间的一般,则大于或等于该年龄的对象直接进入老年代,无需等到满足年龄需求。
-在发送minor gc的时候 虚拟机会检测之前每次晋升到老年代的平均大小是否大于老年代的剩余空间大小,如果大于则minor gc 改为full gc,如果小于则查看是否允许担保,如果允许则继续进行minor gc 否则 也是full gc