垃圾收集与内存回收策略
判定一个对象是否是垃圾
引用计数法
给对象添加一个引用计数器,每当有一个地方去引用他,引用计数器 的值+1,引用失效时,引用计数器的值-1,引用计数器的值为0时,代表该对象不可能再被使用,应该被回收。
引用计数法没有解决对象循环引用的问题,导致JVM无法回收,造成内存泄漏。
可达性分析算法
GC Root的对象作为起点,从这些节点向下搜索,搜索走过的路径成为引用链,当一个对象没有任何引用链相连时代表对象不可用。
GC Root的对象
- 虚拟机栈(栈帧中的本地变量表)中引用的对象
- 方法区中静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈中JNI(Native方法)引用的对象
引用
- 强引用在程序代码中普遍存在的,类似Object obj = new Object(); 只要对象存在这样的引用垃圾回收器就永远不会回收该对象。
- 软引用在要发生内存溢出异常之前,会将这些对象进行回收。
Object obj = new Object();
SoftReference<Object> objectSoftReference = new SoftReference<>(obj);
obj=null;
- 弱引用比弱引用更弱,被弱引用关联的对象只能生存到下一次垃圾收集之前。
Object obj = new Object();
WeakReference<Object> objectWeakReference = new WeakReference<>(obj);
obj=null;
- 虚引用最弱的引用关系,一个对象是否有虚引用的存在完全不会对生存时间造成影响,也无法通过虚引用的get方法获取对象的实例。为对象设置虚引用的目的是为了在回收这个对象时会收到一个系统通知。
对象的自救
如果对象在经过可达性分析后没有与GC Root相关联,进行第一次标记,并进行一次筛选,如果该对象的finalized方法被覆盖并且finalized方法从未被执行,判定对象有必要执行finalized方法,该对象会被放到一个叫F-Queue队列中,并且JVM会开启一个低优先级的finalized线程去执行该队列中对象的finalized方法,如果对象中finalized执行后,该对象重新被引用上了,该对象自救成功,JVM不会对该对象进行回收,但是finalized线程是一个优先级比较低的线程,所以可能有一些对象的finalized方法还没有执行就被GC了。
对象zi救Demo
public class FinalizeEscapeGC {
public static FinalizeEscapeGC SAVE_HOOK=null;
public void isAlive(){
System.out.println("yes i am still alive:~_~");
}
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("finalize method execute!");
FinalizeEscapeGC.SAVE_HOOK=this;
}
public static void main(String[] args) throws InterruptedException {
SAVE_HOOK =new FinalizeEscapeGC();
//first save self
SAVE_HOOK = null;
System.gc();
//Finalizer线程优先级较低,等待一会,保证足够的时间去执行
Thread.sleep(500);
if(SAVE_HOOK!=null){
SAVE_HOOK.isAlive();
}else {
System.out.println("no i am dead =_=");
}
//second save self
SAVE_HOOK = null;
System.gc();
//Finalizer线程优先级较低,等待一会,保证足够的时间去执行
Thread.sleep(500);
if(SAVE_HOOK!=null){
SAVE_HOOK.isAlive();
}else {
System.out.println("no i am dead:=_=");
}
}
}
运行结果
finalize method execute!
yes i am still alive:~_~
no i am dead:=_=
可以看出对象只能通过finalized方法自救一次,因为finalized方法JVM只会自动调用一次。
回收方法区
方法区又名永久代,意为很少有资源被回收。主要存放编译后的代码,类信息,常量,静态变量等。
JVM主要回收方法区中废弃的常量和无用的类(字节码文件)
- 废弃的常量是指没有任何对象对该常量进行引用。
- 无用的类,全部满足一下条件才可以被回收
1.该类的所有实例都已经被回收,java堆中不存在任何该类的任何实例。
2.加载该类的类加载器已经被回收
3.该类对应堆中的java.lang.Class对象没有任何地方被引用,无法在任何地方通过反射访问该类的方法。
垃圾收集算法
标记-清除
存在的问题:
1.标记和清除的效率都不高。
2.标记清除之后,会出现大量不连续的内存碎片,可能导致大对象无法分配。进而提前出发GC。
复制算法
新生代和老年代的大小一般不会相等,堆内存被分为两个Eden和一个Survivor,默认比例为1:1:8
标记-整理
和标记-清除算法类似,但是后续步骤不是直接对对象进行清除,而是将所有存活的对象都移动到一端,然后直接清理端边界以外的内存。
解决了标记-清除对象回收后内存空间不连续的问题,但是增加了对象的移动,效率会略有降低。
分代收集
根据对象的生命周期不同,将堆内存分为新生代和老年代,(新生代的对象大多朝生夕死,老年代的对象存活时间较长)使用不同的垃圾回收算法,新生代使用复制算法,老年代使用标记-清除 或者 标记-整理。