程序计数器、虚拟机栈、本地方法栈这三个区域属于线程私有,只存在于线程的生命周期内,线程结束之后也会消失,因此,不需要对这三个区域进行垃圾回收。垃圾回收主要针对方法区和Java堆进行。
一、判断一个对象是否存活
1、引用计数算法
给对象添加一个引用计数器,当对象增加一个引用时计数器加1,引用失效时计数器减1。引用计数器不为0的对象仍然存活。
两个对象出项循环引用的时候,此时引用计数器永远不为0,导致无法对它们进行垃圾回收:
public class ReferenceCountingGC {
public Object instance = null;
public static void main(String[] args) {
ReferenceCountingGC objectA = new ReferenceCountingGC();
ReferenceCountingGC objectB = new ReferenceCountingGC();
objectA.instance = objectB;
objectB.instance = objectA;
}
}
正因为循环引用的存在,因此Java虚拟机不使用引用计数算法。
2、可达性分析算法:
通过GC Roots作为起始点进行搜索,能够到达的对象都是存活的,不可达的对象就是失效的,可以被回收。
Java虚拟机使用该算法来判断对象是否可以被回收,在Java中GC Roots 一般包括以下内容:
- 虚拟机栈中引用的对象
- 本地方法栈中引用的对象
- 方法区中类静态属性引用的对象
- 方法区中常量引用的对象
二、引用类型
无论是通过引用计数算法判断对象的引用数量,还是通过可达性分析算法判断对象是否可达,
判定对象是否可被回收都于引用有关。
Java中具有四种强度不同的引用类型:
(一)强引用
被强引用关联的对象不会被垃圾收集器回收。
使用 new 一个新对象的方式来创建强引用。
Object obj = new Object();
(二)软引用
被软引用关联的对象,只有在内存不够的情况下才会被回收。
使用SoftReference类来创建软引用。
// 获取对象并缓存
Object object = new Object();
SoftReference softRef = new SoftReference(object);
// 从软引用中获取对象
Object object = (Object) softRef.get();
if (object == null){
// 当软引用被回收后重新获取对象
object = new Object();
}
(三)弱引用
被弱引用关联的对象一定会被垃圾收集器回收,也就是说它只能存活到下一次垃圾收集发生之前。
使用WeakReference类来实现弱引用:
Object obj = new Object();
WeakReference<Object> wf = new WeakReference<Object>(obj);
obj = null;
(四)虚引用
又称为幽灵引用或者幻影引用。一个对象是否又虚引用的存在,完全不会对其生存时间构成影响,
也无法通过虚引用取得一个对象实例。
为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。
使用PhantomReference来实现虚引用:
Object obj = new Object();
PhantomReference<Object> pf = new PhantomReference<Object>(obj);
obj = null;
三、方法区的回收
因为方法区主要存在主要存放永久代对象,而永久代对象的回收率比新生代差很多,因此在方法区上进行回收性价比不高。
方法区的回收主要是常量池的回收和对类的卸载。
类的卸载条件很多,需要满足一下3个条件,并且满足了也不一定会被卸载:
- 该类的所用实例都已经被回收,也就是Java堆中不存在该类的任何实例。
- 加载该类的ClassLoader已经被回收。
- 该类对应java.lang.Class对象没有在任何地方被引用,也就无法在任何地方通过反射访问该类方法。
可以通过-Xnoclassgc 参数来控制是否对类进行卸载。
在大量使用反射、动态代理、cglib等ByteCode框架、动态生成JSP以及OSGi这类频繁自定义ClassLoader的场景都需要虚拟机举办卸载功能,以保证不会出现内存泄漏。
四、finalize()
finalize()类似于C++的析构,用来做关闭外部资源等工作。但try-finally等方法可以坐的更好,并且该方法运行代价高昂,不确定性大,无法保证各个对象的调用顺序,因此最好不用使用。
当一个对象可以被回收时,如果需要执行该对象的finalize()方法,那么就有可能通过在方法中让对象重新被引用,从而实现自救。自救只能进行一次,如果回收对象之前调用了finalize()方法自救,后面回收时不会调用finalize()方法。