当前位置 : 主页 > 网络编程 > lua >

常见垃圾回收方法

来源:互联网 收集:自由互联 发布时间:2021-06-23
1、标记清除法(Mark And Sweep) 第一步:从根部出发,遍历全局,然后对所有可达的对象进行标记 第二步:对所有未标记的对象进行清除 优点:方法简单,速度较快。缺点:容易产生较

1、标记清除法(Mark And Sweep)

第一步:从根部出发,遍历全局,然后对所有可达的对象进行标记

第二步:对所有未标记的对象进行清除

优点:方法简单,速度较快。缺点:容易产生较多的内存碎片。

采用这种方式的语言:lua等

2、标记整理回收(mark-compact)

第一步和标记清除法一样,标记所有可达对象

第二步将未标记的对象清除,同时将现有对象的空间合并

优点:没有内存碎片。缺点:合并空间的时候,引用该对象的所有线程都会被挂起,合并完成后才会重新执行。

采用这种方式的语言:c#等

3、标记复制回收(mark-copy)

复制算法开辟了两个相等的空间,每次只使用其中的一块空间

第一步标记

第二步将所有标记过的对象,复制到另一块空间,当复制完成后,指向原有对象的指针指向新的对象。全部复制完成后,释放原有的空间。

优点:没有内存碎片,不会gc ,效率高   缺点:需要额外的内存空间

采用这种方式的语言:java的新生代

4、引用计数算法(reference counting)

对象每次被引用的时候对引用次数加1,每次被引用对象被删除时,则对引用次数减1,当引用计数为0时,则删除对象。

优点:迅速,每次当对象引用次数为0时,则马上就会被清除。无需系统支持,去确定程序的根。

缺点:

  1、计数赋值器带来额外的开销。所以不适合通用的大容量的内存管理器。

  2、多线程的程序中,可能释放过早。引用计数的存储指针操作是原子化的,并发线程却同时进行读取和修改,开发者要避免更新指针槽过程中出现的竞争问题。

  3、对单个对象的简单操作也会引发内存请求(更新引用次数),会“污染”高速缓存

  4、无法解决循环引用问题

  5、有可能卡顿,当删除一个大的根节点的时候,需要去递归删除每一个子孙节点。

循环引用的解决方法:

  1、定期用标记算法作为补充处理

  2、设为强引用和弱引用,把可能产生环的引用设为弱引用,所有强引用可达且不成环,当强引用次数为0时,删除对象(这种方法为了一些安全性原因,性能开销大,只有少数语言使用)

    c++的智能指针的弱引用和这种算法的弱引用不一样,c++的弱引用只能确定是否可达,主要是为了避免非法访问。

  3、部分跟踪算法,循环引用指针出现有2个条件:

    (1)环状指针内部,所有引用对象都有内部对象指针产生

    (2)如果删除某一对象后,引用计数仍然为0,则说明产生了环状

    扫描对象,如果一个对象的所有引用都是循环引用,则进行处理。临时移除对目标对象的引用次数,从而移除内部指针的引用次数,如果目标对象引用计数仍然大于0,则说明存在外部引用,否则一起处理掉。

计数回收的语言有:python等

5、分代算法

将内存分为几个区域,不同状态的对象放进不同的区域里,对每个区域采取不同的垃圾回收策略,可以兼顾优点,但是比较复杂。

采用分代回收的语言:java等

java将内存分成了新生代、年老代和永久代

新生代:新生代用标记复制回收,因为绝大部分创建的对象都是临时用的,很快会被回收掉,同时为了提高性能,和适合用复制回收,复制回收的两块区域大小是9:1。

年老代:当在新生代里复制一定次数还没有被回收以后,则放到年老代里,年老代采用复制标记回收。

永生代:当在年老代理一定时间没有被回收,则放入永生代,永生代采用复制整理回收。

分成的好处针对不同性质的对象,采用不同的处理方式。复制整理回收的回收效果好,但是整理过程中会造成gc,所以用了两层过渡,减少复制整理的发生。

网友评论