无论在哪一种系统平台/编程语言下,内存资源管理是很是重要的问题,稍不留意就会致使内存泄漏,更有甚者访问非法空间,致使错误。说到底,没有在合适的时机释放对象,或者访问了已经释放的资源。在有垃圾回收的语言中,由平台环境负责资源的及时回收;在C++中则须要程序员本身把握,在一些多线程状态下,对象资源的释放时机经常很差把握,致使了各类各样的问题。为何你们喜欢用带有GC功能的语言作开发,是由于少了太多的心智负担(JAVA, C#, Python ...)。c++
针对原生指针的管理方式,学校老师经常这么教:谁建立、谁释放。要配对,不过现实每每没那么理想,在单线程处理程序中这样没有问题,可是在多线程中有时候作不到。我如今一般的作法是作一个簿记工做,对于系统中大量使用的对象资源,尤为是跨线程使用的,会集中管理登记,并配以状态标记,确认对象使用完毕后再行释放。对象自身跟随业务状态变化,有明确的开始和结束状态。git
在C++ 中有多种类型的智能指针,有些被重用,而有些却被放弃,在陈硕的《Linux多线程服务端编程》中,推荐使用shared_ptr以及weak_ptr进行资源管理。此处梳理一下C++中的智能指针。看看各自如何使用,适合在什么场景下使用。程序员
unique_ptr负责独占对应对象的全部权,一旦unique_ptr析构,那么对应对象自动销毁,unique_ptr对应对象的控制权能够转移,但不可拷贝,具备独占性。可经过实例查看。github
class A { public: A() { std::cout << "A construct." << std::endl; a = 10; b = 11; } ~A() { std::cout << "A destruct." << std::endl; } void func() { std::cout << "A::func()" << std::endl; std::cout << a << " " << b << std::endl; } private : int a; int b; }; void unique_ptr_test() { { std::unique_ptr<A> up1(new A()); up1->func(); { std::unique_ptr<A> up2(std::move(up1)); std::cout << "control from up1 to up2." << std::endl; up2->func(); //up1->func(); 此处执行会报错,up1为empty up1 = std::move(up2); std::cout << "return control from up2 to up1" << std::endl; up1->func(); } } { A* as = new A[5]; std::unique_ptr<A[]> pas(as); } }
A construct. A::func() 10 11 control from up1 to up2. A::func() 10 11 return control from up2 to up1 A::func() 10 11 A destruct. A construct. A construct. A construct. A construct. A construct. A destruct. A destruct. A destruct. A destruct. A destruct.
这个测试用例主要用来讲明几个问题:算法
shared_ptr和weak_ptr是一对组合。shared_ptr是计数型智能指针,属于强引用,每个关联都有计数,而weak_ptr是弱引用,不会影响计数功能。编程
shared_ptr独立测试数组
void shared_ptr_test() { std::shared_ptr<A> sp1(new A()); std::cout << sp1.use_count() << std::endl; { std::shared_ptr<A> sp2(sp1); std::cout << sp2.use_count() << std::endl; std::shared_ptr<A> sp3(sp2); std::cout << sp3.use_count() << std::endl; { std::shared_ptr<A> sp4(sp3); std::cout << sp4.use_count() << std::endl; } } } A construct. 1 2 3 4 A destruct.
weak_ptr独立测试 在微软 MSDN上看到以下一段话:多线程
The template class describes an object that points to a resource that is managed by one or more shared_ptr Class objects. 该模板类用于描述一个对象,该对象已经由一个或者多个shared_ptr对象管理控制。 The weak_ptr objects that point to a resource do not affect the resource's reference count. weak_ptr对象指向一个资源,不会影响该资源的引用计数。 Thus, when the last shared_ptr object that manages that resource is destroyed the resource will be freed, even if there are weak_ptr objects pointing to that resource. This is essential for avoiding cycles in data structures. 当最后一个指向资源的shared_ptr对象析构后,资源被释放,即便还有weak_ptr指向该资源。该方法经常使用于避免循环引用。 void weak_ptr_test() { { std::shared_ptr<A> sp1(new A()); std::weak_ptr<A> wp1(sp1); std::cout << "shared use count: " << sp1.use_count() << std::endl; std::cout << "weak_ptr use count: " << wp1.use_count() << std::endl; { std::shared_ptr<A> sp2(new A()); std::weak_ptr<A> wp2(sp1); std::cout << "shared use count: " << sp2.use_count() << std::endl; std::cout << "weak_ptr use count: " << wp2.use_count() << std::endl; } } } A construct. shared use count: 1 weak_ptr use count: 1 A construct. shared use count: 1 weak_ptr use count: 1 A destruct. A destruct.
经过示例能够看到,即便有weak_ptr指向对象,也只显示shared_ptr强类型智能指针的指向数量。编程语言
weak_ptr的一个做用是能够有效判断某个对象是否还存活,示例以下:测试
void shared_weak_ptr_test() { std::weak_ptr<A> wp1; { std::shared_ptr<A> sp1(new A()); wp1 = sp1; std::cout << "shared use count: " << sp1.use_count() << std::endl; std::cout << "weak_ptr use count: " << wp1.use_count() << std::endl; } if (wp1.lock() != nullptr) { std::cout << "resource exists." << std::endl; } else { std::cout << "resource not exists." << std::endl; } } A construct. shared use count: 1 weak_ptr use count: 1 A destruct. resource not exists.
能够看到,经过weak_ptr进行lock,若是资源存在,那么能够转型为shared_ptr, 若是资源不存在,那么返回的就是nullptr,这经常能够用于多线程程序中判断某个对象是否有效。
shared_ptr和weak_ptr联合测试 为何shared_ptr和weak_ptr老是联合使用呢,在陈硕《Linux多线程服务端编程》中用了一个很是生动的示例进行说明,就是观察者模式。当被观察者发生某个事件,须要通知多个观察者时,每每是经过指针依次调用。这边存在的一个问题就是,若是某个观察者在其余线程中被删除,所指对象已经被删除,那么在调用方法时,就会出现问题。由于对象已经无效。我本身编写了一个简易示例,并未在多线程中运行,可是能够做为说明。
class Observer { public: Observer(int32_t id) : observer_id(id) {} void update() { std::cout << observer_id << " Observer " << std::endl; } private: int32_t observer_id; }; class Observable { public: void notifyall() { std::lock_guard<std::mutex> guard(observable_mutex); for(std::vector<std::weak_ptr<Observer>>::iterator it = observers.begin(); it != observers.end(); ) { std::weak_ptr<Observer> ov = *it; std::shared_ptr<Observer> sp(ov.lock()); if (sp != nullptr) { sp->update(); it++; } else { it = observers.erase(it); } } if (observers.size() == 0) { std::cout << "no observers. ." << std::endl; } } void reg(std::weak_ptr<Observer> ob) { std::lock_guard<std::mutex> guard(observable_mutex); observers.push_back(ob); } private: std::vector<std::weak_ptr<Observer>> observers; std::mutex observable_mutex; }; void observer_test() { std::shared_ptr<Observable> bk; { std::shared_ptr<Observer> ob1(new Observer(1)); std::shared_ptr<Observer> ob2(new Observer(2)); std::shared_ptr<Observer> ob3(new Observer(2)); std::shared_ptr<Observable> obed1(new Observable); obed1->reg(ob1); obed1->reg(ob2); obed1->reg(ob3); obed1->notifyall(); bk = obed1; } bk->notifyall(); } 1 Observer 2 Observer 2 Observer no observers. .
经过上面的示例能够看到,即便注册的观察者已经被释放,被观察者也可正确识别对象的可用性,而不会执行致使core的错误。
cpp还有其余类型的智能指针,好比auto_ptr,不过目前在c++11中并不被推荐使用。 auto_ptr和unique_ptr有些相似,都是表达对资源的惟一全部权,可是区别是,auto_ptr能够经过赋值操做默认转移全部权,而unique_ptr须要显式的表达转移动做。
void auto_ptr_test() { { std::auto_ptr<A> ap(new A()); ap->func(); std::auto_ptr<A> ap2 = ap; ap2->func(); ap->func(); //此处会出现错误,由于ap已经不拥有A对象的资源,在访问对象内部变量的时候,天然会报错 } }
这种经过赋值行为就实现资源转移,确实会让人感到诧异。有一种不告而取的感受。
{ A* a = new A(); std::auto_ptr<A> ap(a); std::auto_ptr<A> ap2(a); }
该段测试代码会致使资源的重复释放问题。
第一次据说C++ 中的垃圾回收器,是在以下知乎中的连接看到。
blink中的垃圾回收器 从通常性考虑来说,Cpp能够很精确的控制内存,也有各类智能指针使用,为何还要有垃圾回收呢。这不是剥夺了Cpp程序员DEBUG的乐趣么。文章解释说由于工程规模大,即便有智能指针等各类技术,仍是避免不了内存泄漏等问题,最后仍是在相应项目下提供一套通用的垃圾回收机制。
源码中使用了不少模板技术,看不懂。
此处内容主要参考书籍《垃圾回收的算法与实现》,说明JVM下的主要垃圾回收办法。
JAVA下的垃圾回收并无采用计数法。由于计数没法解决循环引用的问题。思考下C++中相互引用的两个类,都拥有对方的shared_ptr类型的指针,如何正确释放对象。这也是为啥有weak_ptr的缘由。
JAVA中全部经过new出来的资源对象存放在堆中,相关的引用放在堆栈上。在垃圾回收时,采起可达性分析。经过一系列的“GCRoots”对象做为起点进行搜索,全部可直接或者间接与GC Roots相连的为有效对象,其余则为无效对象;区分开有效、无效对象后,就可进一步处理。
先说几个通用算法。
该算法很好理解,可是将各对象放置到空闲链表中会致使空间不连续,容易致使内存碎片,一些大块内存没法成功申请;同时申请空间会变慢,由于每次遍历空闲列表寻找合适内存空间都要花费时间;
为了不内存碎片的问题,复制算法将堆空间一分为二,当执行回收时,将有效对象复制到另外的空间,而后将原有空间清空便可。可是这样致使堆空间的使用率大大降低。优势是若是大量对象须要回收,那么只须要移动不多一部分存活对象便可完成垃圾回收。
标记整理算法能够称的上是标记清除和复制的综合体,在标记完成后,将全部有效对象都往一端移动,保证堆空间的紧凑。
分代收集算法是目前大部分JVM的垃圾收集器采用的算法。它的核心思想是根据对象存活的生命周期将内存划分为若干个不一样的区域。通常状况下将堆区划分为新生代(Young Generation)和老年代(Tenured Generation)。
新生代特色是每次垃圾回收都有大量的对象须要被回收,只剩下少许有效对象。 老年代的特色是每次垃圾收集只有少许对象须要被回收。 不一样的特色,可使用不一样的垃圾回收算法进行处理,从而提升总体的回收效率。
目前大部分垃圾收集器对于新生代采起复制算法,由于新生代中每次垃圾回收都要回收大部分对象,只有少许有效存活对象,只须要复制少许对象便可完成新生代的垃圾回收。可是实际中并非按照1:1的比例来划分新生代的空间的,而是按照8:1:1,将新生代划分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden空间和其中的一块Survivor空间,当进行回收时,将Eden和Survivor中还存活的对象复制到另外一块Survivor空间中,而后清理掉Eden和刚才使用过的Survivor空间。
因为老年代的特色是每次回收都只回收少许对象,通常使用的是标记压缩算法。
另外还有一个代就是永久代(PermanetGeneration),它用来存储class类、常量、方法描述等。对永久代的回收主要回收两部份内容:废弃常量和无用的类。在Oracle JVM中,永久代并不属于堆空间。
新生代中的S0,S1是轮流使用。这个技巧在不少开发中用到。相似于双缓冲。
在新生代中的年龄达到必定阈值后,会被转移到老年代。
后续会进一步深刻研究各回收算法。