C語(yǔ)言的內(nèi)存泄露
1 內(nèi)存泄露的常見(jiàn)原因
1)指針的重新賦值
char *a = (char *)malloc(10)
char *b = (char *)malloc(10)
a = b
### 指針a和b都分配了10個(gè)字節(jié)的內(nèi)存鬓照,指針a被b重新賦值,
導(dǎo)致之前分配給a的內(nèi)存變成了孤立內(nèi)存,因沒(méi)有指向該位置的引用劣针,所以無(wú)法被釋放拿撩。
2)錯(cuò)誤的內(nèi)存釋放
char *a = (char *)malloc(10)
free(a) ##錯(cuò)誤
##正確
free(a->b)
free(a)
假設(shè) 此時(shí)a的中間第二個(gè)字節(jié)位置又指向新的內(nèi)存地址b。若直接釋放a指針垦细,會(huì)導(dǎo)致b內(nèi)存地址變成孤立的內(nèi)存择镇。
3)返回值不正確的處理
char *f() {
return (char *) malloc(10)
}
void f1() {
f()
### 此時(shí)f1 調(diào)用了f,但是并沒(méi)有使用該內(nèi)存地址括改,導(dǎo)致該內(nèi)存地址變成孤立內(nèi)存腻豌,無(wú)法釋放。
}
2 避免內(nèi)存泄露的常見(jiàn)方法。
1 確保沒(méi)有訪(fǎng)問(wèn)空指針
2 每一個(gè)malloc都有對(duì)應(yīng)的free
3 每當(dāng)向指針寫(xiě)入值時(shí)吝梅,都要確保對(duì)可用字節(jié)數(shù)和所寫(xiě)入的字節(jié)數(shù)進(jìn)行交叉核對(duì)
4 在對(duì)指針賦值前虱疏,一定要確保沒(méi)有內(nèi)存位置會(huì)變?yōu)楣铝⒌?br>
5 每當(dāng)釋放結(jié)構(gòu)化的元素(而該元素又包含指向動(dòng)態(tài)分配的內(nèi)存位置的指針)時(shí),都應(yīng)先遍歷子內(nèi)存位置并從那里開(kāi)始釋放苏携,然后再遍歷回父節(jié)點(diǎn)做瞪。
6 始終正確處理返回動(dòng)態(tài)分配的內(nèi)存引用的函數(shù)返回值
3 定位內(nèi)存泄露的位置
當(dāng)存在內(nèi)存泄露時(shí),需要定位內(nèi)存泄露的位置右冻。
工具: Valgrind是一個(gè)GPL的軟件装蓬,用于Linux(For x86, amd64 and ppc32)程序的內(nèi)存調(diào)試和代碼剖析。你可以在它的環(huán)境中運(yùn)行你的程序來(lái)監(jiān)視內(nèi)存的使用情況纱扭,比如C 語(yǔ)言中的malloc和free或者 C++中的new和 delete牍帚。使用Valgrind的工具包,你可以自動(dòng)的檢測(cè)許多內(nèi)存管理和線(xiàn)程的bug乳蛾,避免花費(fèi)太多的時(shí)間在bug尋找上暗赶,使得你的程序更加穩(wěn)固
使用具體參考:https://blog.csdn.net/qq_40989769/article/details/130785913
java語(yǔ)言中的內(nèi)存泄露:
Java 程序運(yùn)行時(shí)的內(nèi)存分配策略有三種,分別是靜態(tài)分配,棧式分配,和堆式分配,對(duì)應(yīng)的肃叶,三種存儲(chǔ)策略使用的內(nèi)存空間主要分別是靜態(tài)存儲(chǔ)區(qū)(也稱(chēng)方法區(qū))蹂随、棧區(qū)和堆區(qū)。
靜態(tài)存儲(chǔ)區(qū)(方法區(qū)):主要存放靜態(tài)數(shù)據(jù)被环、全局 static 數(shù)據(jù)和常量糙及。這塊內(nèi)存在程序編譯時(shí)就已經(jīng)分配好,并且在程序整個(gè)運(yùn)行期間都存在筛欢。
棧區(qū) :當(dāng)方法被執(zhí)行時(shí)浸锨,方法體內(nèi)的局部變量(其中包括基礎(chǔ)數(shù)據(jù)類(lèi)型、對(duì)象的引用)都在棧上創(chuàng)建版姑,并在方法執(zhí)行結(jié)束時(shí)這些局部變量所持有的內(nèi)存將會(huì)自動(dòng)被釋放柱搜。因?yàn)闂?nèi)存分配運(yùn)算內(nèi)置于處理器的指令集中,效率很高剥险,但是分配的內(nèi)存容量有限聪蘸。
堆區(qū) : 又稱(chēng)動(dòng)態(tài)內(nèi)存分配,通常就是指在程序運(yùn)行時(shí)直接 new 出來(lái)的內(nèi)存表制,也就是對(duì)象的實(shí)例健爬。這部分內(nèi)存在不使用時(shí)將會(huì)由 Java 垃圾回收器來(lái)負(fù)責(zé)回收。
1 內(nèi)存泄露的常見(jiàn)原因么介。
內(nèi)存泄露的判斷方法:1)某個(gè)對(duì)象時(shí)可達(dá)的娜遵,2)對(duì)象時(shí)無(wú)用的。 當(dāng)滿(mǎn)足這兩個(gè)條件后壤短,java中的GC機(jī)制時(shí)無(wú)法回收該對(duì)象的设拟。
根本原因:長(zhǎng)生命周期的對(duì)象持有短生命周期對(duì)象的引用就很可能發(fā)生內(nèi)存泄漏慨仿。
1)靜態(tài)集合類(lèi)引起的內(nèi)存泄露。
像HashMap纳胧、Vector等的使用最容易出現(xiàn)內(nèi)存泄露镰吆,這些靜態(tài)變量的生命周期和應(yīng)用程序一致,他們所引用的所有的對(duì)象Object也不能被釋放跑慕,因?yàn)樗麄円矊⒁恢北籚ector等引用著万皿。
Static Vector v = new Vector(10);
for (int i = 1; i<100; i++)
{
Object o = new Object();
v.add(o);
o = null;
}
若要釋放Vector,需要將vecotr賦值為null核行。
2)單例模式
單例模式和靜態(tài)變量的生命周期類(lèi)似相寇,當(dāng)單例模式引用外部對(duì)象后,這個(gè)對(duì)象將不會(huì)被gc邏輯回收钮科。
3 監(jiān)聽(tīng)器
在java 編程中,我們都需要和監(jiān)聽(tīng)器打交道婆赠,通常一個(gè)應(yīng)用當(dāng)中會(huì)用到很多監(jiān)聽(tīng)器绵脯,我們會(huì)調(diào)用一個(gè)控件的諸如addXXXListener()等方法來(lái)增加監(jiān)聽(tīng)器,但往往在釋放對(duì)象的時(shí)候卻沒(méi)有記住去刪除這些監(jiān)聽(tīng)器休里,從而增加了內(nèi)存泄漏的機(jī)會(huì)蛆挫。
2 Android中常見(jiàn)的內(nèi)存泄露原因
1 集合類(lèi)泄露
集合類(lèi)如果僅僅有添加元素的方法,而沒(méi)有相應(yīng)的刪除機(jī)制妙黍,導(dǎo)致內(nèi)存被占用悴侵。如果這個(gè)集合類(lèi)是全局性的變量 (比如類(lèi)中的靜態(tài)屬性,全局性的 map 等即有靜態(tài)引用或 final 一直指向它)拭嫁,那么沒(méi)有相應(yīng)的刪除機(jī)制可免,很可能導(dǎo)致集合所占用的內(nèi)存只增不減。如上面的vector
2 單例模式造成的內(nèi)存泄露
public class AppManager {
private static AppManager instance;
private Context context;
private AppManager(Context context) {
this.context = context;
### 這里的context 參數(shù)由外部傳入的參數(shù)決定做粤,如果是Activity的context浇借,
##當(dāng)Activity退出時(shí),因?yàn)閱卫J揭昧嗽揳ctivity怕品,所以我們無(wú)法activity妇垢。導(dǎo)致內(nèi)存泄露。
this.context = context.getApplicationContext() ## 這里強(qiáng)制獲取應(yīng)用的ApplicationContext肉康,從而保證周期的同步闯估。
}
public static AppManager getInstance(Context context) {
if (instance == null) {
instance = new AppManager(context);
}
return instance;
}
}
3) 匿名內(nèi)部類(lèi)
匿名內(nèi)部類(lèi)是一種特殊的內(nèi)部類(lèi),沒(méi)有顯示的類(lèi)名吼和,通常需要某個(gè)接口或者抽象類(lèi)的實(shí)例涨薪。如監(jiān)聽(tīng)類(lèi)。
Button button = findViewById(R.id.button)
button.setOnclickListener(new View.OnclickListener() {
public void onClick(View v) {
//點(diǎn)擊事件
}
當(dāng)匿名內(nèi)部類(lèi)持有外部類(lèi)時(shí)纹安,當(dāng)外部類(lèi)被銷(xiāo)毀尤辱,此時(shí) 匿名內(nèi)部類(lèi)依然持有外部類(lèi)的引用砂豌,此時(shí)外部無(wú)法被回收,造成內(nèi)存泄露光督。
解決方案: 使用弱引用阳距,即使匿名內(nèi)部類(lèi)持有對(duì)弱引用的引用,也不會(huì)阻止外部類(lèi)的垃圾回收结借。
- 強(qiáng)引用是默認(rèn)類(lèi)型的引用筐摘,在強(qiáng)引用存在時(shí),垃圾回收器不會(huì)回收它指向的對(duì)象船老。
– 弱引用是一種比強(qiáng)引用更加靈活的引用類(lèi)型咖熟,在垃圾回收時(shí),如果只有弱引用指向?qū)ο罅希敲礋o(wú)論內(nèi)存是否充足馍管,對(duì)象都會(huì)被回收。
public class MyActivity extends Activity {
private WeakReference<MyActivity> mActivityRef;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mActivityRef = new WeakReference<>(this);
Button button = findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
MyActivity activity = mActivityRef.get();
if (activity != null) {
// 處理點(diǎn)擊事件
}
}
});
}
}
4)handler 造成的內(nèi)存泄露
public class SampleActivity extends Activity {
private final Handler mLeakyHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
// ...
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Post a message and delay its execution for 10 minutes.
//此時(shí)當(dāng)SampleActivity 被銷(xiāo)毀后薪韩,此時(shí)由于Handler持有 外部類(lèi)的引用确沸,導(dǎo)致SampleActivity 無(wú)法被釋放。
mLeakyHandler.postDelayed(new Runnable() {
@Override
public void run() { /* ... */ }
}, 1000 * 60 * 10);
// Go back to the previous Activity.
finish();
}
}
使用弱引用
public class SampleActivity extends Activity {
/**
* Instances of static inner classes do not hold an implicit
* reference to their outer class.
*/
private static class MyHandler extends Handler {
private final WeakReference<SampleActivity> mActivity;
public MyHandler(SampleActivity activity) {
mActivity = new WeakReference<SampleActivity>(activity);
}
@Override
public void handleMessage(Message msg) {
SampleActivity activity = mActivity.get();
if (activity != null) {
// ...
}
}
}
private final MyHandler mHandler = new MyHandler(this);
/**
* Instances of anonymous classes do not hold an implicit
* reference to their outer class when they are "static".
*/
private static final Runnable sRunnable = new Runnable() {
@Override
public void run() { /* ... */ }
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Post a message and delay its execution for 10 minutes.
mHandler.postDelayed(sRunnable, 1000 * 60 * 10);
// Go back to the previous Activity.
finish();
}
}
- 盡量避免使用 static 成員變量
如果成員變量被聲明為 static俘陷,那我們都知道其生命周期將與整個(gè)app進(jìn)程生命周期一樣罗捎。
這會(huì)導(dǎo)致一系列問(wèn)題,如果你的app進(jìn)程設(shè)計(jì)上是長(zhǎng)駐內(nèi)存的拉盾,那即使app切到后臺(tái)桨菜,這部分內(nèi)存也不會(huì)被釋放。按照現(xiàn)在手機(jī)app內(nèi)存管理機(jī)制捉偏,占內(nèi)存較大的后臺(tái)進(jìn)程將優(yōu)先回收倒得,yi’wei如果此app做過(guò)進(jìn)程互保保活告私,那會(huì)造成app在后臺(tái)頻繁重啟屎暇。當(dāng)手機(jī)安裝了你參與開(kāi)發(fā)的app以后一夜時(shí)間手機(jī)被消耗空了電量、流量驻粟,你的app不得不被用戶(hù)卸載或者靜默
6)資源未關(guān)閉造成的內(nèi)存泄漏
對(duì)于使用了BraodcastReceiver根悼,ContentObserver,F(xiàn)ile蜀撑,游標(biāo) Cursor挤巡,Stream,Bitmap等資源的使用酷麦,應(yīng)該在A(yíng)ctivity銷(xiāo)毀時(shí)及時(shí)關(guān)閉或者注銷(xiāo)矿卑,否則這些資源將不會(huì)被回收,造成內(nèi)存泄漏沃饶。
參考:https://blog.csdn.net/tianyaleixiaowu/article/details/75783477
go語(yǔ)言中的內(nèi)存泄露
內(nèi)存泄露的原因:
1)暫時(shí)性?xún)?nèi)存泄露母廷。
獲取長(zhǎng)字符串中的一段導(dǎo)致長(zhǎng)字符串未釋放
獲取長(zhǎng)slice中的一段導(dǎo)致長(zhǎng)slice未釋放
在長(zhǎng)slice新建slice導(dǎo)致泄漏
string相比于切片少了一個(gè)容量的cap字段轻黑,可以把string當(dāng)成一個(gè)只讀的切片類(lèi)型。獲取長(zhǎng)string或切片中的一段內(nèi)容琴昆,由于新生成的對(duì)象和老的string或切片共用一個(gè)內(nèi)存空間氓鄙,
會(huì)導(dǎo)致老的string和切片資源暫時(shí)得不到釋放,造成短暫的內(nèi)存泄露业舍。
2) goroutine內(nèi)存泄露
goroutine 阻塞會(huì)導(dǎo)致內(nèi)存泄露抖拦。
// 沒(méi)有消費(fèi)者,發(fā)送端的的goroutine會(huì)一直被阻塞
func channelNoProducter() {
ch := make(chan int)
go func() {
ch <- 1
fmt.Println(111)
}()
}
// 沒(méi)有上游的發(fā)送者舷暮,會(huì)被阻塞态罪。
func channelNoProducer() {
ch := make(chan int, 1)
go func() {
<-ch
fmt.Println(111)
}()
}
// 解決方案,使用超時(shí)方案下面。
func TimeoutCancelContext() {
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(time.Millisecond*800))
go func() {
// 具體的業(yè)務(wù)邏輯
// 取消超時(shí)
defer cancel()
}()
select {
//如果任務(wù)沒(méi)有完成(即cancel函數(shù)沒(méi)有執(zhí)行)复颈,此時(shí)會(huì)發(fā)送 ctx.Done() ,防止阻塞
case <-ctx.Done():
fmt.Println("time out!!!")
return
}
}
//使用channel
func TimeoutCancelChannel() {
done := make(chan struct{}, 1)
go func() {
// 執(zhí)行業(yè)務(wù)邏輯
done <- struct{}{}
}()
select {
case <-done:
fmt.Println("call successfully!!!")
return
case <-time.After(time.Duration(800 * time.Millisecond)):
fmt.Println("timeout!!!")
// 使用獨(dú)立的協(xié)程處理超時(shí),需求添加return退出協(xié)程沥割,否則會(huì)導(dǎo)致當(dāng)前協(xié)程被通知channel阻塞券膀,進(jìn)而導(dǎo)致內(nèi)存泄露
return
}
}
GO中已經(jīng)封裝好了,直接就能使用_ "net/http/pprof"驯遇,可以記錄程序的運(yùn)行信息,可以是CPU使用情況蓄髓、內(nèi)存使用情況叉庐、goroutine運(yùn)行情況等,當(dāng)需要性能調(diào)優(yōu)或者定位Bug時(shí)候会喝,這些記錄的信息是相當(dāng)重要陡叠。
參考:https://blog.csdn.net/weixin_42117918/article/details/121461139
總結(jié)
不同語(yǔ)言回收內(nèi)存的方式不一樣,導(dǎo)致內(nèi)存泄露的處理方式也不一樣肢执。
C語(yǔ)言: C 沒(méi)GC回收機(jī)制枉阵,只有程序員手動(dòng)進(jìn)行回收,產(chǎn)生內(nèi)存泄露主要是在編寫(xiě)程序是不規(guī)范導(dǎo)致预茄。其優(yōu)點(diǎn)是程序更加靈活兴溜,運(yùn)行效率高,但同時(shí)也對(duì)程序員有更高的要求耻陕。
Java語(yǔ)言: Java有GC回收機(jī)制拙徽。 但其語(yǔ)言語(yǔ)法復(fù)雜,涉及靜態(tài)诗宣,匿名膘怕,單例,監(jiān)聽(tīng)器召庞,handler等語(yǔ)法岛心,其內(nèi)存泄露的常見(jiàn)也是最復(fù)雜的来破,其衍生了弱,軟忘古,強(qiáng)引用徘禁。特別時(shí)在A(yíng)ndroid開(kāi)發(fā)中,由于場(chǎng)景復(fù)雜多變存皂,內(nèi)存泄露極其常見(jiàn)晌坤。
Go 語(yǔ)言:go語(yǔ)言也有回收機(jī)制。其主要時(shí)goroutine的泄露旦袋。go語(yǔ)言的回收機(jī)制進(jìn)一步可以參考:https://xie.infoq.cn/article/f56b419e9de2e8ca3d44ee0ce