程序的运行必然需要申请内存资源,无效的对象资源如果不及时处理就会一直占有内存资源,最终将导致内存溢出,所以对内存资源的管理是非常重要了。
引用计数是历史最悠久的一种算法,最早George E. Collins在1960的时候首次提出,50年后的今天,该算法依然被很多编程语言使用。
原理
假设有一个对象A,任何一个对象对A的引用,那么对象A的引用计数器+1,当引用失效时,对象A的引用计数器
就-1,如果对象A的计数器的值为0,就说明对象A没有引用了,可以被回收。
优缺点
class TestA{
public TestB b;
}
class TestB{
public TestA a;
}
//虽然a和b都为null,但是由于a和b存在循环引用,这样a和b永远都不会被回收。
public class Main{
public static void main(String[] args){
A a = new A();
B b = new B();
a.b=b;
b.a=a;
a = null; //释放资源
b = null; //释放资源
}
}
通过一系列称为“GC Roots”的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过程所走过的路径称为“引用链”(Reference Chain),如果某个对象到GC Roots间没有任何引用链相连,就说明从GC Roots到这个对象不可达时,则证明此对象是不可能再被使用的,就是可以回收的对象。
在JVM虚拟机中,可作为GC Roots的对象包括以下几种:
在java中,对象的引用分为:强引用(Strongly Re-ference)、软引用(Soft Reference)、弱引用(Weak Reference)和虚引用(Phantom Reference)4种。
永远不会回收
掉被引用的对象。第二次回收
,如果这次回收还没有足够的内存,才会抛出内存溢出异常。只能存活到下一次垃圾收集发生为止
。该对象是会被垃圾回收器回收
。自动化的管理内存资源,垃圾回收机制必须要有一套算法来进行计算,哪些是有效的对象,哪些是无效的对象,对于无效的对象就要进行回收处理。
常见的垃圾回收算法有:标记清除法、标记压缩法、复制算法、分代算法等。
标记清除算法,是将垃圾回收分为2个阶段,分别是标记和清除。
标记:从根节点开始标记引用的对象。
清除:未被标记引用的对象就是垃圾对象,可以被清理。
标记清除法可以说是最基础的收集算法,因为后续的收集算法大多都是以标记-清除算法为基础,对其缺点进行改进而得到的。
标记前
标记后
回收后
可以看到,标记清除算法解决了引用计数算法中的循环引用的问题,没有从root节点引用的对象都会被回收。
同样,标记清除算法也是有缺点的:
效率较低,标记和清除两个动作都需要遍历所有的对象,并且在GC时,需要停止应用程序,对于交互性要求比较高的应用而言这个体验是非常差的。
通过标记清除算法清理出来的内存,碎片化较为严重,因为被回收的对象可能存在于内存的各个角落,所以清理出来的内存是不连贯的。
标记压缩算法是在标记清除算法的基础之上,做了优化改进的算法。和标记清除算法一样,也是从根节点开始,对对象的引用进行标记,在清理阶段,并不是简单的清理未标记的对象,而是将存活的对象压缩到内存的一端,然后清理边界以外的垃圾,从而解决了碎片化的问题。
优缺点同标记清除算法,解决了标记清除算法的碎片化的问题,同时,标记压缩算法多了一步,对象移动内存位置的步骤,其效率也有有一定的影响。
复制算法的核心就是,将原有的内存空间一分为二,每次只用其中的一块,在垃圾回收时,将正在使用的对象复制
到另一个内存空间中,然后将该内存空间清空,交换两个内存的角色,完成垃圾的回收。
如果内存中的垃圾对象较多,需要复制的对象就较少,这种情况下适合使用该方式并且效率比较高,反之,则不适合。
优点:
在垃圾对象多的情况下,效率较高
清理后,内存无碎片
缺点:
在垃圾对象少的情况下,不适用,如:老年代内存
分配的2块内存空间,在同一个时刻,只能使用一半,内存使用率较低
目前使用的是分代算法
在堆内存中,有些对象短暂存活有些则是长久存活,所以需要将堆内存进行分代,将短暂存活的对象放到一起,进行高频率的回收,长久存活的对象集中放到一起,进行低频率的回收,这样才能够更加合理的利系统资源。
分代算法其实就是这样的,根据回收对象的特点进行选择,在jvm中,年轻代适合使用复制算法,老年代适合使用标记清除或标记压缩算法。