什么是垃圾回收器
Java垃圾回收器是Java虚拟机(JVM)的三个重要模块(另外两个是解释器和多线程机制)之一,为应用程序提供内存的自动分配(Memory Allocation)、自动回收(Garbage Collect)功能,这两个操作都发生在Java堆上(一段内存快)。某一个时点,一个对象如果有一个以上的引用(Rreference)指向它,那么该对象就为活着的(Live),否则死亡(Dead),视为垃圾,可被垃圾回收器回收再利用。垃圾回收操作需要消耗CPU、线程、时间等资源,所以容易理解的是垃圾回收操作不是实时的发生(对象死亡马上释放),当内存消耗完或者是达到某一个指标(Threshold,使用内存占总内存的比列,比如0.75)时,触发垃圾回收操作。有一个对象死亡的例外,java.lang.Thread类型的对象即使没有引用,只要线程还在运行,就不会被回收。
finalize()和System.gc()
Java的垃圾回收器负责回收无用对象占据的内存资源,但是垃圾回收器只知道释放那些经由new分配的内存所以他不知道如何释放"特殊"的内存,如:在分配内存时采用了类似C语言中的做法.为了应对这种情况,java在Object类中定义一个名为finalize()的方法,所有类都继承并可以重写自己的finalize()方法.其工作原理如下:
一旦垃圾回收器准备好释放对象占用的存储空间,将首先调用finalize()方法,并且在下一次垃圾回收动作发生时,才会真正回收对象的内存.
System.gc()是建议启动一个垃圾回收器,(如果内存足够获取其他情况下,并不一定会启动)在释放new分配内存空间之前,将会通过finalize()释放用其他方法分配的内存空间.
所以不应该将finalize()作为通用的清理方法.应牢记这一点:垃圾回收只与内存有关.
垃圾回收器机制
在《thinking in java》中,作者给垃圾回收器起了一个很长的称呼:“自适应的、分代的、停止-复制、标记-清扫”式垃圾回收器,这几个形容词概括了java垃圾回收器的几个重要特点,下面对这几个名词一一解释。
寻找"活着的对象":
1>引用计数
每个对象都含有一个引用计数器;
当有引用连接至对象的时候,引用计数加1;当引用离开了作用域或者被置为null的时候,引用计数减1;
垃圾回收器会在含有全部对象的列表上遍历,当发现某个对象的引用计数为0时,就释放其占用的空间.
这种方法有个缺陷,如果对象之间存在循环引用,如:
有对象 A 和对象 B,对象 A 中含有对象 B 的引用,对象 B 中含有对象 A 的引用。此时,对象 A 和对象 B 的引用计数器都不为 0。但是在系统中却不存在任何第 3 个对象引用了 A 或 B。也就是说,A 和 B 是应该被回收的垃圾对象,但由于垃圾对象间相互引用,从而使垃圾回收器无法识别,引起内存泄漏.
对垃圾回收器而言,定位这样的交互自引用的对象组所需的工作量极大。引用记数常用来说明垃圾收集的工作方式,但似乎从未被应用于任何一种Java虚拟机实现中。
2>追溯引用
它们依据的思想是:对任何“活”的对象,一定能最终追溯到其存活在堆栈或静态存储区之中的引用。这个引用链条可能会穿过数个对象层次。
由此,如果从堆栈和静态存储区开始,遍历所有的引用,就能找到所有“活”的对象。对于发现的每个引用,必须追踪它所引用的对象,然后是此对象包含的所有引用,如此反复进行,直到“根源于堆栈和静态存储区的引用”所形成的网络全部被访问为止。您所访问过的对象必须都是“活”的。注意,这就解决了“交互自引用的对象组”的问题——这种现象根本不会被发现,因此也就被自动回收了。
回收垃圾算法
1.标记-清扫
这种算法依据的思想就是:对任何“活”的对象,一定能追溯到它在堆栈或者静态存储区中的“引用”,因此,从静态存储区和堆栈开始,遍历所有引用,就可以找到所有活得对象(注意,这里由静态存储区和堆栈开始,追踪引用指向的对象,然后再解析对象中包含的引用,这样就可以找到所有活的对象)。
1)根据上面的思想,对所有活的对象进行“标记”
2)第二阶段遍历整个堆,把未标记的对象清除,所有未标记的对象都被作为垃圾回收并返回空闲列表。
2.停止-复制
如上图所示,标记-清扫算法会有一个很大的问题,就是会产生很多“堆碎片”,为解决这个问题,java垃圾回收器采用了“停止-复制”算法,这种算法的过程如下:
1)首先暂停程序的运行,
2)然后将所有存活的对象从当前堆复制到新堆,没有被复制的对象,全部都是“垃圾”。当被复制到新堆时,对象被重新紧密排列,这样就解决了堆碎片的问题
3)注意,这个算法不是在后台进行的,而是停止当前所有动作,所以叫做停止-复制算法
3.分代
所有的回收器类型都是基于分代技术。Java HotSpot虚拟机包含三代,年轻代(Young Generation)、年老代(Old Generation)、永久代(Permanent Generation)。
永久代
存储类、方法以及它们的描述信息。可以通过-XX:PermSize=64m和-XX:MaxPermSize=128m两个可选项指定初始大小和最大值。通常
我们不需要调节该参数,默认的永久代大小足够了,不过如果加载的类非常多,不够用了,调节最大值即可。
年老代
主要存储年轻代中经过多个回收周期仍然存活从而升级的对象,当然对于一些大的内存分配,可能也直接分配到永久代(一个极端的例子是年轻代根本就存不下)。
年轻代
绝大多数的内存分配回收动作都发生在年轻代。如下图所示,
年轻代被划分为三个区域,原始区(Eden)和两个小的存活区(Survivor),两个存活区按功能分为From和To。绝大多数的对象都在原始区分配,超过一个垃圾回收操作仍然存活的对象放到存活区。
在新生代中使用复制算法,即Minor-GC,当一些对象经过多次的Minor-GC后还留在新生代,则会被搬移到老年代中。而老年代中使用标记- 清理或标记-整理算法,即Major GC/Full GC。
4.自适应
java虚拟机会自动监视,如果所有对象都很稳定(存在的代数比较多,老年代),则会切换到标记-清理算法。如果堆空间出现很多碎片,则会自动切换回“停止-复制”方式。这就是自适应技术。