参考资料:详解CMS垃圾回收机制(强烈推荐)
一:为什么需要垃圾回收?
jvm把内存管理权从开发人员收回,开发人员只需要创建数据对象即可,内存的分配和回收都由jvm自动完成。
如果程序只管创建对象,不管对象的回收,jvm也不处理,那么内存很快就会被耗尽。
为什么要把内存管理权从开发人员收回?
- 专业的人做专业的事
- 使用方便
开发人员可以专注业务,而非陷入无休止的底层技术上;
二:怎么判断对象为垃圾?
如果要实现垃圾回收,首先必须能判断哪些对象是垃圾。
对象不再被使用就认为是垃圾。jvm自动回收垃圾,但它如何才能知道一个对象是否不再被使用?
常见的策略有如下两种:引用计数器 、可达性检测。
2.1 引用计数器:
即如果一个对象被外部引用则计数器加 1, 反之减 1。如果计数器为0,则说明当前对外象没有被任何外部使用,则认为是垃圾。
- 优点:
- 缺点:
无法解决循环引用的问题;
如:对象A,B相互引用,除此再没有被其它对象引用,那么它们两个都是垃圾,但计数器却均为1,而无法回收。
注意事项:引用计数器只是一个理论方案,从来没有一个主流的jvm使用这种方式
2.2 可达性检测
引用计数器无法解决循环引用的问题,因此更好的办法是通过可达性分析。jvm中的任何非垃圾对象通过引用链向上追溯,都可以到达一些根对象(法方区的静态变量、常量、栈中的变量),这些根对象都是存活的对象,那么被活对象引用的对象很有可能会继续使用,因此反过来,从根对象向下追溯到的对象都可以认为是存活的对象。这种从根对象追溯的方法称为可达性分析。
如下:从根对象向下追溯,红色标记的对象是不可达的,因此它们就是垃圾,会被GC回收。
2.3 根对象种类
可以做为GC root(根对象)的对象有以下几种:
- 虚拟机栈(栈帧中变量引用的对象)
- 方法区中静态属性(static 属性)
- 方法区中的常量(static final),(jdk8及以上,为元数据区)
- 本地方法栈中引用的对象
三:垃圾回收算法
标记出哪些对象是垃圾后,就需要对这些垃圾对象进行回收。
常用的回收算法有:标记-清除、复制、标记-整理
3.1 标记-清除
通过标记、清除两个阶段回收垃圾对象。因为标记的是存活对象,清除的是非存活对象,所以需要两个阶段:先标记,再遍历所有对象,过滤出非存活对象。
如下图:(绿色-存活对象;红色-垃圾;白色-空闲)
首先,通过可达性分析,标记出存活的对象(绿色块)
其次,遍历堆中所有对象,把非存活的对象全部清空。
优点:实现简单,并且是其它算法的基础
缺点:A:标记效率不高,清除算法也不高(遍历所有对象进行清除).
B:产生大量内存碎片
3.2 复制算法
为了解决标记-清除 算法的效率问题,使用复制算法。
本质:空间换时间(用两块额外的内存做中转,就类似编程中两个变量互换值,需要一个temp变量做中介一样)
复制算法需要一块同样大小额外的内存做为中转。
因为复制的是存活对象,不需再次遍历。
步骤:通过可达性分析,标记出存活对象,并同时把存活对象复制到另一块对等内存。
当所有存活对象都复制完后,直接清空原内存块(不需要遍历,直接移动堆顶指针即可)。
优点: 不需要两阶段,存活对象少时效率高。
没有内存碎片
缺点:需要额片内存,同一时间总有一块内存处于备用状态-浪费内存。
存活对象很多时效率也不高(主要是因为对象复制代价高昂)
使用场景:存活对象多,内存紧张的场景。
复制算法变种:
复制算法最大的缺点是需要一个相同大小的内存块,为了减少内存浪费,复制算法还有一种变种。
如果对象中存活的很少,就不需要一个相同大小的额外内存块,而只需要两个小内存块,交替做为中转站就可以完美解决。
前提:存活的对象很少,IBM研究表明新生代90%以上甚至98%的对象朝生夕死。
步骤:
A:设置三块内存,第一块大内存块,第二第三为两个相等的小内存块
B:创建对象分配置在大内存块和 两小内存块中的任一个,另外一小内存块保持空闲备用。
C:回收:通过可达性分析,标记出第一块和其中使用的小块内存中存活对象,同时把存活对象复制到备用的另一块小内存中
D:清空大内存块和被回收的小块内存。此时:大内存被清空,其中两块小内存:一块清空,一块保存了上次存活的数
E:然后交替使用两块小内存块做为清空大内存和另一块小内存的中转。
优点:减少了内存浪费,同时又保持了复制算法的优点。
缺点:未完全杜绝内存浪费;另外,大数据量时,效率低;存活对象数量占比较大时,小内存块无法做为中转站。
使用场景:在存活对象较少,追求高效率,内存无碎片的场景。
3.3 标记-整理
标记清除算法效率低,碎片严重; 复制算法存活对象少时效率高,无碎片,但内存浪费;为了折中两种算法的优点,有人提供另一种算法:标记-整理算法。
步骤:
A:根据可达性分析,标记出所有存活的对象
B:遍历所有对象,过滤出非存活的对象,并把这些对象一个一个,从内存的某一个角落顺序排列。
优点:没有内存浪费,无碎片
缺点:效率最低,小于标记清除(需要两个阶段<标记,移动>;移动类似复制,代价高于直接清除,存活对象越多,移动代价越大)
四:分代算法
知道如何检查垃圾,也有了垃圾算法,那么一个垃圾回收系统基本成型;
但是,为了追求高效率,根据内存中对象的生命周期不同,结合各各占垃圾回收算法的特点和优劣,对堆内存模型再进行一个细粒度的划分,就可以创建出更好,更高效的回收机制 ----分代算法 ;
准确的讲,分代算法不是一种回收算法,它只是按对象生命周期和特点不同,合理选用以上三种回收算法的手段。
内存模型中(堆内存模型细化,完全因垃圾回收而产生),我们大概了解了堆内存的分代结构如下:
为什么需要分代?
因为不同的对象生命周期不同,有的很长(如:session),有的很短(如:方法中的变更);如果不分代,每次可达性分析标记时,都要遍历暂时不会回收的老对象,当老对象越来越多时,重复对老对象的无用遍利检查,会严重影响回收性能。
如果把对象按年龄隔离,分成新生代和老年代,老年代保存生命周期长的对象,新生代保存新创建的对象,那么老年代就可以长时间不回收,而新年代大部分是朝生夕死,就可以频繁回收。即保证了效率,又保证了新生代内存的及时回收。
总结:新生代:时间换空间(频繁回收:由于存活的数据量少,频繁回收的代价也可以接受)
老年代:空间换时间(需要时回收:存活的多,频繁回收严重影响性能;有些对象可能已经变垃圾了,但仍然存在老年代中,等到新生代不够或其它条件时,才回收老年代)
如何区分新老对象?
这个与垃圾回收器实现有关,对应回收器有相关的配置。
主要有几种情况:
- 大对象直接进行老年代
- 年龄达到一定阀值(每经历过一次回收还活着:年龄加1, 默认阀值为:15,可配置)
- survivor空间中相同年龄所有对象大小的总和超过survivor空间的一半时,即使没达到年龄阀值
五:垃圾回收器
垃圾回收算法只是垃圾回收的理论基础,垃圾回收器是对垃圾回算法的实现。
垃圾回收器关注三个方法:A:垃圾回收算法选择 B:串行和并行(并发)的选择 C:新老代的选择
下面先了解一下jvm中的垃圾回收器种类:
垃圾回收器根据新老代不同区分,一部分只用于新生代回收(Serial、ParNew、Parallel),一部分只用于老年代(Serial old、CMS、Parallel old); G1是一个特殊的存在,后续再讲。
下面我一个一个分析各自的原理及特点,然后分析他们为什么只能使用新生代或老年代;以及实战中如何选择。
5.1 Serial
serial/serial old 收集示意图(图片来自:JVM系列之垃圾回收(二))
- 使用于:新生代
- 垃圾回收算法:复制算法
- 串行/并行/并发:串行,单线程
- stw:是
5.2 ParNew
(图片来自:JVM系列之垃圾回收(二))
- 分代:用于新生代
- 垃圾回收算法:复制算法
- 串行/并行/并发:并发,多线程
- stw:是
5.3 Parallel Scavenge
- 分代:用于新生代
- 垃圾回收算法:复制算法
- 串行/并行/并发:并行,多线程
- stw:是
Parallel Scavenge 与 ParNew一样也是多线程,但是与ParNew不同的是,它关注的点是垃圾回收的吞吐量(用户线程时间/(用户线程时间 + 垃圾回收时间)),也就是:它期望尽可能压榨cpu,多用于业务捃,它关注的是整体,而不是一次。
如:假如每分钟执行1000次垃圾回收,每次的停顿时间很短,但1000次总停顿时间要高于 每分种100次的时间。那么100次垃圾回收就是Parallel Scavenge期望的。
5.4 Serial old
- 分代:用于老年代
- 垃圾回收算法:标记-整理算法
- 串行/并行/并发:串行,单线程
- stw:是
由于老年代,活的多,死的少,且最好没有碎片:标记整理算法;
跟Serial收集器一样,当前收集器也是单线程,因此也不适合多核时代的服务器上,是默认的client模式,同时做cms收集器失败时的备选收集器(因为cms是并发的,如果并发失败,就不要并发了,所以使用了serial Old)。
5.5 CMS
(图片来自:JVM系列之垃圾回收(二))
- 分代:用于老年代
- 垃圾回收算法:标记-清除算法,有碎片
- 串行/并行/并发:多线程
- stw:初始标记stw; 重新标记stw
CMS是首个并发收集器,垃圾回假步骤中的部分阶段可以与用户线程并发执行。
垃圾回收器的最终目标就是:减少垃圾回收对用户线程的影响(停顿频率小、停顿时间少)。
为此,CMS把垃圾回收分为四个阶段,把不需要停顿的阶段与用户线程一起执行:
- 初始标记
- 并发标记
- 重新标记
- 并发清理
5.6 Parallel old
(图片来自:JVM系列之垃圾回收(二))
- 分代:用于老年代
- 垃圾回收算法:标记-整理算法
- 串行/并行/并发:多线程
- stw:是
六:GC日志
七:jvm配置