垃圾回收器的工作原理

来源:互联网 发布:网络优化工程师待遇 编辑:程序博客网 时间:2024/05/29 11:52

垃圾回收器对于提高对象的创建速度,效果明显,这意味着java从堆分配空间的速度可以和其他语言从堆栈上分配空间的速度媲美。

堆更像一个传送带,每分配一个新对象,向前移动一格。这意味着对象存储空间的分配速度非常快。java的”堆指针”只是简单的移动到尚未分配的区域,其效率比得上C++在堆栈上分配空间的效率。实际过程中在记录工作中还有少量开销,但比不上查找可用空间的开销大。

垃圾回收器工作时,一面回收空间,一面将堆中的对象紧凑排列,这样”堆指针”很容易移动到开始处,避免页面错误。通过垃圾回收器对对象重新排列,实现了一种高速的,有无限空间可供分配的堆模型。

未应用于Java中的垃圾回收的机制—-引用计数法。引用计数法是一种简单但是速度很慢的垃圾回收技术。每个对象都有一个引用计数器,当有引用连接到对象时,引用计数加一。当引用离开作用域时或被置为null时,引用计数减一。虽然管理引用计数的开销不大,但这项开销在整个程序生命周期将持续发生。垃圾回收器会在含有全部对象的列表上遍历,当发现某个对象的引用计数为0时,就释放其占用的空间(但是,引用计数模式经常会在计数值为0时立即释放对象)。这种方法有个缺陷,如果对象之间存在循环引用,可能会出现”对象应该被回收,但引用计数却不为0”的情况。对垃圾回收器而言,定位这样的交互自引用的对象组所需的工作量极大。引用计数常用来说明垃圾收集的工作方式,但似乎从未被应用于任何一种Java虚拟机的实现中。

一些更快的模式中,垃圾回收器并非基于引用回收技术。他们依据的思想是:对于任何一个活着的对象,一定能最终追溯到存活在堆栈或静态区的引用,该引用链条可能会穿过数个对象层次。所以,如果从堆栈和静态区开始,遍历所有的引用,就能找到所有活的对象。对于发现的每个引用,必须追踪他的引用对象,然后是此对象包含的所有引用,如此反复进行,直到”根源于堆栈和静态存储区的引用”所形成的网络全部被访问为止。你所访问过的对象必须都是活的。注意,这就解决了”交互自引用的对象组”的问题—-这种现象根本不会被发现,因此也就被自动回收了。

在这种方式下,Java虚拟机采用一种”自适应”的垃圾回收技术。至于如何处理找到的存货对象,取决于不同的Java虚拟机实现。有一种做法名为”停止-复制(stop-and-copy)”。显然这意味着,先暂停程序的运行(所以他不属于后台回收模式),然后将所有存活的对象从当前堆复制到另一个堆,没有被复制的全部都是垃圾。当对象被复制到新堆时,他们是一个挨着一个的,所以新堆保持紧凑排列,然后就可以按前述方法简单,直接地分配新空间。

当把对象从一处搬到另一处时,所有指向他的引用都必须修正。位于堆栈或静态存储区的引用可以直接被找到。对于这种复制式回收器而言,效率会降低,这有两个原因。首先,得有两个堆,然后得在这两个分离的堆之间来回倒腾,从而得维护比实际多一倍的空间。某些java虚拟机堆此问题的处理方式是,按需从堆中分配几块较大的内存,复制动作发生在这些大块内存之间。

第二个问题在于复制。程序进入稳定状态之后,可能只会产生少量垃圾,甚至没有垃圾。尽管如此,复制式回收期仍然会将所有内存自一处复制到另一处,这很浪费。为了避免这种情形,一些Java虚拟机会进行检查:要是诶呦新垃圾产生,就会转换到另一种工作模式(“即自适应”)。这种模式是被称为标记-清扫(mark-and-sweep),Sun公司早期版本的java虚拟机使用了该技术。对一般用途来讲,该方式速度相当慢,但是当你知道只会产生少量垃圾甚至不会产生垃圾时,他的速度就很快了。

“标记-清扫”所依据的思路同样是从堆栈和静态区存储区出发,遍历所有的引用,进而找出所有存活的对象。每当它找到一个存活对象,就会给对象设一个标记,这个过程中不会回收任何对象。只有全部标记工作完成的时候,清理工作才会开始。在清理过程中,没有标记的对象将被释放,不会发生任何复制行为。所以剩下的堆是不连续的,垃圾回收器要是希望得到连续空间的话,就得重新整理剩下的对象。

“停止-复制”的意思是这种垃圾回收动作不是在后台进行的;相反垃圾回收动作发生的同时,程序将会被暂停。当可用内存数量较低时,Sun公司的垃圾回收器会暂停运行程序,同样,”标记-清扫”工作也必须在程序暂停的情况下才能进行。

“停止-复制”要求在释放就有对象之前,必须先把所有存活对象从旧堆复制到新堆,这将导致对象大量内存复制行为。有了块以后,垃圾回收器再回首的时候就可以王菲器的块里拷贝对象了。每个块都用相应的袋鼠来记录它是否还存活。通常,如果块还在某处被引用,期待书会增加;垃圾回收器会定期进行完葬呢光额清理动作—-大型对象仍然不会被复制(只是其代数会增加),内涵小型对象的那些块责备复制并整理。java虚拟机会进行监视,如果所有对象都很稳定,垃圾回收器的效率降低的话,就切换到”标记-清扫”方式;同样,java虚拟机会跟踪”标记-清扫”工作。如果堆空间出现很多碎片,就会切换回”停止-复制”方式。这就是”自适应”技术。或者称其为”自适应的,分代的,停止-复制,标记-清扫”式垃圾回收器。

java虚拟机中有许多附加技术用于提升速度。尤其与加载器有关的,被称为即时(JIT,just in time)编译器的技术,把程序全部或部分翻译成本地机器码(这是JVM的工作),程序运行速度因此得以提升。当需要装载某个类(通常在类创建第一个对象时),编译器会先找到其.class文件,然后将该类的字节码装入内存。此时有两种方案可供选择,一种是就让即时编译器编译所有代码。但这种做法有两个缺陷:这种加载动作散落在整个程序生命周期内,累加起来要花更多时间;并且会增加课执行代码的长度(字节码奥比及时编译器展开后的本地机器码小很多),浙江导致页面调度,从而降低程序速度。另一种做法是惰性评估,意思是及时编译器只在必要的时候才编译代码。这样,从不会被执行的代码也许压根不会被JIT所编译。JDK的HotSpot技术皆采用了类似方法,代码每次被执行的时候都会做一些优化,所以执行的次数越多,它的速度越快。