如果一个对象再也不会被用到,就可以回收它了,所以关键在于如何知道一个对象再也不被使用了。
当一个对象被引用时,引用计数加1,当引用失效时,计数减1。简单直观,但会出现循环引用问题。
a.tb = b
b.ta = a
即使 a
和 b
再也不会被用到了,但他们之间互相引用,导致引用计数一直不为0,无法被回收。
Java, C# 的主流实现都是使用可达性分析来判断一个对象是否存活的。如果从 GC Root
可以到达一个对象,那么 这个对象是可达的,还存活着,否则就不存活,可以被回收。 GC
Root
包含以下几类。
何时及如何回收,涉及具体的回收策略,就放一起说了。
最基本的垃圾回收算法,第一次扫描,标记所有可以被回收的对象,第二次扫描,回收被标记的对象。
不足之处
复制算法将内存分成两部分,每次只使用两部分。假如当前使用的是左半块,右半块没使用。现在要垃圾回收了, 就将左半块还存活的对象复制到右半块。然后将左半块一次性全部回收。
好处是
坏处是
解决方法是,调整左右两块的比例。在 HotSpot 中,内存分成一块 Eden,两块 Survivor,比例是 8:1
. 每次使用一块 Eden, 一块 Survivor A, 另一块 Survivor B 备用。 垃圾回收时,将 Eden 和 Survivor
A 上存活的对象复制到 Survivor B 上,然后将 Eden 和 Survivor A 回收。然后下次使用 Eden, Survivor B, 而 Survivor A 这次备用。
这种方法的前提是每次回收时,大量对象都死掉了,只有一小部分存活着,这样,只复制一小部分就好。但是,如果大量对象存活时间比较长,就要反复来回复制,简直浪费生命。从这一点也可以看出,要根据对象存活时间的特点使用不同的回收策略。
标记整理算法是对标记清除算法的改进。第一次扫描标记需要回收的对象。第二次不是将这些对象清除,而是将还存活的对象移动到内存区域的一端,全他们连在一起。然后对这块区域边界以外的地方全部回收。
从上面的讨论可以看出,要根据对象存活时间使用不同回收策略。有些对象存活时间比较短,这样每次回收时,存活对象少,可以使用复制算法。而有些对象存活时间比较长,这样每次回收时,需要回收的对象少,可以使用标记清除和标记整理算法。Java 堆据此分为新生代和老年代,新生代中对象存活时间短,老年代中对象存活时间长。
分代的标准有两个,一个是存活时间,一个是对象大小,大对象会直接进入老年代。
HotSpot 垃圾回收器按分代,可以分为新生代回收器和老年代回收器。按垃圾回收器线程数可以分为单线程回收器和多线程回收器。按垃圾回收时是否需要停止所有工作线程可以分为并发回收器和非并发回收器。所以,看到一个回收器,需要明白它:
其它回收器目标在于减少用户程序因为垃圾回收而停顿的时间,而Parallel Scavenge 回收器 的目标在于可按的吞吐量,因为它又被称为吞吐量优先回收器。 这里有一个矛盾,如果每次停顿时间减少,那么用户程序可以得到更快的响应,但这同 时意味着,垃圾回收变得频繁,垃圾回收总体时间变长,吞吐量下降。可以使用 -XX:MaxGCPauseMillis
调整垃圾回收停顿时间,也可以使用 -XX:GCTimeRation
调整吞吐量。
运行过程:
其中并发标记和重新标记需要暂停所有用户线程,但这两个阶段用时相对另外两个阶段非常少。而并发标记与并发清除消耗时间长,但他们可以与 用户线程一起工作。
初始标记只标记 GC Root 直接关联的对象,重新标记修正并发标记期间因用户程序运行导致标记变动的对象。
这篇文章是学习《深入理解Java虚拟机》的总结。
原文地址:http://blog.csdn.net/on_1y/article/details/38900597