JAVA进阶(5)—— 内存回收机制
哪些内存需要回收
可达性分析法
基本思想是通过一系列称为“GC Roots”的对象作为起始点,从这些节点向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链(即GC Roots到对象不可达)时,则证明此对象是不可用的。
在Java语言中可以作为GC Roots的对象包括:
- 虚拟机栈中引用的对象
- 方法区中静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈中JNI(即Native方法)引用的对象
垃圾回收算法
标记-清除(Mark-Sweep)算法
这是最基础的算法,分为“标记”和“清除”两个阶段:首先
标记出所有需要回收的对象,标记完成后统一回收所有被标记的对象。
不足主要体现在效率和空间,
从效率的角度讲,标记和清除两个过程的效率都不高;
从空间的角度讲,会产生大量不连续的内存碎片, 内存碎片太多可能会导致以后程序运行过程中在
需要分配较大对象时,无法找到足够的连续内存而不得不提前触发一次GC。执行过程如图:
image
复制(Copying)算法
复制算法是为了解决效率问题而出现的,它将可用的内存分为两块,每次只用其中一块,当这一块内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已经使用过的内存空间一次性清理掉。这样每次只需要对整个半区进行内存回收,内存分配时也不需要考虑内存碎片等复杂情况,只需要移动指针,按照顺序分配即可。
复制算法的执行过程如图:
image
标记-整理(Mark-Compact)算法
复制算法在对象存活率较高的场景下要进行大量的复制操作,效率很低。万一对象100%存活,那么需要有额外的空间进行分配担保。老年代都是不易被回收的对象,对象存活率高,因此一般不能直接选用复制算法。根据老年代的特点,有人提出了另外一种标记-整理算法,过程与标记-清除算法一样,不过不是直接对可回收对象进行清理,而是让所有存活对象都向一端移动,然后直接清理掉边界以外的内存。
标记-整理算法的工作过程如图:
image
分代收集算法
当前商业虚拟机都采用这种算法。首先根据对象存活周期的不同将堆内存分为几块即新生代、老年代,然后根据不同年代的特点,采用不同的收集算法。在新生代中,每次垃圾收集时都有大量对象死去,只有少量存活,所以选择了复制算法。而老年代中因为对象存活率比较高,所以采用标记-整理算法(或者标记-清除算法)
image
新生代又被进一步划分为Eden和Survivor区,最后Survivor由FromSpace和ToSpace组成。新建的对象都是用新生代分配内存,Eden空间不足的时候,会把存活的对象转移到Survivor中。老生代用于存放新生代中经过多次垃圾回收(也即Minor GC)仍然存活的对象。