Java虚拟机之垃圾收集算法

内存运行时区域的各个部分中,程序计数器、虚拟机栈、本地方法栈3个区域随线程而生,随线程而灭。栈中的栈帧随着方法的进入和退出而有条不紊地执行着出栈和入栈操作。每一个栈帧中分配多少内存基本上是在类结构确定下来时就已知的,所以这几个区域的内存分配和回收都具备确定性,不需要考虑过多的问题。方法结束或线程结束时,内存就跟着回收了。

而Java堆内存分配是动态的,只有在程序运行期间才知道会创建哪些对象。

一、哪些对象还“存活”着,哪些已“死去”

垃圾收集器在对堆进行回收时首先就是要确定堆中的哪些对象还“存活”着,哪些已“死去”(不会再被使用的对象)。

1. 引用计数法

给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值减1;当计数器值为0时就表示该对象不可能再被使用了。

主流的Java虚拟机里面没有选用这种算法来管理内存,最主要的原因是它很难解决对象之间相互循环引用的问题。

2. 可达性分析算法

该算法是通过一系列成为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索锁走过的路径成为引用链,当一个对象到GC Roots没有任何引用链相连时,就证明这个对象时不可用的。它将被判定为可回收的对象。

在Java中,可作为GC Roots的对象包括下面几种:
1. 虚拟机栈中引用的对象
2. 方法区中类静态属性应用的对象
3. 方法区中常量引用的对象
4. 本地方法栈中JNI(Native方法)引用的对象

二、垃圾收集算法

目前主要有如下四种垃圾收集算法。

1. 标记-清除算法

标记-清除(Mark-Sweep)算法是最基础的算法,分为“标记“和“清除”两个阶段。首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。
主要有以下不足之处:
效率不高问题:标记和清除两个过程效率都不高。
空间碎片问题:标记清除之后会产生大量的不连续内存碎片。空间碎片太多可能导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。

2. 复制算法(新生代采用)

复制(copying)算法就是解决效率问题的,它将可用的内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块内存用完了,就将还活着的对象复制到另一块上,然后再把已用过的内存空间一次清理掉。
实现简单,效率高。不用考虑内存碎片等复杂情况
代价是将内存缩小了为原来的一半。

现在的商用虚拟机都采用这种收集算法来回收新生代。新生代将内存分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden和其中一块Survivor。
当回收时,将Eden和Survivor中还存活着的对象一次性地复制到另外一块Survivor空间上,最后清理掉Eden和刚才使用过的Survivor空间。
Hotspot虚拟机默认Eden和Survivor的大小比列是8:1,所以只有10%的内存会被浪费。
当Survivor空间不够时,需要依赖其他内存(老年代)进行分配担保。

3. 标记-整理(压缩)算法(老年代采用)

标记-整理(Mark-Compact)算法是根据老年代的特点提出的。
分为如下两个过程:
标记过程:与“标记-清除”算法过程一样,标记出所有存活的对象。
整理过程:将存活的对象都移动到一端,然后直接清理掉端边界以外的内存。

4. 分代收集算法

当前商用虚拟机的垃圾收集都采用“分代收集”(Gennerational Collection)算法。
根据对象存活的周期不同将内存划分为几块。一般把Java堆分为新生代和老年代,然后根据各个年代的特点采用适当的收集算法。
新生代:每次收集都有大批对象死去,只有少量存活,所以采用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。
老年代:存活率高,没有额外的空间进行分配担保,就必须使用“标记-清理”或“标记-整理”算法来回收。
这里写图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值