本系列文章由作者。
@hunter129翻译,转载请注明出处。
第三章包括5个小部分,1.垃圾收集的基本概念,2.优秀垃圾收集器的特征,3.收集器的设计选择,4.性能度量指标,5.分代收集的基本原理。内容比较长,我会分成2篇帖子发,本章的内容是通用的,也就说这些内容同样适用于其他JVM的实现,甚至其他语言的VM的垃圾收集实现。
今天的部分包括1-3,以下是正文:
第三章 垃圾收集概念垃圾收集器负责以下几个事情:
1.分配内存
2.确保被引用的对象留在内存中
3.回收执行中代码的引用无法到达的对象占用的内存(译注:强调执行中是为了排除对象互相引用的情况。A、B互相引用,但没有任何执行中代码引用他们,A、B也应被回收)
对象被引用称为活着的(live)。不再被引用的对象称为死掉的(dead),术语叫垃圾(garbage)。发现和释放(或者叫回收(reclaiming))这些垃圾占用的空间叫做垃圾收集(garbage collection)。
垃圾收集可以解决很多内存分配问题,却不能解决所有的内存分配问题。例如,你可以创建对象并无限期的引用着直到没有内存可用。垃圾收集在使用其自身的时间和资源上是一个复杂的任务。
垃圾收集器负责的内存是由精确的算法来组织、分配和回收的,并对开发人员隐藏。空间通常从一个被称为堆(heap)的非常大的内存池分配出来。垃圾收集的时间由垃圾收集器决定。通常,整个堆或堆的一部分被填满或者达到某个阈值的时候,会触发垃圾收集。
满足内存分配的请求是一个困难的任务,这其中包括要从堆中找到一个足够大的内存块。对于大部分的动态内存分配算法,其主要的问题是需要在保持内存分配和回收效率的同时避免内存碎片。
优秀的垃圾收集器的特征垃圾收集器必须是安全有效的。就是说,使用中的数据永远不能被错误的释放,同时在很少的几个收集周期内垃圾就应该被回收。
垃圾收集器的执行效率也必须很优秀,暂停时间不能太长。暂停的时候,应用系统是不运行的。然而,像大部分的计算机系统那样,这里也必须在时间、空间、频率之间做出权衡。例如,堆很小的时候,垃圾收集的速度很快但堆被填满的速度更快,这样就需要更频繁的垃圾收集。相反的,大的堆填满的速度慢,收集的频率也慢,但花费的时间会比较长。
另一个特征是有限的内存碎片(fragmentation)。当垃圾对象的内存释放后,释放的空间会在各种各样的区域形成小块的空隙以至于可能导致没有一个足够大的连续区域分配给较大的对象。一种减少内存碎片的方法叫做压缩(compaction),在下面垃圾收集器的设计决策部分会讨论到。
可扩展性同样很重要。在多核系统上运行的多线程程序中,内存分配、垃圾收集都不能成为瓶颈。
设计决策设计和选择垃圾收集算法时必须做出一系列选择:
串行还是并行 在串行收集中,同一时间只做一件事情。例如,即便有多个cpu可用,却只有一个进行收集工作。当使用并行收集时,垃圾收集任务会分成几个小的部分,这些小的部分在不同的cpu上同时执行。同时执行的操作使得收集速度更快,它的代价是额外的复杂性和可能更多的内存碎片。
并发的(Concurrent)还是停止一切(Stop-the-world) 当执行停止一切(Stop-the-world)的垃圾收集时,应用系统在收集期间完全暂停(suspended)了。另外一种选择是,一个或多个垃圾收集任务可以和应用系统同时并发的执行。通常,一个并发的收集器,大部分工作并发的执行,但仍会有一些短暂的暂停(stop-the-world pauses)。停止一切的垃圾收集比并发收集更简单,因为整个堆都冻结了,在收集期间对象不会改变。它缺点是一些应用程序不喜欢的暂停(paused)。相应的,并发收集的暂停时间更短,但收集器必须格外的小心,执行收集的同时应用系统可能会改变对象的状态,这会增加一些开销。并发收集会影响性能并且需要较大的堆内存。
压缩 or 不压缩 or 拷贝
当收集器判定内存中的对象哪些是存活的哪些是垃圾之后,收集器可以压缩(compact)内存,将所有存活的对象放到一起,从而完全的恢复剩余的内存。压缩之后,在第一个空闲位置分配内存将会非常的容易和迅速。可以用一个简单的指针维持下一个可分配对象的位置。相对压缩的收集器,非压缩(non-compacting)的收集器在原地(in-place)释放垃圾对象占用的空间,它不会像压缩的收集器那样移动存活的对象创建一个大的回收区。非压缩的好处是收集完成的很快,缺点是可能有内存碎片。一般来说,从原地释放的内存分配空间比从压缩的堆分配内存更困难些。它必须搜素堆空间找到一个足够大能容纳新对象的连续内存区域。第三种可供选择的是复制(copying)收集器,拷贝(或疏导evacuates)所有活动的对象到另一个不同的内存区域。它的好处是原来的区域可以直接置空,简单快速的为随后的内存分配做好准备,缺点是需要额外的空间和时间。