判断Java对象存活的算法
一、引用计数算法
给对象添加一个引用计数器,每当一个地方引用它的时候就将计数器加1,当引用失效的时候就将计数器减1,任何时刻计数器为0的对象都不可再被使用。这种算法虽然简单,但是有个致命的缺点,就是不能适用于相互引用的情况。
二、可达性分析算法
通过一系列称为"GC Roots"的对象作为起始点,从这些节点往下搜索,搜索走过的路径称为引用链(Reference Chain)。当一个对象不在任何引用链上的时候,就表示这个对象不可达,不可用了。
可作为GC Roots的对象包括:
1、虚拟机栈中引用的对象
2、方法区中静态变量和常量引用的对象
3、Native方法中引用的对象
一、标记清除算法
标记清除(mark-sweep)算法是现代垃圾回收算法的思想基础。标记-清除算法将垃圾回收分为两个阶段:标记阶段和清除阶段。先标记,再清除。
有2个缺点:
1、效率问题。标记和清除两个过程的效率都不高。
2、空间问题。标记清除后会产生大量不连续的内存碎片,碎片太多可能会导致以后在程序运行过程中需要分配较大的对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集操作。
二、标记整理算法
标记整理算法和标记清除算法类似,不同的是,标记整理算法在标记完对象后,不是直接对可回收对象进行清除,而是先让所有存活的对象都往一端移动,然后再清除掉边界以外的内存。
相对于标记清除算法,标记整理算法解决了内存碎片的问题,但效率不高的问题依然存在。
三、复制算法
复制算法可以解决效率问题。它将可用内存分成大小相等的两块,每次只使用其中的一块,当这一块用完了,就将还存活着的对象复制到另一块上面,然后再把原来半块的对象全部清理掉。这样,每次都是对整个半区进行内存回收,内存分配时也不用考虑内存碎片的情况,按顺序分配即可。
复制算法的优点是效率高,没有内存碎片。但也有2个缺点:
1、浪费一半的内存空间。
2、在对象存活率较高的情况下,会有较多的复制操作,效率会变低。
四、分代收集算法
根据对象存活周期的不同,将内存分为几块,一般是把Java堆分为新生代和老年代,然后根据各个年代的特点采用最适当的收集算法。在新生代中,每次垃圾收集都有大批对象死去,只有少量存活,就选用复制算法。而老年代对象存活率高,必须采用标记清除算法或者标记整理算法来回收内存。
新生代又可细分为一块较大的Eden空间和两块较小的Survivor空间(HotSpot虚拟机默认Eden和Survivor的大小比例是8:1,即Eden空间占堆内存的80%,两个Survivor空间各占10%),每次只使用Eden空间和一块Survivor空间。对象优先在Eden空间分配内存,如果对象过大,则会分配到老年代空间。新生代回收时,会把Eden空间和Survivor空间中存活的对象复制到另一块Survivor空间,然后清理掉Eden空间和之前Survivor空间的对象。当然,如果在复制的时候,新的Survivor空间不够,则会把多出来的对象复制到老年代空间。同时,新生代中存活时间较长的对象也会进入老年代。