1.内存泄漏:(memory leak)指程序在申请内存后,无法释放已申请的内存空间,一次内存泄漏似乎不会有大的影响,但内存泄漏堆积后的后果就是内存溢出。
2.内存溢出:(out of memory)指程序申请内存时,没有足够的内存供申请者使用,或者说,给了你一块存储int类型数据的存储空间,但是你却存储long类型的数据,那么结果就是内存不够用,此时就会报错OOM,即所谓的内存溢出。
3.二者的关系
内存泄漏的堆积最终会导致内存溢出;
内存溢出就是你要的内存空间超过了系统实际分配给你的空间,此时系统相当于没法满足你的需求,就会报内存溢出的错误;
内存泄漏是指你向系统申请分配内存进行使用(new),可是使用完了以后却不归还(delete),结果你申请到的那块内存你自己也不能再访问(也许你把它的地址给弄丢了),而系统也不能再次将它分配给需要的程序;
内存溢出:一个盘子用尽各种方法只能装4个果子,你装了5个,结果掉倒地上不能吃了。这就是溢出;
4.内存泄漏的分类
常发性内存泄漏
偶发性内存泄漏
一次性内存泄漏
隐式内存泄漏
5.内存溢出的原因及解决方法:
内存溢出原因:
内存中加载的数据量过于庞大,如一次从数据库取出过多数据;
集合类中有对对象的引用,使用完后未清空,使得JVM不能回收;
代码中存在死循环或循环产生过多重复的对象实体;
使用的第三方软件中的BUG;
启动参数内存值设定的过小
内存溢出的解决方案:
第一步,修改JVM启动参数,直接增加内存。(-Xms,-Xmx参数一定不要忘记加。)
第二步,检查错误日志,查看“OutOfMemory”错误前是否有其 它异常或错误。
第三步,对代码进行走查和分析,找出可能发生内存溢出的位置。
如何检测内存泄漏
最好是使用各种内存泄漏检测工具,还有一种简单的方法是重载new和delete,当使用new的时候就记录这块空间的信息,delete了就删除这个信息。
溢出的部分有可能会覆盖到你的其他有用的数据上去。有一种网络攻击手段就叫做缓冲区溢出攻击就是这个原理。
https://www.cnblogs.com/bastard/archive/2011/12/12/2285296.html
智能指针 (存放于栈上)
不论程序运行发送什么意外情况,一定会帮用户把资源释放掉
—> 一定会发生浅拷贝
(如何区分深拷贝与浅拷贝?简单点来说,就是假设B复制了A,当修改A时,看B是否会发生变化,如果B也跟着变了,说明是浅拷贝,如果B没变,那就是深拷贝。深入点来说,就是B复制了A,如果B复制的是A的引用,那就是浅拷贝,如果B复制的是A的本体,那就是深拷贝。)
1.不带引用计数的智能指针
eg: auto_ptr scope_ptr unique_ptr
2.带引用计数的智能指针
(1)shared_ptr
强智能指针 --> 可以改变资源的引用计数
当有一个指针指向这块内存区域的时候,引用计数器+1
当指向这块内存取得指针销毁的时候,引用计数-1
如果没有任何指针指向这块区域,引用计数=0,将内存释放
(2)weak_ptr
弱智能指针 --> 不可以改变资源的引用计数
他可以通过weak_ptr的构造函数或者lock成员函数转化成share_ptr
作用:强智能指针循环/交叉引用(计数器的值始终不为0)会导致资源无法释放
weak_ptr可以打破对象的循环引用
share_ptr控制对象的生命期。share_ptr是强引用(铁丝绑住堆上的对象)
指向对象x的share_ptr存在,对象就不会析构。当最后一个指向x的share_ptr析构的时候,x保证会被销毁。
weak_ptr不控制对象的生命期,但他知道对象是否活着(棉线轻轻拴住对象)。如果对象活着,它提升为share_ptr;如果对象死了,提升失败,返回一个空的share_ptr。“提升的行为是线程安全的”
此时p1就成了悬空指针
(危害:访问悬空指针,结果随机访问。可能导致功能不正常,也可能导致程序奔溃。)—>所以就有了带引用计数的智能指针
解决悬空指针:引入一层间接性,让p1和p2所指的对象永久有效
引入中间层Proxy,让Proxy对象(持有object指针)去维护这个对象:
当销毁obj之后,proxy继续存在, 其值变为0
p2也没有变成悬空指针,它可以通过查看proxy的内容来判断obj是否还活着
要想线程安全地释放proxy也不容易,因为资源可能还在竞争。
eg:p1第一眼的时候proxy还不为0,正准备调用obj的成员函数,期间对象已经被p2给销毁了。
何时释放proxy?
引入引用计数—>把p1和p2从指针变成对象sp1和sp2。
proxy现在有两个成员,指针和计数器