来自:开源中国(oschina2013)
链接:https://www.oschina.net/translate/java-gc
如需转载请注明上述信息,其他信息无效并视为侵权
什么是自动垃圾回收?
自动垃圾回收是指监测堆内存、识别正在使用或未使用的对象、然后删除那些未使用对象的过程。正在使用中的对象,或者说被引用的对象,是指程序中的某些地方仍然维护者指向该对象的指针。未使用的对象,或者说未被引用的对象,不再被程序中的任何地方引用。所以,未被引用的对象所占用的内存可以被回收。
在像C这样的编程语言中,分配和回收内存需要手动处理。在Java中,内存回收的过程由垃圾收集器自动处理。基本过程可以描述如下。
第一步:标记
这个过程的第一步叫做标记。这一阶段,垃圾回收器识别哪些内存正在使用,哪些内存未被使用。
被引用的对象以蓝色展示。未被引用的对象以金黄色展示。在标记阶段,所有对象都需要扫描并做出判断。如果系统中的所有对象都必须扫描到,这将是一个非常耗时的过程。
第二步:常规删除
这一阶段,移除所有未被引用的对象,保留被引用的对象,并维护指向空闲内存的指针。
内存分配器维护着空闲内存块的引用,新建对象的内存将在这里被分配。
第二(a)步:删除并整理
为了进一步提高性能,除了删除未被引用的对象,还可以压缩剩余的被引用对象(的空间占用)。把被引用的对象移动到一起,会使得新对象的内存分配更容易、更快。
为什么使用分代垃圾回收?
如前所述,标记并整理所有JVM里的对象,效率是很低的。随着越来越多的对象被分配,对象列表不断增长,导致垃圾回收时间越来越长。然而,根据应用程序的经验分析,大多数的对象都是短命的。
下面是这类数据的一个例子。Y轴表示分配的字节数,X轴表示随着时间分配的字节数。
正如你所看到的,随着时间的推移,仍然存活的对象越来越少。事实上,大多数对象的生命周期都很短,如图左侧较高的值所示。
JVM分代
从对象分配行为中学到的内容,可以用来提高JVM的性能。因此,可以考虑将堆分成更小的部分或者几代。比如:年轻代、老年代、持久代。
年轻代是用来分配和老化所有新对象的地方。当年轻代被填满,将会触发Minor GC。如果对象死亡率很高Minor GC可以优化。年轻代的所有死亡对象可以被迅速回收。一些存活对象会被老化,最终会被移动到老年代。
Stop the World——所有Minor GC都会触发“Stop the World”。这意味着所有应用程序线程都会停止,直到操作完成。Minor GC 总会触发Stop the Word。
老年代用来保存长时间存活的对象。通常,设置一个阈值,当达到该年龄时,年轻代对象会被移动到老年代。最终老年代也会被回收。这个事件成为 Major GC。
Major GC 也会触发STW(Stop the World)。通常,Major GC会慢很多,因为它涉及到所有存活对象。所以,对于响应性的应用程序,应该尽量避免Major GC。还要注意,Major GC的STW的时长受年老代垃圾回收器类型的影响。
永久代包含JVM用于描述应用程序中类和方法的元数据。永久代是由JVM在运行时根据应用程序使用的类来填充的。此外,Java SE类库和方法也存储在这里。
如果JVM发现某些类不再需要,并且其他类可能需要空间,则这些类可能会被回收。
世代垃圾收集过程
现在你已经理解了为什么堆被分成不同的代,现在是时候看看这些空间是如何相互作用的。 后面的图片将介绍JVM中的对象分配和老化过程。
首先,将任何新对象分配给 eden 空间。 两个 survivor 空间都是空的。
当 eden 空间填满时,会触发轻微的垃圾收集。
引用的对象被移动到第一个 survivor 空间。 清除 eden 空间时,将删除未引用的对象。
在下一次Minor GC中,Eden区也会做同样的操作。删除未被引用的对象,并将被引用的对象移动到Survivor区。然而,这里,他们被移动到了第二个Survivor区(S1)。此外,第一个Survivor区(S0)中,在上一次Minor GC幸存的对象,会增加年龄,并被移动到S1中。待所有幸存对象都被移动到S1后,S0和Eden区都会被清空。注意,Survivor区中有了不同年龄的对象。
在下一次Minor GC中,会重复同样的操作。不过,这一次Survivor区会交换。被引用的对象移动到S0,。幸存的对象增加年龄。Eden区和S1被清空。
此幻灯片演示了 promotion。 在较小的GC之后,当老化的物体达到一定的年龄阈值(在该示例中为8)时,它们从年轻一代晋升到老一代。
随着较小的GC持续发生,物体将继续被推广到老一代空间。
所以这几乎涵盖了年轻一代的整个过程。 最终,将主要对老一代进行GC,清理并最终压缩该空间。