JAVA虚拟机JVM-2.垃圾回收
判断对象是否“已死”
引用计数法
在对象中添加一个引用计数器,每当一个地方引用它时,计数器值+1,引用失效的时候,计数器值-1;当计数器值为0的时候说明对象“已死”。
这样做的好处就是简单快捷,且效率也很高,但是无法解决掉循环互相引用的问题。
可达性分析算法
通过一系列被成为“GC Roots”的根对象作为起始点集,从这些点开始,根据引用关系向下搜索,搜索过程所走的路径被称为“引用链”,如果某个对象到“GC Roots”间没有任何引用链,或者用图论的话来说就是从GC Roots到这个对象不可达时,这个对象就是不可能在被使用了。
引用概念
- 强引用:new对象就是这种引用关系,这种引用永远不会被回收。
- 软引用:描述一些还有用,但是非必须的对象。只被软引用关联着的对象,在系统发生内存溢出异常之前,会把这些对象列进回收范围中进行第二次回收,如果这次回收还没有足够的内存,才会抛出内存溢出异常。
- 弱引用:强度比软引用更弱的引用,只能生存到下次垃圾回收发生为止。
- 虚引用:“幽灵引用”或者“幻影引用”,最弱的引用关系。
判断是否死亡
即使被算法判定为不可达对象,也不是非死不可,宣告一个对象真正死亡是需要经过两次标记的过程:可达性分析标记为第一次,随后进行筛选,筛选条件是此对象是否有必要执行finalize()方法。
如果对象没有覆盖finalize()方法,或者finalize()方法已经被调用过,那么这两种情况虚拟机认为没必要在执行finalize()方法。
如果对象吧内判定有必要执行finalize()方法,那么该对象将会被放置在一个名为F-Queue的队列中,并在稍后由一条由虚拟机自动建立的,低调度优先级的Finalizer线程去执行他们的finalize()方法。
回收方法区
方法区的垃圾回收内容:废弃的常量以及不再使用的类型。
判断废弃的条件:
- 该类所有的实例都被回收。
- 加载该类的加载器也被回收。
- 该类对应的java.lang.Class对象没有被引用,无法在任何地方通过反射访问该类的方法。
垃圾回收算法
分代收集理论
建立在两个假说之上的理论:(第三条是为了解决新声代引用老年代的问题)
- 弱分代假说:绝大多数对象朝生夕灭
- 强分代假说:熬过越多次垃圾收集过程的对象就越难以消亡
- 跨代引用假说:跨代引用相对于同代引用来说仅占少数。
标记-清除算法
容易造成空间碎片化,如果遇见大对象需要连续空间,会触发Full GC
标记-复制算法
将内存分成三块区域,一块比较大的Eden区,和两块比较小的Survivor区域,比例默认为8:1:1。每次分配内存只使用一块Survivor区域和Eden区域,当发生垃圾收集时候,将Eden和Survivor中存活的对象一次性复制到另一块Survivor空间上,然后直接清掉Eden和已经使用过的Survivor空间。
如果其中一块Survivor空间不足于存放Minor GC之后存活的对象,就需要依赖其他区域进行分配担保(老年代担保)。
标记-整理算法
先标记之后,将空间进行整理,最后清除垃圾,这样保证了可用空间的连续性,优化了标记-清除算法中产生的空间碎片化问题。只是整理的时候对于系统的开销比较大,因为要对存活的对象进行移动,这样会产生“stop the word”的现象。
垃圾收集器
Serial收集器:
单线程收集器,新生代采用复制算法暂停所有用户线程(单线程),老年代采用标记-整理算法暂停所有用户线程(单线程)。
ParNew收集器:
多线程收集器,新生代采用复制算法暂停所有用户线程(多线程),老年代采用标记-整理算法暂停所有用户线程(单线程)。
Parallel Scavenge收集器
多线程收集器,但是和其他收集器关注点不同,关注的是吞吐量。
Serial Old收集器
是Serial收集器的老年代版本,同样是单线程收集器,使用标记清除算法。
Parallel Old收集器
是Parallel Scavenge收集器的老年代版本,支持多线程并发,基于标记整理算法实现。
CMS收集器
以获取最短回收停顿时间为目标的收集器。
- 初始标记。(单线程)
- 并发标记。(多线程)
- 重新标记。(多线程)
- 并发清除。(多线程)
G1收集器(Garbage First收集器)
开创了收集器面向局部收集的设计思路和基于Region的内存布局形式。
G1也会进行分代,但是分代的同时还进行了区域region划分,每个region的大小为2的n次方,大小范围在1-32MB之间。如果一个对象的大小超过了region的二分之一,那么认为这个对象是大对象。对于那些超过整个region大小的大对象,他们会被放在连续的region区域中。
工作的过程和CMS差不多,如下:
- 初始标记。(单线程)
- 并发标记。(多线程)
- 最终标记。(多线程)
- 筛选回收。(多线程)
其他垃圾收集器
- Shenandoah收集器:关注低延迟
- ZGC收集器:关注低延迟
- Epsilon收集器:没有任何回收行为的收集器
内存分配和回收策略
对象优先在Eden空间分配
新声代对象大多数应该在eden区分配,当eden区没有足够空间分配时,进行一次Minor GC。
大对象直接进入老年代
大对象是只需要大量连续内存空间的JAVA对象,例如很长的字符串或者元素数量很大的数组。
长期存活的对象进入老年代
对象头header会有Age计数器,每个对象在经历一次Minor GC后,存在与Survivor空间内的话,那么它的age会+1,当年龄大于一定岁数的时候(默认15岁),就会晋升到老年代。
动态对象年龄判定
如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或者等于该年龄的对象直接晋升老年代。
空间分配担保
在发生Minor GC之前,虚拟机应该检查老年代的最大可用连续空间是否大于新声代所有对象的空间总和,如果这个条件成立,那么本次MinorGC是安全的;如果不成立,虚拟机会看是否允许担保失败(虚拟机配置 -XX:HandlePromotionFailer),如果允许担保失败,那会继续检查老年代最大可用连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试Minor GC,但是有风险;如果小于或者不允许担保失败,那么进行Full GC。