由于時間比較緊張我就不排版了,但是對于每一種可能的情況都會出對應的代碼示例以及解決方案代碼示例。
內存泄漏可能的原因之一在于用戶在動態分配一個內存空間之中,忘記將這部分內容手動釋放。例如:(c++之中使用new分配內存沒有使用delete釋放,或者c語言使用malloc分配內容沒有使用free釋放)
#include<iostream>
using namespace std;
int main(){
?? ?int* num=new int(10);
?? ?return 0;
}
這部分內容沒有對num進行delete內存釋放,導致內存泄漏。
需要注意的是內存泄漏并不是內存直接漏出去,而是指部分應該被回收的內存沒有被回收或者錯誤的被跳過導致沒有正確的回收,導致內存越來越多占據了大量的內存空間,可分配的內存越來越少,影響性能。
如何避免:
通過手動釋放這部分內容:
#include<iostream>
using namespace std;
int main(){
?? ?int* num=new int(10);
?? ?delete num;
?? ?return 0;
}
內存泄漏的原因二:沒有書寫析構函數,導致一部分動態分配的內存沒有被釋放或者析構函數被遺漏調用。例如基類的析構函數沒有被派生類重寫,導致當通過基類指針刪除派生類時,只調用了基類的析構函數,派生類本身的一部分動態分配的內存沒有被釋放,導致一部分內容沒有被正確的釋放,這是析構函數被遺漏時可能發生的情況。解決方法:添加析構函數或者在書寫成虛方法的析構函數對其進行方法重寫。
#include<iostream>
using namespace std;
class Test{
?? ?private:
?? ??? ?int* num;
?? ?public:
?? ??? ?Test(){
?? ??? ??? ?num=new int(10);
?? ??? ?}
?? ??? ?~Test(){
?? ??? ??? ?delete num;
?? ??? ?}
};
int main(){
?? ?Test test;
?? ?return 0;
}
原因三:使用的內存被循環的引用,導致引用計數始終不為0,例如使用智能指針shared_ptr,當兩個類相互的調用對方的智能指針時,引用計數始終不為0,這部分內容不會被正確的釋放。例如:
#include<iostream>
#include<memory>
using namespace std;
class B;
class A{
?? ?public:
?? ??? ?shared_ptr<B> b_ptr;
};
class B{
?? ?public:
?? ??? ?shared_ptr<A> a_ptr;
};
int main(){
?? ?shared_ptr<A> a=make_shared<A>();
?? ?shared_ptr<B> b=make_shared<B>();
?? ?a->b_ptr=b;
?? ?b->a_ptr=a;
?? ?return 0;
}
始終持有對方的智能指針的引用,引用計數始終不清零。
更改建議:可以使用weak_ptr打破循環引用:
例如:
#include<iostream>
#include<memory>
using namespace std;
class B;
class A{
?? ?public:
?? ??? ?shared_ptr<B> b_ptr;
};
class B{
?? ?public:
?? ??? ?weak_ptr<A> a_ptr;
};
int main(){
?? ?shared_ptr<A> a=make_shared<A>();
?? ?shared_ptr<B> b=make_shared<B>();
?? ?a->b_ptr=b;
?? ?b->a_ptr=weak_ptr<A>(a);
?? ?return 0;
}
原因四:程序雖然正確的書寫了delete對內容進行釋放,但是被異常拋出的錯誤跳過了內存釋放,導致內存釋放的部分被跳過,沒有正確的釋放這部分內存空間,舉例說明:
#include<iostream>
#include<stdexcept>
using namespace std;
void func(){
?? ?int* num=new int(10);
?? ?throw runtime_error("Exception");
?? ?delete num;
}
int main(){
?? ?try{
?? ??? ?func();
?? ?}
?? ?catch(const exception& error){
?? ??? ?cout<<error.what()<<endl;
?? ?}
?? ?return 0;
}
這部分由于拋出異常被跳過內存釋放,我們可以使用智能指針unique_ptr,使其在異常拋出之后自動的釋放這一部分內存,就不會發生這種異常,舉例說明:
#include<iostream>
#include<memory>
#include<stdexcept>
using namespace std;
void func(){
?? ?unique_ptr<int> u=make_unique<int>(10);
?? ?throw runtime_error("Exception");
}
int main(){
?? ?try{
?? ??? ?func();
?? ?}
?? ?catch(const exception& error){
?? ??? ?cout<<error.what()<<endl;
?? ?}
?? ?return 0;
}
?? ??? ?
原因5:資源管理對象的生命周期不當,沒有在正確的時機管理釋放內存。
老規矩,作為一名unity開發程序員,我們來思考C#之中存在哪一些內存泄漏(簡單說一下吧,之前在博客之中有詳細的描述)
C#之中如果事件訂閱未被取消也會導致內存泄漏,所以我們說事件的添加和移除應該是成雙成對出現的。另外一些使用lambda表達式的委托無法安全的移除,這時候盡量不要使用lambda表達式防止內存泄漏。另外一個靜態變量無法被內存回收,如果靜態變量引用了一些對象,這部分內容是無法被垃圾回收的。
C#相對于C++的內存泄漏問題比較少,這是由于C#的自動垃圾回收機制,會自動對一部分內存進行回收,大大降低了內存泄漏的風險,對于C++來說錯誤遺漏沒有進行delete釋放或者使用delete釋放之后又使用了已經被釋放的內容會出現懸空指針的問題。
C#的內存泄漏排查也相對于C++來說比較容易。不過,在使用非托管資源(如文件、網絡連接等)時,仍需要手動管理資源的釋放。