Java JVM 2:垃圾收集算法 - 标记整理算法(伪代码实现与深入分析)
2017-04-09 19:52
856 查看
算法原理
标记整理法首先需要标记出存活的对象,然后所谓的整理就是把这些存活的对象往一端推。然后就清除边界以外的区域即可。
老年代的垃圾回收器(例如 Serial Old,Parallel Old,到那时不包括CMS,CMS使用的是标记清除法)都是采用这个算法,主要由于老年代的对象都比较持久,不是短暂的。
这样一看,每次整理,将不会产生内存碎片问题,因为也没有分配对象需要查空闲链表了。
伪代码实现
只要涉及到对象的移动,都会存在一个问题,就是假如 A -> B(A 引用 B,A 移动后的对象为 A’,B 移动后的对象为 B’,),那么怎么保证 A’-> B’。 其实这里还是利用了一个对象的属性 forwarding(参考:Java JVM 3:垃圾收集算法 - 复制算法(伪代码实现与深入分析))。
forwarding 的意思就是 它是 A对象的一个属性,然后记录了 A 对象移动到 A’。forwarding 会记录这个 A’,但是实际上还没移动,就是先记录好。
整个过程分 3 步:
首先,需要设置这个 forwarding,用于知道 A 后面移动到 A’,B 后面移动到 B’,这样才有可能把 A’ -> B’ 关联上。
这段代码感觉还算比较好理解的,也就是记录自己移动后是到哪个位置:scan.forwarding = new_address。(注意,这里只是预先记录一下,还没有真正开始移动)
步骤二,更新指针:
这段代码主要说的就是重写 A’ 对象移动后指向哪个对象。例如 *child = (*child).forwarding ,这里可以看到 A原来是引用B,(*child).forwarding 表示 B 到时候会变成 B’,此时需要把 child 改成以后的 B’ ,那么,此时在 A对象中,已经记录了之后A会变到 A’(forwarding指针记录了),然后上面那个代码又知道了引用的 B 对象以后要变到哪里去,所以说,最后移动的时候,A’ -> B’就很容易关联上。
步骤三:真正的移动
这段主要就是先 copy:copy_data(new_address,scan,scan,size) ,然后位置一直往前挪动:$free += new_address.size
优缺点
耗费的时间在哪里
1、系统怎么知道A对象后来去到 A’ ,那么,这个是需要遍历整个堆的。比如说,发现第一个位置需要GC了,然后 A 不需要,那么就把 A.forwarding 设置到第一个位置这里。所以这里要遍历一遍堆。
2、设置 A’ -> B’ 的关联。这个从上面代码实现来看也要遍历一遍堆:while(scan < $heap_end)。但是个人表示怀疑,因为根据 forwarding 知道了 A 变到 A’ ,也能由A得到 B,再由 B 的 forwarding 可以得到 B’,所以感觉这里应该可以设置 A’ -> B’。
3、移动对象,这个是肯定要遍历一遍堆的。因为涉及到移动,清除。
所以总的来说,这个算法的效率无论比起复制算法还是标记清除法,遍历的次数都是要多一点。
没有内存碎片问题
所以说分配内存的时候也不需要到空闲链表这个东西
标记整理法首先需要标记出存活的对象,然后所谓的整理就是把这些存活的对象往一端推。然后就清除边界以外的区域即可。
老年代的垃圾回收器(例如 Serial Old,Parallel Old,到那时不包括CMS,CMS使用的是标记清除法)都是采用这个算法,主要由于老年代的对象都比较持久,不是短暂的。
这样一看,每次整理,将不会产生内存碎片问题,因为也没有分配对象需要查空闲链表了。
伪代码实现
只要涉及到对象的移动,都会存在一个问题,就是假如 A -> B(A 引用 B,A 移动后的对象为 A’,B 移动后的对象为 B’,),那么怎么保证 A’-> B’。 其实这里还是利用了一个对象的属性 forwarding(参考:Java JVM 3:垃圾收集算法 - 复制算法(伪代码实现与深入分析))。
forwarding 的意思就是 它是 A对象的一个属性,然后记录了 A 对象移动到 A’。forwarding 会记录这个 A’,但是实际上还没移动,就是先记录好。
整个过程分 3 步:
首先,需要设置这个 forwarding,用于知道 A 后面移动到 A’,B 后面移动到 B’,这样才有可能把 A’ -> B’ 关联上。
set_forwarding_ptr(){ scan = new_address = $heap_start while(scan < $heap_end){ if(scan.mark == TRUE){ scan.forwarding = new_address new_address += scan.size } scan += scan.size } }
这段代码感觉还算比较好理解的,也就是记录自己移动后是到哪个位置:scan.forwarding = new_address。(注意,这里只是预先记录一下,还没有真正开始移动)
步骤二,更新指针:
adjust_ptr(){ for(r : $roots){ *r = (*r).forwarding } scan = $heap_start while(scan < $heap_end){ if(scan.mark == TRUE){ for(child : children(scan)){ *child = (*child).forwarding } } scan += scan.size } }
这段代码主要说的就是重写 A’ 对象移动后指向哪个对象。例如 *child = (*child).forwarding ,这里可以看到 A原来是引用B,(*child).forwarding 表示 B 到时候会变成 B’,此时需要把 child 改成以后的 B’ ,那么,此时在 A对象中,已经记录了之后A会变到 A’(forwarding指针记录了),然后上面那个代码又知道了引用的 B 对象以后要变到哪里去,所以说,最后移动的时候,A’ -> B’就很容易关联上。
步骤三:真正的移动
move_obj(){ sacn = $free = $heap_start while(scan < $heap_end){ if(scan.mark == TRUE){ new_address = scan.forwarding copy_data(new_address,scan,scan,size) new_address.forwarding = NULL new_address.mark = FALSE $free += new_address.size scan += scan.size } } }
这段主要就是先 copy:copy_data(new_address,scan,scan,size) ,然后位置一直往前挪动:$free += new_address.size
优缺点
耗费的时间在哪里
1、系统怎么知道A对象后来去到 A’ ,那么,这个是需要遍历整个堆的。比如说,发现第一个位置需要GC了,然后 A 不需要,那么就把 A.forwarding 设置到第一个位置这里。所以这里要遍历一遍堆。
2、设置 A’ -> B’ 的关联。这个从上面代码实现来看也要遍历一遍堆:while(scan < $heap_end)。但是个人表示怀疑,因为根据 forwarding 知道了 A 变到 A’ ,也能由A得到 B,再由 B 的 forwarding 可以得到 B’,所以感觉这里应该可以设置 A’ -> B’。
3、移动对象,这个是肯定要遍历一遍堆的。因为涉及到移动,清除。
所以总的来说,这个算法的效率无论比起复制算法还是标记清除法,遍历的次数都是要多一点。
没有内存碎片问题
所以说分配内存的时候也不需要到空闲链表这个东西
相关文章推荐
- Java JVM 3:垃圾收集算法 - 复制算法(伪代码实现与深入分析)
- Java JVM 1:垃圾收集算法 - 标记清除算法(伪代码实现与深入分析)
- 深入理解JVM--第三章--垃圾收集算法(“标记-清除”,“复制”,“标记-整理”,“分代收集”)
- jvm垃圾回收算法(并发垃圾收集器(CMS)为什么没有采用标记整理-算法来实现,而是采用的标记-清除算法?)
- 《深入理解 Java 虚拟机》- 笔记 - HotSpot 的垃圾收集算法实现
- jvm垃圾收集(标记-清除,复制,标记-整理,分代)算法
- JVM垃圾收集算法(标记-清除、复制、标记-整理)
- JVM--jvm垃圾收集(标记-清除,复制,标记-整理,分代)算法
- jvm垃圾收集(标记-清除,复制,标记-整理,分代)算法
- 并发垃圾收集器(CMS)为什么没有采用标记-整理算法来实现?
- jvm垃圾收集(标记-清除,复制,标记-整理,分代)算法
- 【深入理解JVM】:垃圾收集算法
- JVM笔记3:Java垃圾收集算法与垃圾收集器
- 深入学习Java JVM - 垃圾收集器与内存分配策略
- JVM 深入笔记(3)垃圾标记算法
- 深入理解JVM : Java垃圾收集器
- 深入理解JVM:垃圾收集算法
- JVM笔记3:Java垃圾收集算法与垃圾收集器
- JVM中内存回收深入分析,各种垃圾收集器
- Java虚拟机(三):垃圾收集方式-分代,复制,标记整理算法