Java虚拟机在回收对象时,是处理掉一些不再使用的的对象,而存活的对象将被继续使用,那什么叫不再使用的对象呢?答案就是已经死了的对象。在一些教科书上我们可以看到书中说判断对象是否已经死了是通过引用计数算法,即对象有一个引用计数器,对象每次被使用该引用计数器的值就增加1,反之引用失效该计数器的值就减1,任何时刻引用计数器的值为0的对象就是已经死了的对象即虚拟机可以回收的对象。当前比较火热的Python语言是采用引用计数算法但是主流的Java虚拟机中却不是采用引用计数算法,而是采用可达性分析算法!
可达性分析算法是从一些被称作GC Roots的对象出发向下搜索,搜索经过的路径被称作引用链,当从GC Roots出发没有引用链达到该对象,那么这个对象就是可被回收的。GC Roots对象有以下四类:
- 虚拟机栈(栈帧中本地变量表)中,引用的对象;
- 方法区类静态属性,引用的对象;
- 方法区中常量,引用的对象;
- 本地方法栈中引用的对象;
可达性分析算法标记了可以被回收的对象,那么该如何进行回收呢,有以下几种垃圾回收算法。
- 标记清除算法(Mark-Sweep):该算法分为两个步骤,首先标记出可回收的对象,然后统一处理。这种做法容易产生空间碎片在程序碰到分配大对象时容易触发另一次垃圾回收处理。
- 复制算法:该算法将内存区域分为大小相等的两个部分A跟B,对象创建时分配的内存空间在A上,当在进行垃圾回收时将存活的对象复制到区域B中,然后将区域A整个进行清除操作。这种做法避免空间随便的问题且实现简单,运行高效,但是降低了空间利用率。
- 标记整理算法(Mark-Compact):标记过程跟Mark-Sweep一致,标记后不是立即清除可回收对象,而是将存活对象都向一端移动,然后再清理掉端边界以外的内存。
当前商业虚拟机中采用分代收集算法,这里没有什么新奇的方式,只不过按照对象的存活周期将内存划分不同的区域,一般是Java堆内存分为新生代跟老年代,新生代中又分成一个较大Eden区跟两个较小Survivor区域,Eden跟Survivor区域大小比例默认是8:1,新生代中的对象存活时间短每次垃圾回收时有大量的对象死去,因此使用复制算法。每次使用Eden跟Survivor其中一个区域,在回收时将存活的对象一次性复制到另一个Survivor区域。而老年代中的对象由于存活率高且没有额外担保,因此使用标记清理或者标记整理算法。