垃圾收集主要是完成3件事:什么内存需要回收,什么时候回收;通过什么方法回收。
1、什么内存需要回收:
通过判断对象是否还是“存活”,主流的JAVA虚拟机主要是通过“可达性分析”,来判断。
什么是可达性分析呢,大概是意思就是首先设置一系列的CGROOT(一般是虚拟机栈中引用的对象;方法区中的静态/常量引用对象;本地方法栈引用的对象),通过这些节点为起点,是否是有引用关系的相应的节点,那些能到达的节点判断为可到达,反之亦然。
2、什么时候回收:
要真正的宣告一个对象死亡,至少要经历两次标记的过程。若在“可达性分析”中不可到达的对象,将会被第一次标记并且进行一次筛选,当没有覆盖finalize()方法或者是已经被虚拟机调用过覆盖finalize()方法,就表示为真正可以回收了。当覆盖了finalize()方法并且没有被虚拟机调用就机会继续存活(在finalize()方法中重新也引用链建立关系,但执行的过程中不会承诺等待其运行结束,因为finalize()方法缓慢或者是发生了死循环或者其他更严重的问题,就可能会导致很严重的问题)。
方法区回收,虽然虚拟机规范中表示可以不要求表情虚拟机在方法区中实现垃圾收集,并且效率也比较低,但是要保证方法区不会溢出,也是很有必要的。一般是从:废弃常量和无用类这两个方面垃圾收集。
判定一个常量比较简单,如果没有其他的地方引用这个常量,如果这个时候发生了内存回收,并且有必要的话,这个常量就会被回收。
判定一个“无用类”需要同时满足如下三个条件才会被回收:
1)该类的所以实例全部被回收了。
2)加载这个类的ClassLoader也被回收了。
3)该类对应的java.lang.Class对象在任何地方多没有被引用,任何地方都无法通过反射访问到该类的方法。
3、通过什么方法回收:
虚拟机的垃圾收集算法涉及到大量的细节,并且各个平台的虚拟机又各不相同,但主要还是遵从如下的算法:
标记-清除算法
首先标记所有需要回收的对象,然后一次进行回收。这种算法是最基础的算法,很多算法都是以这种算法改善得到的。但这种算法存在效率问题(两个过程的效率不高)和空间问题(大量不连续碎片)
复制算法
内存划分两块空间(IBM划分三块8:1:1),每次只使用其中的一块,每次中使用其中的一块(IBM使用8:1),然后在回收的过程会把还存活的对象复制到剩下的那一块,最后清除之前使用那一块。
标记-整理算法
标记和“标记-清除算法”的标记一致,首先标记所有需要回收的对象,然后让所有存活的对象向一边移动,最后清除段边界以外的内存。
在当代的商业虚拟机中一般是采用“分代收集”。新生代采用复制算法,因为新生代中存在大批的对象死去,只有小量的存活,所以比较适合复制算法。老年代中对象存活率高,所以可以使用“标记-清除算法”或者“标记-清除算法”
4、回收的实现:
垃圾回收是通过垃圾收集器实现的,下图是各个收集器的关系图和简单介绍,而具体的介绍这里就不作详细的说明了,有兴趣的可以去查一下相关内容。