昨天看到一个面试题,问的是Java的垃圾回收机制,当时想了想,发现自己对这块一知半解,很难组织系统的语言来描述。既然不是很清楚,当然要趁这个机会好好了解一下。这种东西自然是人家官网的文档最为准确,果断跑去Oracle网站上,找到了一篇讲Java垃圾回收的基础教学篇。看完之后顺便翻译一下,以便以后翻阅。
原文地址:http://www.oracle.com/webfolder/technetwork/tutorials/obe/java/gc01/index.html
什么是自动垃圾回收?(What is Automatic Garbage Collection?)
自动垃圾回收是针对堆区内存中的对象,分辨哪些对象是正在使用中而哪些对象没有,然后删除那些没用的对象。一个正在使用中的对象或者是一个被引用的对象,意味着你的程序中的某部分仍然维护着指向这个对象的指针。一个无用的对象或者是一个无引用的对象,是说它已经不再被你的程序中任何一个模块所引用。所以被一个无用对象所占用的内存是可以被回收的。
在其他的编程语言,如C中,分配和回收内存是用户手动管理的。但在Java中,内存的回收是由垃圾回收器自动管理的。基本的处理过程如以下的描述。
步骤 1:标记(Marking)
处理过程的第一步叫做标记。这一步中垃圾回收器会分辨哪些内存是使用中,哪些没有。
被引用的对象用蓝色表示。没有被引用的对象用金黄色表示。在标记阶段会扫描所有的对象以判断。这会是一个非常耗时的处理过程因为系统中所有的对象都需要被扫描。
步骤 2:正常删除(Normal Deletion)
正常删除会删除那些没有被引用的对象,保留被引用的对象和指向可用空间的指针。
内存分配器会维护指向可用空间的指针,以便分配新的对象时使用。
步骤 2a:删除压缩(Deletion with Compacting)
为了提高性能,除了删除未被引用的对象之外,你还可以压缩保留的被引用对象。通过将被引用的对象合并到一起,这使得新内存的分配更加简单和快速。
为什么分代垃圾回收?(Why Generational Garbage Collection?)
上文说过,在一个JVM中标记和压缩所有的对象是很低效率的。随着越来越多的对象被分配,对象列表也变得越来越长而垃圾回收的时间就变得越来越长。然而,通过对应用程序的经验分析表明大多数对象的存活时间都很短。
这里是一个例子,Y轴表示已分配的字节数,X轴表示随着时间变化已分配的字节数的变化。
你可以看到,随着时间的增加越来越少的对象在占用内存。实际上,大多数的对象如图中左边所示只有很短的生命周期。
JVM分代(JVM Generations)
从对象分配的规律中学习到的信息,我们可以用以来改善JVM的性能。因此,堆区被分成更小的部分或者说代(generations)。主要有:年轻代(Young Generation)、年老代(Old or Tenured Generation)和永久代(Permanent Generation)。
年轻代,所有新分配和成长中的对象。当年轻代装满时,会引起一次小垃圾回收(minor garbage collection)。小回收可以通过提高对象的淘汰率来提高效率。一个充满死对象的年轻代可以被非常迅速的回收。一些幸存的对象会变老,最终会被移到年老代中。
让地球停止转到的事件(Stop the World Event)- 所有的小垃圾回收都是“让地球停止转动”的事件。这意味着,所有的应用线程都会被停止直到这次回收结束。小垃圾回收总是“让地球停止转动”的事件。
年老代,用来存储长久幸存下来的对象。一般地,对年轻代的对象设置一个阈值,当它的年龄到达了阈值时,这个对象就会被转移到年老代中。最终年老代也需要被回收。这个事件叫做大垃圾回收(major garbage collection)。
大垃圾回收也是一个“让地球停止转动”的事件。一个大垃圾回收经常会更加的缓慢,这是因为它包含了所有活着的对象。所以对于响应型应用程序(Responsive application)来说,大垃圾回收的次数要尽可能的少。同样需要注意,大垃圾回收占用的时间与使用的垃圾回收器的种类也有很大的关系。
永久代,包含JVM所需要的用于描述类和方法的元数据。永久代是在运行时由JVM根据应用程序中所使用的类来进行分配。另外,Java SE库的类和方法可能会存储在这里。
如果JVM发现程序不再需要一些类的时候,它会将这些类回收然后将空间分配给其他的类。在一次全垃圾回收(full garbage collection)中,永久代也会被回收。
分代垃圾回收过程(The Generational Garbage Collection Process)
现在你已经明白了为什么堆区会被分为不同的代,是时候来看一下这些代之间是怎样联系的。以下的图片讲述了JVM中对象的分配和年龄增长的过程。
1. 首先,任何新分配的对象会被分配到伊甸园(eden)中。两个幸存者(survivor)空间一开始都是空的。
2. 当伊甸园被装满时,触发一次小垃圾回收。
3. 被引用的对象会被转移到第一个幸存者空间中。未被引用的对象会被删除。
4. 在下一次小垃圾回收时,伊甸园中发生相同的事情。未被引用的对象被删除而被引用的对象被移到一个幸存者空间中。然后,这回它们被转移到第二个幸存者空间中(S1)。另外,在第一个幸存者空间(S0)中仍幸存的对象会年龄增加然后转移到S1中。一旦所有幸存的对象都被移动到S1中,S0和伊甸园就都清理完毕。注意现在我们现在在幸存者空间中有不同年龄的对象了。
5. 再下一次小垃圾回收,处理流程与上一次一样。不过这次幸存者空间调换了位置。被引用的对象都被转移到了S0.幸存的对比年龄增加。伊甸园和S1被清理完毕。
6. 这次有了晋级操作。在一次小垃圾回收后,当对象的年龄达到了阈值(在例子中为8),它们就从年轻代晋级到了年老代。
7. 由于小垃圾回收不断地被触发,就有对象不断的晋级到年老代。
- 到目前为止已经差不多覆盖了年轻代所有的处理过程。最终,在年老代会进行一次大垃圾回收来清理和压缩空间。
PS:看完整个垃圾回收的基础内容之后,很深的一个感受就是命名的时候真的是大有深意啊。有些时候看中文翻译过来的名词会感觉很莫名其妙,但是看了原文后就会恍然大悟。例如本文中涉及到的词:eden, survivor, yong generation, old generation。有兴趣的可以查一下eden的典故,相信很容易就可以把Java分代垃圾回收这一过程理解为一个讲述大自然优胜劣汰适者生存的故事。