JVM--标记-清除算法Mark-Sweep

前言

垃圾自动回收机制的出现使编程更加的简单,使得我们不需要再去考虑内存分配和释放的问题,而是更加的专注在我们产品功能的实现上。但是我们还是需要花时间去了解下垃圾收集机制是怎么工作的,以便后面能够更好的进行我们应用的性能调优等。

目前最基本的垃圾收集算法有四种,标记-清除算法(mark-sweep),标记-压缩算法(mark-compact),复制算法(copying)以及引用计数算法(reference counting).而现代流行的垃圾收集算法一般是由这四种中的其中几种算法相互组合而成,比如说,对堆(heap)的一部分采用标记-清除算法,对堆(heap)的另外一部分则采用复制算法等等。今天我们主要来看下标记-清除算法的原理。

基本概念

在了解标记-清除算法前,我们先要了解几个基本概念。

首先是mutator和collector,这两个名词经常在垃圾收集算法中出现,collector指的就是垃圾收集器,而mutator是指除了垃圾收集器之外的部分,比如说我们应用程序本身。mutator的职责一般是NEW(分配内存),READ(从内存中读取内容),WRITE(将内容写入内存),而collector则就是回收不再使用的内存来供mutator进行NEW操作的使用。

第二个基本概念是关于mutator roots(mutator根对象),mutator根对象一般指的是分配在堆内存之外,可以直接被mutator直接访问到的对象,一般是指静态/全局变量以及Thread-Local变量(在Java中,存储在java.lang.ThreadLocal中的变量和分配在栈上的变量 - 方法内部的临时变量等都属于此类).

第三个基本概念是关于可达对象的定义,从mutator根对象开始进行遍历,可以被访问到的对象都称为是可达对象。这些对象也是mutator(你的应用程序)正在使用的对象。

算法原理

顾名思义,标记-清除算法分为两个阶段,标记(mark)和清除(sweep).

在标记阶段,collector从mutator根对象开始进行遍历,对从mutator根对象可以访问到的对象都打上一个标识,一般是在对象的header中,将其记录为可达对象。

而在清除阶段,collector对堆内存(heap memory)从头到尾进行线性的遍历,如果发现某个对象没有标记为可达对象-通过读取对象的header信息,则就将其回收。

从上图我们可以看到,在Mark阶段,从根对象1可以访问到B对象,从B对象又可以访问到E对象,所以B,E对象都是可达的。同理,F,G,J,K也都是可达对象。到了Sweep阶段,所有非可达对象都会被collector回收。同时,Collector在进行标记和清除阶段时会将整个应用程序暂停(mutator),等待标记清除结束后才会恢复应用程序的运行,这也是Stop-The-World这个单词的来历。

接着我们先看下一般垃圾收集动作是怎么被触发的,下面是mutator进行NEW操作的伪代码:

New():
    ref <- allocate()  //分配新的内存到ref指针
    if ref == null
       collect()  //内存不足,则触发垃圾收集
       ref <- allocate()
       if ref == null
          throw "Out of Memory"   //垃圾收集后仍然内存不足,则抛出Out of Memory错误
          return ref

atomic collect():
    markFromRoots()
    sweep(HeapStart,HeapEnd)

而下面是对应的mark算法:

markFromRoots():
    worklist <- empty
    for each fld in Roots  //遍历所有mutator根对象
        ref <- *fld
        if ref != null && isNotMarked(ref)  //如果它是可达的而且没有被标记的,直接标记该对象并将其加到worklist中
           setMarked(ref)
           add(worklist,ref)
           mark()
mark():
    while not isEmpty(worklist)
          ref <- remove(worklist)  //将worklist的最后一个元素弹出,赋值给ref
          for each fld in Pointers(ref)  //遍历ref对象的所有指针域,如果其指针域(child)是可达的,直接标记其为可达对象并且将其加入worklist中
          //通过这样的方式来实现深度遍历,直到将该对象下面所有可以访问到的对象都标记为可达对象。
                child <- *fld
                if child != null && isNotMarked(child)
                   setMarked(child)
                   add(worklist,child)

在mark阶段结束后,sweep算法就比较简单了,它就是从堆内存起始位置开始,线性遍历所有对象直到堆内存末尾,如果该对象是可达对象的(在mark阶段被标记过的),那就直接去除标记位(为下一次的mark做准备),如果该对象是不可达的,直接释放内存。

sweep(start,end):
    scan <- start
   while scan < end
       if isMarked(scan)
          setUnMarked(scan)
      else
          free(scan)
      scan <- nextObject(scan)

缺点

标记-清除算法的比较大的缺点就是垃圾收集后有可能会造成大量的内存碎片,像上面的图片所示,垃圾收集后内存中存在三个内存碎片,假设一个方格代表1个单位的内存,如果有一个对象需要占用3个内存单位的话,那么就会导致Mutator一直处于暂停状态,而Collector一直在尝试进行垃圾收集,直到Out of Memory。

时间: 2024-10-18 16:28:05

JVM--标记-清除算法Mark-Sweep的相关文章

GC标记-清除算法(Mark Sweep GC)

世界上第一个GC算法,由 JohnMcCarthy 在1960年发布. 标记-清除算法由标记阶段和清除阶段构成. 标记阶段就是把所有的活动对象都做上标记的阶段. 标记阶段就是"遍历对象并标记"的处理过程. 标记阶段经常用到深度优先搜索. 清除阶段就是把那些没有标记的对象,也就是非活动对象回收的阶段. 清除阶段collector会遍历整个堆,回收没有打上标记的对象(即垃圾). 内存的合并操作也是在清除阶段进行的. 分配 分配指将回收的内存空间进行再利用. -> 伪代码实现内存分配

CMS为什么采用“标记-清除”算法

分代式GC里,年老代常用mark-sweep:或者是mark-sweep/mark-compact的混合方式,一般情况下用mark-sweep,统计估算碎片量达到一定程度时用mark-compact.这是因为传统上大家认为年老代的对象可能会长时间存活且存活率高,或者是比较大,这样拷贝起来不划算,还不如采用就地收集的方式.Mark-sweep.mark-compact.copying这三种基本算法里,只有mark-sweep是不移动对象(也就是不用拷贝)的,所以选用mark-sweep. 简要对比

标记清除算法

function mark(p, level){ if(level >3){ return ; } if(typeof p == "object" && p != null && p.marked == undefined){ p.marked = true; console.log(p); for(var prop in p){ mark(p[prop], level+1); } } } var g1={name:"xiaoming&q

图解系列之垃圾收集标记-清除算法

原文地址:http://blog.51cto.com/4837471/2324571

JVM垃圾收集算法(标记-清除、复制、标记-整理)

 [JVM垃圾收集算法] 1)标记-清除算法: 标记阶段:先通过根节点,标记所有从根节点开始的对象,未被标记的为垃圾对象(错了吧?) 清除阶段:清除所有未被标记的对象 2)复制算法: 将原有的内存空间分为两块,每次只使用一块,在垃圾回收时,将正在使用的内存中的存活对象复制到未被使用的内存块中,然后清除正在使用的内存块中的所有对象. 3)标记-整理算法: 标记阶段:先通过根节点,标记所有从根节点开始的可达对象,未被标记的为垃圾对象 整理阶段:将所有的存活对象压缩到内存的一段,之后清理边界所有的空间

jvm垃圾收集(标记-清除,复制,标记-整理,分代)算法

1. 标记 - 清除算法  标记清除算法是最基础的收集算法,其他收集算法都是基于这种思想.标记清除算法分为"标记"和"清除"两个阶段:首先标记出需要回收的对象,标记完成之后统一清除对象. 它的主要缺点:①.标记和清除过程效率不高 . ②.标记清除之后会产生大量不连续的内存碎片. . 2. 复制算法 它将可用内存容量划分为大小相等的两块,每次只使用其中的一块.当这一块用完之后,就将还存活的对象复制到另外一块上面,然后在把已使用过的内存空间一次理掉.这样使得每次都是对其

Jvm(24),回收策略-----标记整理算法

标记/整理算法 标记/整理算法与标记/清除算法非常相似,它也是分为两个阶段:标记和整理.下面LZ 给各位介绍一下这两个阶段都做了什么. 标记:它的第一个阶段与标记/清除算法是一模一样的,均是遍历GC Roots,然后将存活的对象标记. 整理:移动所有存活的对象,且按照内存地址次序依次排列,然后将末端内存地址以后的内存全部回收.因此,第二阶段才称为整理阶段. 它GC前后的图示与复制算法的图非常相似,只不过没有了活动区间和空闲区间的区别,而过程又与标记/清除算法非常相似,我们来看GC前内存中对象的状

JVM(五)-垃圾收集算法和垃圾收集器

一.垃圾收集算法 (1)标记-清除算法 "标记-清除"(Mark-Sweep)算法,如它的名字一样,算法分为"标记"和"清除"两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收掉所有被标记的对象.之所以说它是最基础的收集算法,是因为后续的收集算法都是基于这种思路并对其缺点进行改进而得到的. 它的主要缺点有两个:一个是效率问题,标记和清除过程的效率都不高:另外一个是空间问题,标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致,

JVM垃圾回收算法和垃圾收集器笔记

概述 程序计数器,本地方法栈,虚拟机栈随线程而生,随线程而灭. Java堆和方法区则不一样,这部分内存分配和回收的都是动态的,垃圾收集器所关注的是这部分内存. 判断对象是否是垃圾的算法 JVM没有选用引用计数算法来管理内存,最主要的是引用计数很难解决对象之间相互循环引用的问题.JVM采用了可达性分析算法来判断对象是否能回收.用GC Roots对象作为起点,向下搜索,搜索走过的路劲称为引用链(reference chain),当对一个对象到GC Roots没有任何引用链时,则证明对象不可用的. 判