在进行垃圾收集的时候,需要考虑三件事情
1.哪些内容需要gc
2.什么时候需要被回收
3.回收方法
我们通过这三点来讨论垃圾回收策略
哪些内容需要收集
垃圾收集,主要是堆区域。
对于堆当中的对象,简单来说,当对象被判定为死亡之后,就会进行垃圾收集。那么如何判定对象死亡呢?
对于一般对象来说,通过两种方法判断对象是否死亡。分别是 引用计数法和可达性分析算法。
引用计数方法基本思路就是在对象被引用之后计数加一。当对象引用被取消之后计数减一。 但是这种算法没有办法解决循环引用问题。
可达性分析算法基本思路就是通过一系列可以作为GCroot的节点出发,寻找引用链,当寻找完毕之后,其他未能被引用的对象就会被认为是需要被垃圾回收的对象。与之而来的,是哪些节点可以GCROOT节点:
1.所有Java线程当前活跃的栈帧里指向GC堆里的对象的引用;换句话说,当前所有正在被调用的方法的引用类型的参数/局部变量/临时值。
2.VM的一些静态数据结构里指向GC堆里的对象的引用,例如说HotSpot,VM里的Universe里有很多这样的引用。
JNI handles,包括global handles和localhandles(看情况)
3.所有当前被加载的Java类(看情况)
4.Java类的引用类型静态变量(看情况)
5.Java类的运行时常量池里的引用类型常量(String或Class类型)(看情况)
6.String常量池(StringTable)里的引用
此处需要注意,对于对象的成员变量不可以作为被引用对象
除此之外,java其实还有强引用,软引用,弱引用,虚引用之分。
对于我们一般创建的对象就是强引用。
对于强引用,当强引用的对象还存活的时候,垃圾收集器是永远不会回收这些活着的强引用对象的。
对于软引用,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行第二次回收,如果对象回收之后还是没有内存,则报内存溢出的错误。
对于弱引用,他的强度与软引用更弱了一些。被弱引用关联的对象只能生存至下一次垃圾回收之前。当进行GC时候,不管内存溢不溢出,都会被回收。
对于虚引用,它是一种最弱的引用关系。无法通过引用来获取对象,他的存在对内存不造成任何影响。设置着一个引用的目的是为了能在这个对象被收集器回收的时候收到一个系统通知。
另外,还涉及到一个区域-方法区也会有需要进行垃圾收集,主要回收内容是废弃常量和无用的类
对于废弃常量的判定比较简单,当这个常量没有被引用的时候,就会被认为废弃
对于无用类判定比较严苛:
该类所有的实例都已经被回收,也就是java堆中不存在该类的任何实例。
加载该类的ClassLoader已经被回收
该类对应的java.lang.Class对象没有在仍和地方被引用,无法再任何地方通过反射访问到该类的方法。
在知道了哪些对象需要被收集的时候,那么接下来一个问题,对象何时被收集
对象何时被收集
(1)程序调用System.gc时可以触发(会建议JVM进行垃圾回收,不代表一定会进行GC);
(2)系统自身来决定GC触发的时机。 (当内存区域不够用的时候)
收集方法
标记清除算法
流程:
1.标记出所有需要回收的对象
2.清除所有标记的对象
缺点:
标记和清除的效率都不高
标记清除后产生大量的内存碎片,如果碎片太多,就会导致如果需要分配大对象的时候,找不到一块完整的空间,而尽早的触发GC。
标记整理算法
如果在对象存活率较高时就要进行较多的复制操作,效率将会变低。更关键的是,如果不想浪费50%的空间,就需要有额外的空间进行分配担保,以应对被使用的内存中所有对象都100%存活的极端情况,所以在老年代一般不能直接选中这种算法
和标记-清除算法一样,标记-压缩算法也首先需要从根节点开始,对所有可达对象做一次标记;但之后,它并不简单的清理未标记的对象,而是将所有的存活对象压缩到内存的一端;之后,清理边界外所有的空间。
缺点: 效率不高
复制算法
流程:
将原有的内存空间分为两块,每次只使用其中一块,在垃圾回收时,将正在使用的内存中的存活对象复制到未使用的内存块中,之后,清除正在使用的内存块中的所有对象,交换两个内存的角色,完成垃圾回收。
优点:可用区域连续,因为是连续内存,清理起来比较快
缺点: 空间浪费大,所以需要对于可用区域做权衡,不适合存活对象较多的情况(比较适合新生代对象收集)
分代收集算法
少量对象存活,适合复制算法:在新生代中,每次GC时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成GC。
大量对象存活,适合用标记-清理/标记-整理:在老年代中,因为对象存活率高、没有额外空间对他进行分配担保,就必须使用“标记-清理”/“标记-整理”算法进行GC。