Java 内存回收(一)
次访问
首先明白一个问题:这一篇讲得内存回收主要是针对Java堆而言的,因为栈的内存随着出栈操作自动回收。
怎样对象是否存活?
在回收之前必然要判断堆中的对象是否还存活。
1.引用计数法
给对象添加一个引用计数器,每当有一个地方引用它时,计数器值加1;当引用失效时,计数器值减去1,当计数器为0时,对象不能再被使用。
这种算法实现简单,但是缺点太明显。
记住一句话:在主流的Java 虚拟机里面没有选取引用计数法来管理内存,其中最主要的原因就在于它的循环引用。
2.可达性分析算法
通过一系列的GC Roots的对象作为起点,从这些节点开始向下搜索,当任何一个对象不能通过引用链找到时,就判定为可回收对象。
这种算法被主流商用语言采用。
下面是画的一张图,浅绿色表明是仍然存活的对象,因为从GC Roots可以找到这些对象,但是蓝色的对象已经判定为可回收对象,因为无法从根对象寻址到这些对象。
关于引用
分成四类:
1.强引用:程序代码中普遍存在的,类似”Object obj = new Object()”这类引用。垃圾收集器不会问津这些对象。
2.软引用:用来描述一些还有用但是并非必须的对象。对于这些对象,系统将要发生内存溢出之前将其回收掉。
3.弱引用:比软引用更弱,这些对象只能生存到下一次垃圾收集发生之前。
4.虚引用:最弱,对对象的生存周期不构成影响,设置虚引用的目的是在这个对象被垃圾收集器回收时,收到一个系统通知。
垃圾收集算法
下面进入本文的核心内容:垃圾回收算法
1.标记-清除算法(Mark-Sweep)
最基础的收集算法,后面的算法都是这种算法的改进。分为两个阶段:
标记阶段:标记出所要回收的对象。
回收阶段:回收所被标记的对象。
缺点:
a.效率太低。
b.内存碎片:标记清除之后产生大量的不连续的内存碎片,导致分配较大的对象时出现空间不够的问题。
2.复制算法
将可用内存按照容量划分为大小相等的两块,每次只使用其中的一块,当一块内存用完了,就将还存活着的对象复制到另一块上,然后再把已经使用过的内存空间一次释放。每次对半个空间进行内存回收。
优点:算法简单,运行高效,没有内存碎片。适用于清理生命周期较短的对象。
缺点:内存使用率不高,每次只能使用一半内存。
3.标记压缩算法(Mark-Compact)
标记过程仍然与“标记-清除”算法一样;然后将所有存活对象都向一端移动,然后直接清理掉端边界之外的内存。
优点:内存利用率较高,适用于回收生命周期较长的对象。
4.分代收集算法
当前商业虚拟机垃圾收集采用分代收集(Generation Collection)算法,集上面三种方法之大成。
根据对象存活周期的不同将内存划分为几块,一般把Java堆分成了新生代和老年代,在新生代对象存活期较短,采用复制算法,老年代对象存活周期较长,则使用“标记-清理”或者“标记-压缩”算法来进行回收。