垃圾收集设计到算法和源码,这里只在概念上进行总结:
根据上一篇文章的内容,我们知道方法区和堆是垃圾收集的对象。这其中,方法区是类信息,堆是对象数据。对堆的收集更为频繁和有效,而方法区中的垃圾收集条件严格,收集的成功率也不高。但是方法区中的类信息,由于现在的J2EE大量使用反射,自定义ClassLoader等,使得方法区的信息频繁的加载,所以卸载也是必须的!
垃圾收集的思路
- 老的思路是计数器,在创建一个对象时为这个对象计数1,引用这个对象时计数器+1,当计数器为0时该对象即可被垃圾收集。但是存在的问题是两个对象相互引用,像死锁一样永远无法收集。
- 目前的垃圾收集都采用跟踪搜索思路。即从根开始遍历所有对象,若处在树中那就是被引用,否则的话都可以垃圾回收。
垃圾回收的基础算法
- 标记-清除,采用搜索跟踪,所有被引用的对象被标记,搜索完成后,未被标记的对象即被清除。缺点:清除后可能产生堆碎片。
- 复制,复制不进行标记,而是搜索跟踪后把被引用的对象复制到一块新的内存堆中。原内存堆即整体释放。这样不会产生碎片。缺点:需要一块新空间来完成复制。
- 标记-整理,即在标记-清除后,主动的移动对象以保证空间的连续性。
垃圾回收的实际应用思路–结合基础算法加上一定的策略用于实际中
分代:JVM会将对象分成不同的代并存放在特定的内存区域。在垃圾收集时,可以对这些区域应用不同的收集频率和收集算法。目标就是要提高性能。
1.1所有新生成的对象,都属于young generation(新生代,年轻代)。但是,这些对象有的很容易消亡,如程序中的string对象;有的却需要存活很长时间,如用户session。
1.2如果一个新生代对象在经过数次垃圾回收后仍存在,就进入年老代,tenured generation。具体如何进入,有一定的算法。有兴趣可以去搜索。
1.3通常会给年轻代的内存区设立一个尺寸大小,但是年老代无法设立尺寸大小,因为对象不被垃圾收集的话只能不断的涌入年老代。渐进收集:或者说是部分收集。垃圾收集大部分使用的是stop the world即暂停所有线程来执行垃圾收集。那么如果暂停的时间太长,势必影响到程序实时性。所以,需要在限定的时间内收集限定的区域并切换回程序线程,保证实时性用户体验。
2.1因为年轻代的区域尺寸固定,《深入JAVA虚拟机》给出的说法是“收集器可以大体上保证在一个最大时间值内【渐进】的收集所有对象”。(意思就是不复杂不需要特别的设计?)
2.2由于年老代区域可能会很大,所以需要使用火车算法来给出固定的长度,每次只收集固定部分的垃圾。
2.3注意这个固定时间并不是严格保证的,因为万一一块区域内的垃圾很多,也还是要等收集工作做完才会切换回程序线程。
构造专门的对象配合垃圾收集
我们明白了垃圾收集的机制后,有什么用呢?一个典型的应用就是缓存,我们在一块内存空间中缓存一些对象,就像一个池子一样。 这个缓存池里什么数据应该存在什么数据应该清理呢?这就需要垃圾收集能以我们预期的方式去运作。Java构造了专门的对象来帮助我们实现这种运作。
强引用,在程序编写时最常见的引用。一个对象直接的持有另一个对象就是强引用,强引用不能被垃圾收集。
**问题来了,缓存中的对象如果被强引用了,不能被收集,一些个使用频率特别少的对象,却始终占据缓存空间。那么缓存就不划算了。
软引用。SoftReference sr = new SoftReference(Object)。缓存可以把刚才提到的那些对象设置为软引用。垃圾回收器可以自行决定何时回收这些对象,在缓存空间OOM时,则必须回收这些对象。
**注意,回收是指把Object变为不可触及,进而回收Object。回收后sr.get()就会得到null。弱引用。对于一些优先级更低的对象,可以使用弱引用。弱引用指向的Object会在垃圾回收发现时直接清除。 WeakHashMap就是一个结合弱引用机制的设计。
虚引用,幽灵引用。PhantomReference。虚引用的目的和软、弱引用不同,不是为了告诉GC这个引用的Object可以被删除。一个对象被虚引用了,就和不存在这种引用一样,GC对待这个对象和对待普通对象没区别。
唯一的区别就是,虚引用的目的是必须要实现这个引用的clear()方法,而GC在回收对象时必须调用clear()方法,所以程序员可以利用这点做一些深度的清理工作。
ps:尽量不要使用finalize方法,原因是因为,垃圾收集器在任何情况下,都不能保证finalize会马上执行,甚至会执行,而在finalize中有可能会被逃逸。finalize方法本身也存在严重的性能问题。