几种垃圾回收机制

参考
http://www.2cto.com/kf/201110/108419.html

http://www.cnblogs.com/nele/p/5673215.html


1.引用计数算法

引用计数(Reference Counting)算法是每个对象计算指向它的指针的数量,当有一个指针指向自己时计数值加1;当删除一个指向自己的指针时,计数值减1,如果计数值减为0,说明已经不存在指向该对象的指针了,所以它可以被安全的销毁了。

引用计数的明显缺点:无法处理环形引用

class B;

class A{
private:
	std::shared_ptr<B> _b;  
public:
	void setB(std::shared_ptr<B> b){ _b = b; }

	~A(){ std::cout << "~A()" << std::endl; }
};

class B{
private:
	std::shared_ptr<A> _a;
public:
	void setA(std::shared_ptr<A> a){ _a = a; }

	~B(){ std::cout << "~B()" << std::endl; }
};

int main(int argc, char** argv){
	std::shared_ptr<A> sa(new A());
	std::shared_ptr<B> sb(new B());

#if 1
	if ( sa && sb) {
		sa->setB(sb);
		sb->setA(sa);
	}
#endif

	return 0;
}

自己实现shared_ptr, 转自
http://www.cnblogs.com/runnyu/p/5822304.html

template<class T>

class MShare_ptr
{
private:
	T* _ptr; // 类型指针
	int* count; // 引用计数

public:
	// 构造函数
	MShare_ptr(T* p) : count(new int(1)), _ptr(p){};

	// 拷贝构造函数,需要增减引用计数
	MShare_ptr(MShare_ptr<T>& other) 
		: count(&(++*other.count)), _ptr(other._ptr) {}


	// 析构函数(当离开作用域时,会将引用计数减1,如果引用计数最终为0,则需要释放内存)
	~MShare_ptr()
	{
		if (--*count == 0)
		{
			delete count;
			delete _ptr;

			std::cout << "~MShare_ptr" << std::endl;
		}
	}

	int getRef() { return *count; }

public:
	T* operator->() { return _ptr; }
	T& operator*() { return *_ptr; }

	MShare_ptr<T>& operator=(MShare_ptr<T>& other)
	{
		++*other.count;
		if (this->_ptr && 0 == --*this->count)
		{
			delete count;
			delete _ptr;
		}
		this->_ptr = other._ptr;
		this->count = other.count;
		return *this;
	}
};

算法特点

  1. 需要单独的字段存储计数器,增加了存储空间的开销;

  2. 每次赋值都需要更新计数器,增加了时间开销;

  3. 垃圾对象便于辨识,只要计数器为0,就可作为垃圾回收;

  4. 及时回收垃圾,没有延迟性;

  5. 不能解决循环引用的问题;

2. 标记-清除(Mark-Sweep)算法

例如: Lua就采用了mark-sweep的垃圾回收机制

标记-清除(Mark-Sweep)算法依赖于对所有存活对象进行一次全局遍历来确定哪些对象可以回收,遍历的过程从根出发,找到所有可达对象,除此之外,其它不可达的对象就是垃圾对象,可被回收。整个过程分为两个阶段:标记阶段找到所有存活对象;清除阶段清除所有垃圾对象。

在这里插入图片描述

在这里插入图片描述

优点

  • 相比较引用计数算法,标记-清除算法可以非常自然的处理环形引用问题,

  • 另外在创建对象和销毁对象时时少了操作引用计数值的开销

缺点

  • 标记-清除算法是一种“停止-启动”算法,在垃圾回收器运行过程中,应用程序必须暂时停止

  • 标记-清除算法在标记阶段需要遍历所有的存活对象,会造成一定的开销

  • 在清除阶段,清除垃圾对象后会造成大量的内存碎片。

3. 标记-缩并(Mark-Compact)算法(解决内存碎片)

整个过程可以描述为

  • 标记所有的存活对象;
  • 通过重新调整存活对象位置来缩并对象图;
  • 更新指向被移动了位置的对象的指针。

标记-压缩算法最大的难点在于如何选择所使用的压缩算法,如果压缩算法选择不好,将会导致极大的程序性能问题,如导致Cache命中率低等。一般来说,根据压缩后对象的位置不同,压缩算法可以分为以下三种:

  1. 任意:移动对象时不考虑它们原来的次序,也不考虑它们之间是否有互相引用的关系。

  2. 线性:尽可能的将原来的对象和它所指向的对象放在相邻位置上,这样可以达到更好的空间局部性。

  3. 滑动:将对象“滑动”到堆的一端,把存活对象之间的自由单元“挤出去”,从而维持了分配时的原始次序。

这里写图片描述

4. 节点拷贝(Copying)算法(解决内部碎片)

节点拷贝算法是把整个堆分成两个半区(From,To), GC的过程其实就是把存活对象从一个半区From拷贝到另外一个半区To的过程,而在下一次回收时,两个半区再互换角色。在移动结束后,再更新对象的指针引用

参考
http://blog.csdn.net/sinat_36246371/article/details/53002209

在这里插入图片描述

简单,高效,空间换时间。


5 Generational Collection(分代收集)算法

分代收集算法是目前大部分JVM的垃圾收集器采用的算法。它的核心思想是根据对象存活的生命周期将内存划分为若干个不同的区域。一般情况下将堆区划分为老年代(Tenured Generation)和新生代(Young Generation),老年代的特点是每次垃圾收集时只有少量对象需要被回收,而新生代的特点是每次垃圾回收时都有大量的对象需要被回收,那么就可以根据不同代的特点采取最适合的收集算法。

目前大部分垃圾收集器对于新生代都采取Copying算法,因为新生代中每次垃圾回收都要回收大部分对象,也就是说需要复制的操作次数较少,但是实际中并不是按照1:1的比例来划分新生代的空间的,一般来说是将新生代划分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden空间和其中的一块Survivor空间,当进行回收时,将Eden和Survivor中还存活的对象复制到另一块Survivor空间中,然后清理掉Eden和刚才使用过的Survivor空间。

而由于老年代的特点是每次回收都只回收少量对象,一般使用的是Mark-Compact算法。

注意,在堆区之外还有一个代就是永久代(Permanet Generation),它用来存储class类、常量、方法描述等。对永久代的回收主要回收两部分内容:废弃常量和无用的类。

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值