1.介绍垃圾收集器
垃圾收集器(Garbage Collection,GC)就是用于回收方法区和堆区,其他程序计数器、虚拟机栈、本地方法栈这3个区域都是随线程而生,随线程而灭,栈中的栈帧会随着方法的进入和退出而有条不紊地执行着出栈和入栈操作。每一个栈帧中分配多少内存在类结构确定下来时就是已知的,因此这几个区域的内存分配和回收都是具备确定性,所以这3个区域不需要考虑回收,因为方法结束或线程结束时,内存自然就跟着回收了。而Java堆和方法区就不同,一个接口中的多个实现类需要的内存可能不一样,一个方法中的多个分支需要的内存也可能不一样,我们只有在程序出于运行期间才能知道会创建那些对象,这部分内存分配和回收都是动态的,所以这是垃圾收集器关注的内存。
1.1如何判定对象是否存活?
引用计数算法
给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器为0 的对象就是不能在被使用的。这种算法的优点在于判定效率高,实现简单,但Java 虚拟机没有选用引用计数算法来管理内存,原因是它很难解决相互循环引用的问题。
下面的代码可以看出两个对象互相引用对方的属性所以不会被GC
public class ReferenceCountingGc { public Object instance = null; private int _1MB = 1024*1024; private byte[] bigSize = new byte[2 * _1MB]; public static void testGC() { ReferenceCountingGc objA = new ReferenceCountingGc(); ReferenceCountingGc objB = new ReferenceCountingGc(); objA.instance = objB; objB.instance = objA; objA = null; objB = null; System.gc(); } public static void main(String[] args) { ReferenceCountingGc.testGC(); } }
可达性分析算法
主流商用程序语言(JAVA 、C#)的主流实现都是通过可达性分析来判定对象是否存活。基本思路就是通过一些列的成为“GC Roots”的对象作为起始点,从这些结点开始向下搜索,搜索所走过的路径成为引用链(Reference Chain),当一个对象到 GC Roots 没有任何引用链相连时,则证明对象是不可用的。
在 Java 语言中,可作为GC Roots的对象包括下面几种:
- 虚拟机栈(栈帧中的本地变量表)中引用的对象
- 方法区中静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈中 JNI(Native方法) 引用的对象
2.对象的引用
无论是引用计数器判断对象的引用数量,还是可达性分析算法判断引用链是否可达,判定对象是否存活都与“引用”有关。JDK1.2 以前,Java 中引用的定义为 如果 reference类型的数据中存储的数值代表的是另外一块内存的起始地址,就称这块内存代表这一个引用。一个对象只有引用或者未被引用两种状态,总感觉缺少点什么,我们希望能描述这样一类对象:当内存还足够时,则能保留内存之中;如果内存空间在进行GC后还非常紧张,则可以抛弃这些对象。
所以在JDK1.2 之后,Java 对引用概念进行扩充,将引用分为强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)、虚引用(Phantom Reference)。
- 强引用:类似“Object obj = new Object()”只要强引用还存在,垃圾收集器永远不会回收掉被应用的对象
- 软引用:用来描述一些还有用但并非必须的对象。对于软引用关联的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行第二次回收。如果这次回收还没有足够的内存,才回抛出内存溢出异常->JDK1.2后,提供了SoftReference类来实现软引用
- 弱引用:用来描述非必须对象的,强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集器发生之前。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象->JDK1.2后,提供了WeakReference类来实现弱引用
- 虚引用:称为幽灵引用或幻影引用,它是最弱的一种引用关系,一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。一个对象关联虚拟引用唯一目地就是能在这个对象被收集器回收时收到一个通知->PhantomReference来实现虚引用
用法如下
public class Test { User u = new User(); SoftReference s = new SoftReference(u); WeakReference w = new WeakReference(u); PhantomReference p = new PhantomReference(u, null); }
2.1对象的缓刑
并不是在可达性分析算法中被标记一次就会马上被回收,而是至少要经历两次标记过程:如果对象在进行可达性分析后没有与GC Roots 相连接的引用链,则会被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行的 finalize()方法。当对象没有覆盖 finalize()方法,或 finalize()方法已经被虚拟机调用过,虚拟机将这两种情况都视为“没有必要执行”。
如果这个对象被判定没必要执行 finalize()方法,这个对象将会放置在F-Queue的队列中,并稍后由虚拟机自动建立的、低优先级的 Finalizer 线程去执行它。这里“执行”是虚拟机会触发这个方法,并不承诺会等待它运行结束,如果在对象的finalize()方法中执行缓慢,或者发生死循环,会导致F-Queue 队列其他对象永久处于等待,会导致内存回收系统崩溃。finalize()方法是对象逃脱回收的最后一次机会,稍后GC 将对 F-Queue中的对象进行第二次小规模标记,如果对象在finalize()中重新与引用链上任何一个对象建立关联即可,如把自己(this)赋值给某个类变量或者对象的成员变量,第二次标记时就会将它移除“即将回收”的集合;如果对象此时还没逃脱基本就被回收了。
注意:finalize()方法只会被系统自动调用一次
2.1回收方法区
垃圾收集器主要回收两部分内容:废弃常量和无用的类废弃常量如字符串“abc”已经进入常量池中,但没有任何一个String对象引用它,也没有其他地方引用这个字面量,如果发生内存回收,而且有必要的话,这个“abc”常量就会被系统清理出常量池
无用的类需要同时满足已下3个条件才算。
- 该类所有的实例都已经被回收,也就是Java堆中不存在该类的任何实例
- 加载该类的ClassLoader已经被回收
- 该类java.lang.Class 对象没有任何地被引用,无法在任何地方通过反射访问该类的方法