Java垃圾回收机制—几种回收方式的介绍(学习笔记)

  • 垃圾回收器如何工作

       它像一个传送带,每分配一个新对象,它就往前移动一格。这意味着对象存储空间的分配速度非常快。Java的“堆指针”只是简单地移动到尚未分配的区域,其效率比得上C++在堆栈上分配空间的效率。当它工作时将一面回收空间,一面使堆中的对象紧凑排列,这样“堆指针”就可以很容易移动到传送带的开始处,也就避免了一些错误的发生。通过垃圾回收器对对象重新排列,实现了一种高速的、有无限空间可被分配的堆模型。

  • 实现

  • 引用计数

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

  • 停止—复制and标记—清扫

       停止-复制意味着先暂停程序,然后将所有存活的对象从当前堆复制到另一个堆,没有被复制的全部都是垃圾。当对象被复制到新堆时,它们是一个挨着一个的保持着紧凑的状态,所以可以直接分配新空间了。

       当把对象搬到另一处的时候,所有指向它们的引用都需要修改,位于堆和静态存储区的引用可以直接修改,但是可能还有其他指向这些对象的引用,需要在遍历的过程中才能被找到(可以想象成一个表格,将旧地址映射到新地址)。

       对于这种所谓的“复制式回收器”而言,效率会低下,这其中有两个原因。

       1:得有两个堆,然后得在这两个分离的堆之间来回倒腾,从而得维护比实际需要多一倍的空间(某些Java虚拟机的处理方式 是,按需从堆中分配几块较大的内存,复制动作发生在这些大块内存之间)。

       2:第二个问题在于复制。程序进入稳定状态后,可能只产生少量的垃圾,甚至没有垃圾。尽管如此,复制式回收器仍会将所有的内存自一处复制到另一处,这很浪费。为了避免这种情况就会转换为另一种工作模式(自适应)。这种模式成为标记-清扫,标记-清扫方式的速度相当慢,但是当你知道只会产生少量垃圾甚至不会产生垃圾时,它的速度就很快了。

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

       在这里讨论Java虚拟机中,内存分配以较大的“块”为单位。如果对象较大,它会占用单独的块。严格来说,“停止-复制”要求在释放对象前,必须先把存活的对象从旧堆复制到新堆,这将导致大量的内存复制行为。有了块之后,垃圾回收器在回收的时候就可以往废弃的块里拷贝对象了。每个块都用相应的代数来记录它是否还存活。通常,如果块在某处被引用,其代数就会增加;垃圾回收器将对上次回收之后的块进行整理。这对处理大量短命的临时对象很有帮助。垃圾回收器会定期进行完整的清理动作——大型对象仍然不会被复制(其代数将会增加),内含小型对象的那些块则被复制并并整理。Java虚拟机会进行监视,若果所有的对象都很稳定,垃圾回收器的效率降低的话,就切换到“标记-清扫”方式;同样,要是空间出现很多碎片,就会切回“停止-复制”的方式。这就是“自适应”技术。

 

你可能感兴趣的:(java,垃圾回收器)