判断对象是否存活
引用计数算法
很多判断对象是否存活的方式就是引用计数算法:给对象添加一个引用计数器,每当一个地方引用它,计数器+1;引用失效,计数器-1。
任何时刻计数器为0的对象是不可能再被使用的。
Java虚拟机没有使用主流的引用计数法管理内存。主要原因是难以解决对象之间相互循环引用的问题
classA.instance = classB
classB.instance = classA
classA和classB已经不可能再被访问,但是classA和classB互相引用对方,导致计数器永远不可能是0,导致GC无法回收两个对象
可达性分析算法
通过一系列称为”GC Roots”对象作为起始点,从这些节点向下搜索,搜索过的路径称为引用链。
当一个对象的GCRoots没有任何引用链相连时,则证明该对象不可用。
在java中可作为GCRoots对象的是
- 虚拟机栈(栈帧本地变量表)引用的对象
- 方法区中类属性引用对象
- 方法区中常量引用对象
- 本地方法栈(JNI)中引用对象
引用
Java引用分类(又上到下引用关系依次减弱)
- 强引用
- 必须存在的对象
- 类似ObjectA objA = new ObjectA()
- 软引用
- 有用但非必须的对象
- 在发生内存溢出后,可进行二次回收
- 弱引用
- 非必须对象
- 无论当前内存是否足够,都会回收掉只被弱关联对象
- 虚引用
- 是否存在不会对生存时间对其影响
生存或者死亡
判断一个对象死亡,至少经历两次标志过程:如果对象进行可达性分析,GCRoot没有相连的引用链,那么被第一次标记进行筛选;筛选的条件是是否有必要进行finalize()方法;对象没有覆盖finalize()方法或者已经被虚拟机调用过finalize()方法,则该对象没必要销毁;如果这个对象有必要执行finalize()方法,会将它放置在F-Queue队列中,并在稍后由一个虚拟机自动执行、低优先级的Finalizer线程去执行;GC稍后会对F-Queue中的对象进行二次标记
如果对象需要拯救自己不被回收。只要重新与引用链的任何一个对象重新关联为止(例如.this)
注意:自救的机会只能使用一次;一个对象的finalize方法最多只被系统调用一次
private static FinallyProperty escapeGC = null;
@Override
void finalize() throws Throwable{
super.finalize();
FinallyProperty.escapeGC = this;
}
回收方法区
永久代的垃圾回收主要是2个部分:废弃变量和无用的类
- 废弃变量:与Java堆中的回收类似;例如.没有任何一个地方引用常量池的”abc”字面量,这时发生了内存回收;这个”abc”会被清理出常量池
- 无用的类需要满足的3个条件
- java堆中不存在该类的任何实例
- 加载该类的ClassLoader已被回收
- 无法在任何地方通过反射访问该类的方法
垃圾收集算法
标记-清除
在回收之前,首先标志出需要回收的对象,在标志完成后统一回收所有标志对象
后面的算法都是基于”标记-清除”算法的改进
该算法的主要缺点
1. 效率低下:产生过多内存碎片
2. 空间占用过多
复制算法
解决”标记-清除”算法效率问题
将内存划分为容量相等的2个部分,每次只使用其中一块。当这块内存使用完毕,将还存活的对象复制在另一块上面。然后把已使用的空间一次性清理掉。
每次对整个半区进行回收,而不讨论内存碎片等复杂情况,只需移动堆顶指针、按顺序分配内存即可。
标志-整理算法
复制收集算法在对象存活率过高时进行复制操作,影响效率。
根据老年代特点,提出了标志-整理算法;标志过程和标志-清除的过程一样。整理:让所有存活对象向一端移动,然后直接清理掉边界以外的内存
分代收集算法
一般把Java划分成新生代和老年代;根据各个年代的特点采用合适的收集算法
通常新生代采取:复制算法
老年代采取:标志-整理或者标志-清除算法