[读书笔记]Java垃圾回收器如何工作

  垃圾回收器工作时,将一面回收空间,一面使堆中的对象紧凑排列,也就尽量避免了页面错误。通过垃圾回收器对对象重新排列,实现了一种高速的、有无限空间可供分配的堆模型。[注:该文整理自《Thinking in Java》]
 
==============================
其他系统中的垃圾回收机制:
 
引用计数
 
  引用计数是一种简单但速度很慢的垃圾回收技术。每个对象对会含有一个引用计数,当有引用接至对象时引用加1。当引用离开作用域或被至为null时,引用计数减1。垃圾回收器会在含有全部对象的列表上持续便利,发现引用计数为0及释放该对象占用的空间。
  但是这样很容易出现“对象应该被回收但引用计数却不为0”的情况,及出现了对象中的循环使用。
 
自适应垃圾回收技术
 
  有一些更快的模式中,垃圾回收器并非基于引用计数技术。他们依据的思想是:对任何“活”的对象,一定能最终追溯到其存活在堆栈或静态存储区之中的引用。如果从堆栈和静态存储区开始,遍历所有的引用,就能找到所有“活”的对象。对于发现的每一个引用,必须追踪他所引用的对象,然后是此对象包含的所有引用,如此反复进行,直到“根源于堆栈和静态存储区的引用”所形成的网络全部被访问为止。你所访问过的对象必须都是“活”的。
 
停止-复制(stop-and-copy)
 
  先暂停程序的运行(所以它不属于后台回收模式),然后将所有存活的对象从当前堆复制到另一个堆,没有被复制的全部都是垃圾。当对象被复制到新堆去的时候,它们是一个一个紧挨着的,所以新堆保持紧凑的排列,然后就可以简单地、直接地分配空间了
  缺点:效率会降低,首先,得有两个堆,然后得在这两个分离的堆之间来回倒腾,从而得维护比实际需要多一倍的空间。其次在于复制。当程序进入稳定状态之后,可能只产生少量垃圾,尽管如此,复制式回收器仍然会将所有内存自一处复制到另一处,这很浪费。
  面对这一情况,一些Java虚拟机会进行检查,如果没有新垃圾产生,就自动转入另一种工作模式(即“自适应”)。
 
标记-清扫(mark-and-clean)
 
  对于一般用途而言,“标记-清扫”方式速度相当慢,但当你知道只会少量垃圾甚至不会产生垃圾时,这种方式就相当快了。
  “标记-清扫”同样是从堆栈和静态存储区出发,遍历所有引用,进而找出所有存活的对象,每当它找到一个存活对象,就会给对象做一个标记,这个过程不会回收对象。只有全部标记工作完成的时候,清理动作才会开始。在清理过程中,没有标记的对象将被释放,不会发生任何复制动作,所以存留下来的空间是不连续的,垃圾回收器要是希望得到连续的空间,就要重新整理剩下的对象。
 
自适应的、分代的、停止-复制、标记-清扫式垃圾回收器
 
  在这里所讨论的Java虚拟机中,内存分配以较大的“块”为单位。如果对象较大,他会占用单独的块。严格说来,“复制-停止”要求在释放旧有对象之前,必须先把所有存活对象从旧堆复制到新堆,这将导致大量内存复制行为。有了块之后,垃圾回收器在回收的时候就可以往废弃的块里拷贝对象了。每个块都有相应的代数(generation count)来记录它是否还存活。通常,如果块在某处被引用,其代数会增加;垃圾回收器将对上次回收动作之后新分配的块进行整理。这对处理大量短命的临时对象很有帮助。垃圾回收器会定期进行完整的清理动作——大型对象仍然不会被复制(只会其代数增加),内含小型对象的那些块则被复制并整理。Java虚拟机会进行监视,如果所有对象都很稳定,垃圾回收器的效率降低的话,就切换到“清扫-标记”方式;同样,Java虚拟机会跟踪“标记-清扫”效果,要是堆空间碎片很多,就切换到“复制-清扫”方式。
 
"即时"(Just-In-Time)编译器
 
  即时技术可以把程序全部或部分翻译成机器码,从而提升程序运行速度。当需要装在某个类的时候,编译器就会先找到其.class文件,然后将该类字节码装入内存。此时,有两种方式可供选择:1)让即时编译器编译所有代码;2)惰性评估(lazy evaluation),即时编译器只在必要的时候才编译代码。
  关于1)有两个缺陷:这种加载动作散落在整个程序的生命周期中,累加起来花费更多时间;会增加可执行代码的长度。
posted @ 2013-09-07 23:21  geeky_jane  Views(248)  Comments(0Edit  收藏  举报