在C/C++中使用完内存空间之后都要手动进行清除释放内存空间,如果没有及时释放内存空间,可能回导致内存溢出或“引用悬空”(dangling references),而Java拥有一套完善的自动垃圾回收机制,避免了上述问题的发生。两种常用的办法是引用计数和对象引用遍历
引用计数收集器
早期的JVM垃圾回收机制中使用引用计数收集器来对垃圾进行回收。在这种方法中,JVM为每个对象设置了一个引用计数器,当一个对象被创建并分配了变量后,将引用计数器置为1,若有某个变量引用了该对象,则将引用计数器加1,若某个引用该对象的变量超出了生命周期或被赋予了新值,则将引用计数器减1,当某个对象对应的引用计数器为0是,JVM就将其视为垃圾进行回收。这种方法的优点是:资源开销小,能够交织在程序中执行,对程序的性能影响不大;缺点是:当碰到循环引用,如父对象引用子对象,反过来子对象也引用了父对象,则他们的引用计数器就永远不会变为0
跟踪收集器
现在大多数JVM采用对象引用遍历,该方法从一组对象开始,递归遍历该对象图的链,直至找到可到达的对象,并对其进行标记。当遍历完成后,扫描存放对象的堆栈区,对未标记的对象进行清除并释放内存空间。如果现在就停止操作,则内存就会产生大量内存碎片,因此JVM的垃圾回收机制会重新组织被标记的对象,并压缩内存。
下面介绍一些JVM垃圾回收机制的算法
标记-清除
通过遍历对象,并对可到达的对象进行标记,之后扫描堆栈区,清除释放未被标记对象的内存空间。该收集器可以使用单线程完成。
压缩
在清除工作之后,将被标记的对象放到新区域中,这种方法也停止其他操作。
复制
将存放对象的堆栈区域分成两个空间,每次都只能使用其中一个空间,将新生成的对象放入另外一个空间;JVM将可到达的对象放入另一个空间,以此压缩空间。但这种方法不适合具有长生命周期的对象,因为多次的更换长生命周期的对象会消耗大量开销。
增量
该收集器将堆栈区域分为两个以上的区域,每次仅对其中的一个区域进行垃圾回收,这样只占用了较少的程序中断时间,让用户几乎察觉不到垃圾回收机制在执行。
分代
将堆栈分为两个或两个以上的域,分别用来存放不同寿命的对象,将新生成的对象放入其中一个域中,当过了一段时间后将继续存在的对象将被放入到具有更长寿命的区域中,每个区域使用不同的算法。