意义
在C++中,对象所占的内存在程序结束运行之前一直被占用,在明确释放之前不能分配给其它对象;而在Java中,当没有对象引用指向原先分配给某个对象的内存时,该内存便成为垃圾。JVM的一个系统级线程会自动释放该内存块。垃圾收集意味着程序不再需要的对象是"无用信息",这些信息将被丢弃。当一个对象不再被引用的时候,内存回收它占领的空间,以便空间被后来的新对象使用。事实上,除了释放没用的对象,垃圾收集也可以清除内存记录碎片。由于创建对象和垃圾收集器释放丢弃对象所占的内存空间,内存会出现碎片。碎片是分配给对象的内存块之间的空闲内存洞。碎片整理将所占用的堆内存移到堆的一端,JVM将整理出的内存分配给新的对象。
垃圾收集能自动释放内存空间,减轻编程的负担。这使Java 虚拟机具有一些优点。首先,它能使编程效率提高。在没有垃圾收集机制的时候,可能要花许多时间来解决一个难懂的存储器问题。在用Java语言编程的时候,靠垃圾收集机制可大大缩短时间。其次是它保护程序的完整性,垃圾收集是Java语言安全性策略的一个重要部份。
垃圾收集的一个潜在的缺点是它的开销影响程序性能。Java虚拟机必须追踪运行程序中有用的对象,而且最终释放没用的对象。这一个过程需要花费处理器的时间。其次垃圾收集算法的不完备性,早先采用的某些垃圾收集算法就不能保证100%收集到所有的废弃内存。当然随着垃圾收集算法的不断改进以及软硬件运行效率的不断提升,这些问题都可以迎刃而解。
一般来说,Java开发人员可以不重视JVM中堆内存的分配和垃圾处理收集,但是,充分理解Java的这一特性可以让我们更有效地利用资源。同时要注意finalize()方法是Java的缺省机制,有时为确保对象资源的明确释放,可以编写自己的finalize方法。
原理
关于垃圾收集器,在学习GC前,你应该知道一个技术名词:这个词是“stop-the-world。“无论你选择哪种GC算法,Stop-the-world都会发生。Stop-the-world意味着JVM停止应用程序,而去进行垃圾回收。当stop-the-world发生时,除了进行垃圾回收的线程,其他所有线程都将停止运行。被中断的任务将在GC任务完成后恢复执行。GC调优往往意味着减少stop-the-world的时间。
分代垃圾收集
在Java代码中,Java语言没有显式的提供分配内存和删除内存的方法。一些开发人员将引用对象设置为null或者调用System.gc()来释放内存。将引用对象设置为null没有什么大问题,但是调用system.gc()方法会大大的影响系统性能,绝对不能这个干。(谢天谢地,我还没看到任何NHN开发者调用这个方法。)
在Java中,由于开发人员没有在代码中显式删除内存,所以垃圾收集器会去发现不需要(垃圾)的对象,然后删除它们,释放内存。这款垃圾收集器是基于以下两个假设而创建的。(称他们为前提条件更好,而不是假设。)
绝大多数对象在短时间内变得不可达,只有少量年老对象引用年轻对象。这些假设被称为“弱代假说”。为了发挥这一假设的优势,在HotSpot虚拟机中,物理的将内存分为两个—年轻代(young generation)和老年代(old generation)。
年轻代:新创建的对象都存放在这里。因为大多数对象很快变得不可达,所以大多数对象在年轻代中创建,然后消失。当对象从这块内存区域消失时,我们说发生了一次“minor GC”。
老年代:没有变得不可达,存活下来的年轻代对象被复制到这里。这块内存区域一般大于年轻代。因为它更大的规模,GC发生的次数比在年轻代的少。对象从老年代消失时,我们说“major GC”(或“full GC”)发生了。
我们看一下这幅图:
图 1: GC区 & 数据流
上图中的永久代(permanent generation)也称为“方法区(method area)”,他存储class对象和字符串常量。所以这块内存区域绝对不是永久的存放从老年代存活下来的对象的。在这块内存中有可能发生垃圾回收。发生在这里垃圾回收也被称为major GC。
一些人可能想知道:一个老年代的对象需要引用年轻代的对象,该怎么办?
为了解决这些问题,老年代中有一个被称为“卡表(card table)”的东西,它是一个512 byte大小的块。每当老年代的对象引用年轻代对象时,这种引用会被记录在这张表格中。当垃圾回收发生在年轻代时,只需对这张表进行搜索以确定是否需要进行垃圾回收,而不是检查老年代中的所有对象引用。这张表格用一个叫做“写闸(write barrier)”的东西进行管理。“写闸”是一种装置,对minor GC有更好性能。虽然因为这种机制,会产生一些时间性能开销,但降低了整体的GC时间。
年轻代组成部分
为了理解GC,我们学习一下年轻代,对象第一次创建发生在这块内存区域。年轻代分为3块,Eden区和2个Survivor区。
年轻代总共有3块空间,其中2块为Survivor区。各个空间的执行顺序如下:
绝大多数新创建的对象分配在Eden区。
在Eden区发生一次GC后,存活的对象移到其中一个Survivor区。
在Eden区发生一次GC后,对象是存放到Survivor区,这个Survivor区已经存在其他存活的对象。
一旦一个Survivor区已满,存活的对象移动到另外一个Survivor区。然后之前那个空间已满Survi