Java中的內(nèi)存泄露問(wèn)題和解決辦法
(Memory Leak,內(nèi)存泄漏)
為什么會(huì)產(chǎn)生內(nèi)存泄漏?
當(dāng)一個(gè)對(duì)象已經(jīng)不需要再使用本該被回收時(shí),另外一個(gè)正在使用的對(duì)象持有它的引用從而導(dǎo)致它不能被回收,這導(dǎo)致本該被回收的對(duì)象不能被回收而停留在堆內(nèi)存中,這就產(chǎn)生了內(nèi)存泄漏。
內(nèi)存泄漏對(duì)程序的影響?
內(nèi)存泄漏是造成應(yīng)用程序OOM的主要原因之一。我們知道Android系統(tǒng)為每個(gè)應(yīng)用程序分配的內(nèi)存是有限的,而當(dāng)一個(gè)應(yīng)用中產(chǎn)生的內(nèi)存泄漏比較多時(shí),這就難免會(huì)導(dǎo)致應(yīng)用所需要的內(nèi)存超過(guò)系統(tǒng)分配的內(nèi)存限額,這就造成了內(nèi)存溢出從而導(dǎo)致應(yīng)用Crash。
如何檢查和分析內(nèi)存泄漏?
因?yàn)閮?nèi)存泄漏是在堆內(nèi)存中,所以對(duì)我們來(lái)說(shuō)并不是可見(jiàn)的。通常我們可以借助MAT、LeakCanary等工具來(lái)檢測(cè)應(yīng)用程序是否存在內(nèi)存泄漏。
1、MAT是一款強(qiáng)大的內(nèi)存分析工具,功能繁多而復(fù)雜。
2、LeakCanary則是由Square開(kāi)源的一款輕量級(jí)的第三方內(nèi)存泄漏檢測(cè)工具,當(dāng)檢測(cè)到程序中產(chǎn)生內(nèi)存泄漏時(shí),它將以最直觀的方式告訴我們哪里產(chǎn)生了內(nèi)存泄漏和導(dǎo)致誰(shuí)泄漏了而不能被回收。
常見(jiàn)的內(nèi)存泄漏及解決方法
1、單例造成的內(nèi)存泄漏
由于單例的靜態(tài)特性使得其生命周期和應(yīng)用的生命周期一樣長(zhǎng),如果一個(gè)對(duì)象已經(jīng)不再需要使用了,而單例對(duì)象還持有該對(duì)象的引用,就會(huì)使得該對(duì)象不能被正?;厥眨瑥亩鴮?dǎo)致了內(nèi)存泄漏。
示例:防止單例導(dǎo)致內(nèi)存泄漏的實(shí)例
// 使用了單例模式
public class AppManager {
private static AppManager instance;
private Context context;
private AppManager(Context context) {
this.context = context;
}
public static AppManager getInstance(Context context) {
if (instance != null) {
instance = new AppManager(context);
}
return instance;
}
}這樣不管傳入什么Context最終將使用Application的Context,而單例的生命周期和應(yīng)用的一樣長(zhǎng),這樣就防止了內(nèi)存泄漏。???
2、非靜態(tài)內(nèi)部類(lèi)創(chuàng)建靜態(tài)實(shí)例造成的內(nèi)存泄漏【已無(wú)】
例如,有時(shí)候我們可能會(huì)在啟動(dòng)頻繁的Activity中,為了避免重復(fù)創(chuàng)建相同的數(shù)據(jù)資源,可能會(huì)出現(xiàn)如下寫(xiě)法:
public class MainActivity extends AppCompatActivity {
private static TestResource mResource = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if(mResource == null){
mResource = new TestResource();
}
//...
}
class TestResource {
//...
}
}這樣在Activity內(nèi)部創(chuàng)建了一個(gè)非靜態(tài)內(nèi)部類(lèi)的單例,每次啟動(dòng)Activity時(shí)都會(huì)使用該單例的數(shù)據(jù)。雖然這樣避免了資源的重復(fù)創(chuàng)建,但是這種寫(xiě)法卻會(huì)造成內(nèi)存泄漏。因?yàn)榉庆o態(tài)內(nèi)部類(lèi)默認(rèn)會(huì)持有外部類(lèi)的引用,而該非靜態(tài)內(nèi)部類(lèi)又創(chuàng)建了一個(gè)靜態(tài)的實(shí)例,該實(shí)例的生命周期和應(yīng)用的一樣長(zhǎng),這就導(dǎo)致了該靜態(tài)實(shí)例一直會(huì)持有該Activity的引用,從而導(dǎo)致Activity的內(nèi)存資源不能被正?;厥铡?br />解決方法:將該內(nèi)部類(lèi)設(shè)為靜態(tài)內(nèi)部類(lèi)或?qū)⒃搩?nèi)部類(lèi)抽取出來(lái)封裝成一個(gè)單例,如果需要使用Context,就使用Application的Context。
3、Handler造成的內(nèi)存泄漏
示例:創(chuàng)建匿名內(nèi)部類(lèi)的靜態(tài)對(duì)象
public class MainActivity extends AppCompatActivity {
private final Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
// ...
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new Thread(new Runnable() {
@Override
public void run() {
// ...
handler.sendEmptyMessage(0x123);
}
});
}
}1、從Android的角度
當(dāng)Android應(yīng)用程序啟動(dòng)時(shí),該應(yīng)用程序的主線程會(huì)自動(dòng)創(chuàng)建一個(gè)Looper對(duì)象和與之關(guān)聯(lián)的MessageQueue。當(dāng)主線程中實(shí)例化一個(gè)Handler對(duì)象后,它就會(huì)自動(dòng)與主線程Looper的MessageQueue關(guān)聯(lián)起來(lái)。所有發(fā)送到MessageQueue的Messag都會(huì)持有Handler的引用,所以Looper會(huì)據(jù)此回調(diào)Handle的handleMessage()方法來(lái)處理消息。只要MessageQueue中有未處理的Message,Looper就會(huì)不斷的從中取出并交給Handler處理。另外,主線程的Looper對(duì)象會(huì)伴隨該應(yīng)用程序的整個(gè)生命周期。
2、 Java角度
在Java中,非靜態(tài)內(nèi)部類(lèi)和匿名類(lèi)內(nèi)部類(lèi)都會(huì)潛在持有它們所屬的外部類(lèi)的引用,但是靜態(tài)內(nèi)部類(lèi)卻不會(huì)。
對(duì)上述的示例進(jìn)行分析,當(dāng)MainActivity結(jié)束時(shí),未處理的消息持有handler的引用,而handler又持有它所屬的外部類(lèi)也就是MainActivity的引用。這條引用關(guān)系會(huì)一直保持直到消息得到處理,這樣阻止了MainActivity被垃圾回收器回收,從而造成了內(nèi)存泄漏。
解決方法:將Handler類(lèi)獨(dú)立出來(lái)或者使用靜態(tài)內(nèi)部類(lèi),這樣便可以避免內(nèi)存泄漏。
4、線程造成的內(nèi)存泄漏
示例:AsyncTask和Runnable
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new Thread(new MyRunnable()).start();
new MyAsyncTask(this).execute();
}
class MyAsyncTask extends AsyncTask<Void, Void, Void> {
// ...
public MyAsyncTask(Context context) {
// ...
}
@Override
protected Void doInBackground(Void... params) {
// ...
return null;
}
@Override
protected void onPostExecute(Void aVoid) {
// ...
}
}
class MyRunnable implements Runnable {
@Override
public void run() {
// ...
}
}
}AsyncTask和Runnable都使用了匿名內(nèi)部類(lèi),那么它們將持有其所在Activity的隱式引用。如果任務(wù)在Activity銷(xiāo)毀之前還未完成,那么將導(dǎo)致Activity的內(nèi)存資源無(wú)法被回收,從而造成內(nèi)存泄漏。
解決方法:將AsyncTask和Runnable類(lèi)獨(dú)立出來(lái)或者使用靜態(tài)內(nèi)部類(lèi),這樣便可以避免內(nèi)存泄漏。
5、資源未關(guān)閉造成的內(nèi)存泄漏
對(duì)于使用了BraodcastReceiver,ContentObserver,F(xiàn)ile,Cursor,Stream,Bitmap等資源,應(yīng)該在Activity銷(xiāo)毀時(shí)及時(shí)關(guān)閉或者注銷(xiāo),否則這些資源將不會(huì)被回收,從而造成內(nèi)存泄漏。
1)比如在Activity中register了一個(gè)BraodcastReceiver,但在Activity結(jié)束后沒(méi)有unregister該BraodcastReceiver。
2)資源性對(duì)象比如Cursor,Stream、File文件等往往都用了一些緩沖,我們?cè)诓皇褂玫臅r(shí)候,應(yīng)該及時(shí)關(guān)閉它們,以便它們的緩沖及時(shí)回收內(nèi)存。它們的緩沖不僅存在于 java虛擬機(jī)內(nèi),還存在于java虛擬機(jī)外。如果我們僅僅是把它的引用設(shè)置為null,而不關(guān)閉它們,往往會(huì)造成內(nèi)存泄漏。
3)對(duì)于資源性對(duì)象在不使用的時(shí)候,應(yīng)該調(diào)用它的close()函數(shù)將其關(guān)閉掉,然后再設(shè)置為null。在我們的程序退出時(shí)一定要確保我們的資源性對(duì)象已經(jīng)關(guān)閉。
4)Bitmap對(duì)象不在使用時(shí)調(diào)用recycle()釋放內(nèi)存。2.3以后的bitmap應(yīng)該是不需要手動(dòng)recycle了,內(nèi)存已經(jīng)在java層了。
6、使用ListView時(shí)造成的內(nèi)存泄漏
初始時(shí)ListView會(huì)從BaseAdapter中根據(jù)當(dāng)前的屏幕布局實(shí)例化一定數(shù)量的View對(duì)象,同時(shí)ListView會(huì)將這些View對(duì)象緩存起來(lái)。當(dāng)向上滾動(dòng)ListView時(shí),原先位于最上面的Item的View對(duì)象會(huì)被回收,然后被用來(lái)構(gòu)造新出現(xiàn)在下面的Item。這個(gè)構(gòu)造過(guò)程就是由getView()方法完成的,getView()的第二個(gè)形參convertView就是被緩存起來(lái)的Item的View對(duì)象(初始化時(shí)緩存中沒(méi)有View對(duì)象則convertView是null)。
構(gòu)造Adapter時(shí),沒(méi)有使用緩存的convertView。
解決方法:在構(gòu)造Adapter時(shí),使用緩存的convertView。
7、集合容器中的內(nèi)存泄露
我們通常把一些對(duì)象的引用加入到了集合容器(比如ArrayList)中,當(dāng)我們不需要該對(duì)象時(shí),并沒(méi)有把它的引用從集合中清理掉,這樣這個(gè)集合就會(huì)越來(lái)越大。如果這個(gè)集合是static的話,那情況就更嚴(yán)重了。
解決方法:在退出程序之前,將集合里的東西clear,然后置為null,再退出程序。
8、WebView造成的泄露
當(dāng)我們不要使用WebView對(duì)象時(shí),應(yīng)該調(diào)用它的destory()函數(shù)來(lái)銷(xiāo)毀它,并釋放其占用的內(nèi)存,否則其長(zhǎng)期占用的內(nèi)存也不能被回收,從而造成內(nèi)存泄露。
解決方法:為WebView另外開(kāi)啟一個(gè)進(jìn)程,通過(guò)AIDL與主線程進(jìn)行通信,WebView所在的進(jìn)程可以根據(jù)業(yè)務(wù)的需要選擇合適的時(shí)機(jī)進(jìn)行銷(xiāo)毀,從而達(dá)到內(nèi)存的完整釋放。
如何避免內(nèi)存泄漏?
1、在涉及使用Context時(shí),對(duì)于生命周期比Activity長(zhǎng)的對(duì)象應(yīng)該使用Application的Context。凡是使用Context優(yōu)先考慮Application的Context,當(dāng)然它并不是萬(wàn)能的,對(duì)于有些地方則必須使用Activity的Context。對(duì)于Application,Service,Activity三者的Context的應(yīng)用場(chǎng)景如下:

其中,NO1表示Application和Service可以啟動(dòng)一個(gè)Activity,不過(guò)需要?jiǎng)?chuàng)建一個(gè)新的task任務(wù)隊(duì)列。而對(duì)于Dialog而言,只有在Activity中才能創(chuàng)建。除此之外三者都可以使用。
2、對(duì)于需要在靜態(tài)內(nèi)部類(lèi)中使用非靜態(tài)外部成員變量(如:Context、View ),可以在靜態(tài)內(nèi)部類(lèi)中使用弱引用來(lái)引用外部類(lèi)的變量來(lái)避免內(nèi)存泄漏。
3、對(duì)于不再需要使用的對(duì)象,顯示的將其賦值為null,比如使用完Bitmap后先調(diào)用recycle(),再賦為null。
4、保持對(duì)對(duì)象生命周期的敏感,特別注意單例、靜態(tài)對(duì)象、全局性集合等的生命周期。
5、對(duì)于生命周期比Activity長(zhǎng)的內(nèi)部類(lèi)對(duì)象,并且內(nèi)部類(lèi)中使用了外部類(lèi)的成員變量,可以這樣做避免內(nèi)存泄漏:
1)將內(nèi)部類(lèi)改為靜態(tài)內(nèi)部類(lèi)
2)靜態(tài)內(nèi)部類(lèi)中使用弱引用來(lái)引用外部類(lèi)的成員變量
總結(jié)
到此這篇關(guān)于Java中的內(nèi)存泄露問(wèn)題和解決辦法的文章就介紹到這了,更多相關(guān)Java內(nèi)存泄露問(wèn)題內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
MyBatisPlus 自定義sql語(yǔ)句的實(shí)現(xiàn)
這篇文章主要介紹了MyBatisPlus 自定義sql語(yǔ)句的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-08-08
基于Spring Boot DevTools實(shí)現(xiàn)開(kāi)發(fā)過(guò)程優(yōu)化
這篇文章主要介紹了基于Spring Boot DevTools實(shí)現(xiàn)開(kāi)發(fā)過(guò)程優(yōu)化,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-09-09
Java guava monitor監(jiān)視器線程的使用詳解
工作中的場(chǎng)景中是否存在類(lèi)似這樣的場(chǎng)景,需要提交的線程在某個(gè)觸發(fā)條件下執(zhí)行。本文主要就是使用guava中的monitor來(lái)優(yōu)雅的實(shí)現(xiàn)帶監(jiān)視器的線程2021-11-11
httpclient 請(qǐng)求http數(shù)據(jù),json轉(zhuǎn)map的實(shí)例
下面小編就為大家?guī)?lái)一篇httpclient 請(qǐng)求http數(shù)據(jù),json轉(zhuǎn)map的實(shí)例。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-12-12
SpringBoot項(xiàng)目集成Flyway詳細(xì)過(guò)程
今天帶大家學(xué)習(xí)SpringBoot項(xiàng)目集成Flyway詳細(xì)過(guò)程,文中有非常詳細(xì)的介紹及代碼示例,對(duì)正在學(xué)習(xí)java的小伙伴們有很好地幫助,需要的朋友可以參考下2021-05-05
Java中LocalDate的詳細(xì)方法舉例總結(jié)
這篇文章主要給大家介紹了關(guān)于Java中LocalDate詳細(xì)方法舉例的相關(guān)資料,LocalDate主要是用來(lái)處理日期的類(lèi),文中通過(guò)代碼示例介紹的非常詳細(xì),需要的朋友可以參考下2023-09-09
java跳出循環(huán)的三種方式總結(jié)(break語(yǔ)句、continue語(yǔ)句和return語(yǔ)句)
在實(shí)際編程中,有時(shí)需要在條件語(yǔ)句匹配的時(shí)候跳出循環(huán),下面這篇文章主要給大家介紹了關(guān)于java跳出循環(huán)的三種方式,其中包括break語(yǔ)句、continue語(yǔ)句和return語(yǔ)句的相關(guān)資料,需要的朋友可以參考下2023-03-03

