程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> Java中關於內存洩露湧現的緣由匯總及若何防止內存洩露(超具體版)

Java中關於內存洩露湧現的緣由匯總及若何防止內存洩露(超具體版)

編輯:關於JAVA

Java中關於內存洩露湧現的緣由匯總及若何防止內存洩露(超具體版)。本站提示廣大學習愛好者:(Java中關於內存洩露湧現的緣由匯總及若何防止內存洩露(超具體版))文章只能為提供參考,不一定能成為您想要的結果。以下是Java中關於內存洩露湧現的緣由匯總及若何防止內存洩露(超具體版)正文


Android 內存洩露總結

內存治理的目標就是讓我們在開辟中怎樣有用的防止我們的運用湧現內存洩露的成績。內存洩露年夜家都不生疏了,簡略粗鄙的講,就是該被釋放的對象沒有釋放,一向被某個或某些實例所持有卻不再被應用招致 GC 不克不及收受接管。比來本身浏覽了年夜量相干的文檔材料,盤算做個 總結 沉澱上去跟年夜家一路分享和進修,也給本身一個警示,今後 coding 時怎樣防止這些情形,進步運用的體驗和質量。

我會從 java 內存洩露的基本常識開端,並經由過程詳細例子來講明 Android 惹起內存洩露的各類緣由,和若何應用對象來剖析運用內存洩露,最初再做總結。

Java 內存分派戰略

Java 法式運轉時的內存分派戰略有三種,分離是靜態分派,棧式分派,和堆式分派,對應的,三種存儲戰略應用的內存空間重要分離是靜態存儲區(也稱辦法區)、棧區和堆區。

靜態存儲區(辦法區):重要寄存靜態數據、全局 static 數據和常量。這塊內存在法式編譯時就曾經分派好,而且在法式全部運轉時代都存在。

棧區 :當辦法被履行時,辦法體內的部分變量(個中包含基本數據類型、對象的援用)都在棧上創立,並在辦法履行停止時這些部分變量所持有的內存將會主動被釋放。由於棧內存分派運算內置於處置器的指令集中,效力很高,然則分派的內存容量無限。

堆區 : 又稱靜態內存分派,平日就是指在法式運轉時直接 new 出來的內存,也就是對象的實例。這部門內存在不應用時將會由 Java 渣滓收受接管器來擔任收受接管。

棧與堆的差別:

在辦法體內界說的(部分變量)一些根本類型的變量和對象的援用變量都是在辦法的棧內存平分配的。當在一段辦法塊中界說一個變量時,Java 就會在棧中為該變量分派內存空間,當跨越該變量的感化域後,該變量也就有效了,分派給它的內存空間也將被釋放失落,該內存空間可以被從新應用。

堆內存用來寄存一切由 new 創立的對象(包含該對象個中的一切成員變量)和數組。在堆平分配的內存,將由 Java 渣滓收受接管器來主動治理。在堆中發生了一個數組或許對象後,還可以在棧中界說一個特別的變量,這個變量的取值等於數組或許對象在堆內存中的首地址,這個特別的變量就是我們下面說的援用變量。我們可以經由過程這個援用變量來拜訪堆中的對象或許數組。

舉個例子:

public class Sample {
int s1 = 0;
Sample mSample1 = new Sample();
public void method() {
int s2 = 1;
Sample mSample2 = new Sample();
}
}
Sample mSample3 = new Sample();

Sample 類的部分變量 s2 和援用變量 mSample2 都是存在於棧中,但 mSample2 指向的對象是存在於堆上的。
mSample3 指向的對象實體寄存在堆上,包含這個對象的一切成員變量 s1 和 mSample1,而它本身存在於棧中。

結論:

部分變量的根本數據類型和援用存儲於棧中,援用的對象實體存儲於堆中。—— 由於它們屬於辦法中的變量,性命周期隨辦法而停止。

成員變量全體存儲與堆中(包含根本數據類型,援用和援用的對象實體)—— 由於它們屬於類,類對象畢竟是要被new出來應用的。

懂得了 Java 的內存分派以後,我們再來看看 Java 是怎樣治理內存的。

Java是若何治理內存

Java的內存治理就是對象的分派和釋放成績。在 Java 中,法式員須要經由過程症結字 new 為每一個對象請求內存空間 (根本類型除外),一切的對象都在堆 (Heap)平分配空間。別的,對象的釋放是由 GC 決議和履行的。在 Java 中,內存的分派是由法式完成的,而內存的釋放是由 GC 完成的,這類進出兩條線的辦法確切簡化了法式員的任務。但同時,它也減輕了JVM的任務。這也是 Java 法式運轉速度較慢的緣由之一。由於,GC 為了可以或許准確釋放對象,GC 必需監控每個對象的運轉狀況,包含對象的請求、援用、被援用、賦值等,GC 都須要停止監控。

監督對象狀況是為了加倍精確地、實時地釋放對象,而釋放對象的基本准繩就是該對象不再被援用。

為了更好懂得 GC 的任務道理,我們可以將對象斟酌為有向圖的極點,將援用關系斟酌為圖的有向邊,有向邊從援用者指向被引對象。別的,每一個線程對象可以作為一個圖的肇端極點,例如年夜多法式從 main 過程開端履行,那末該圖就是以 main 過程極點開端的一棵根樹。在這個有向圖中,根極點可達的對象都是有用對象,GC將不收受接管這些對象。假如某個對象 (連通子圖)與這個根極點弗成達(留意,該圖為有向圖),那末我們以為這個(這些)對象不再被援用,可以被 GC 收受接管。
以下,我們舉一個例子解釋若何用有向圖表現內存治理。關於法式的每個時辰,我們都有一個有向圖表現JVM的內存分派情形。以下右圖,就是右邊法式運轉到第6行的表示圖。

Java應用有向圖的方法停止內存治理,可以清除援用輪回的成績,例若有三個對象,互相援用,只需它們和根過程弗成達的,那末GC也是可以收受接管它們的。這類方法的長處是治理內存的精度很高,然則效力較低。別的一種經常使用的內存治理技巧是應用計數器,例如COM模子采取計數器方法治理構件,它與有向圖比擬,精度行低(很難處置輪回援用的成績),但履行效力很高。

甚麼是Java中的內存洩漏

在Java中,內存洩露就是存在一些被分派的對象,這些對象有上面兩個特色,起首,這些對象是可達的,即在有向圖中,存在通路可以與其相連;其次,這些對象是無用的,即法式今後不會再應用這些對象。假如對象知足這兩個前提,這些對象便可以剖斷為Java中的內存洩露,這些對象不會被GC所收受接管,但是它卻占用內存。

在C++中,內存洩露的規模更年夜一些。有些對象被分派了內存空間,然後卻弗成達,因為C++中沒有GC,這些內存將永久收不回來。在Java中,這些弗成達的對象都由GC擔任收受接管,是以法式員不須要斟酌這部門的內存洩漏。

經由過程剖析,我們得知,關於C++,法式員須要本身治理邊和極點,而關於Java法式員只須要治理邊便可以了(不須要治理極點的釋放)。經由過程這類方法,Java進步了編程的效力。

是以,經由過程以上剖析,我們曉得在Java中也有內存洩露,但規模比C++要小一些。由於Java從說話上包管,任何對象都是可達的,一切的弗成達對象都由GC治理。

關於法式員來講,GC根本是通明的,弗成見的。固然,我們只要幾個函數可以拜訪GC,例如運轉GC的函數System.gc(),然則依據Java說話標准界說, 該函數不包管JVM的渣滓搜集器必定會履行。由於,分歧的JVM完成者能夠應用分歧的算法治理GC。平日,GC的線程的優先級別較低。JVM挪用GC的戰略也有許多種,有的是內存應用達到必定水平時,GC才開端任務,也有准時履行的,有的是陡峭履行GC,有的是中止式履行GC。但平日來講,我們不須要關懷這些。除非在一些特定的場所,GC的履行影呼應用法式的機能,例如關於基於Web的及時體系,如收集游戲等,用戶不願望GC忽然中止運用法式履行而停止渣滓收受接管,那末我們須要調劑GC的參數,讓GC可以或許經由過程陡峭的方法釋放內存,例如將渣滓收受接管分化為一系列的小步調履行,Sun供給的HotSpot JVM就支撐這一特征。

異樣給出一個 Java 內存洩露的典范例子,

Vector v = new Vector(10);
for (int i = 1; i < 100; i++) {
Object o = new Object();
v.add(o);
o = null; 
}

在這個例子中,我們輪回請求Object對象,並將所請求的對象放入一個 Vector 中,假如我們僅僅釋放援用自己,那末 Vector 依然援用該對象,所以這個對象對 GC 來講是弗成收受接管的。是以,假如對象參加到Vector 後,還必需從 Vector 中刪除,最簡略的辦法就是將 Vector 對象設置為 null。

具體Java中的內存洩露

1.Java內存收受接管機制

豈論哪一種說話的內存分派方法,都須要前往所分派內存的真實地址,也就是前往一個指針到內存塊的首地址。Java中對象是采取new或許反射的辦法創立的,這些對象的創立都是在堆(Heap)平分配的,一切對象的收受接管都是由Java虛擬機經由過程渣滓收受接管機制完成的。GC為了可以或許准確釋放對象,會監控每一個對象的運轉狀態,對他們的請求、援用、被援用、賦值等狀態停止監控,Java會應用有向圖的辦法停止治理內存,及時監控對象能否可以到達,假如弗成達到,則就將其收受接管,如許也能夠清除援用輪回的成績。在Java說話中,斷定一個內存空間能否相符渣滓搜集尺度有兩個:一個是給對象付與了空值null,以下再沒有挪用過,另外一個是給對象付與了新值,如許從新分派了內存空間。

2.Java內存洩露惹起的緣由

內存洩露是指無用對象(不再應用的對象)連續占領內存或無用對象的內存得不到實時釋放,從而形成內存空間的糟蹋稱為內存洩露。內存洩漏有時不嚴重且不容易發覺,如許開辟者就不曉得存在內存洩漏,但有時也會很嚴重,會提醒你Out of memory。

Java內存洩露的基本緣由是甚麼呢?永生命周期的對象持有短性命周期對象的援用就極可能產生內存洩露,雖然短性命周期對象曾經不再須要,然則由於永生命周期持有它的援用而招致不克不及被收受接管,這就是Java中內存洩露的產生場景。詳細重要有以下幾年夜類:

1、靜態聚集類惹起內存洩露:

像HashMap、Vector等的應用最輕易湧現內存洩漏,這些靜態變量的性命周期和運用法式分歧,他們所援用的一切的對象Object也不克不及被釋放,由於他們也將一向被Vector等援用著。

例如

Static Vector v = new Vector(10);
for (int i = 1; i<100; i++)
{
Object o = new Object();
v.add(o);
o = null;
}

在這個例子中,輪回請求Object 對象,並將所請求的對象放入一個Vector 中,假如僅僅釋放援用自己(o=null),那末Vector 依然援用該對象,所以這個對象對GC 來講是弗成收受接管的。是以,假如對象參加到Vector 後,還必需從Vector 中刪除,最簡略的辦法就是將Vector對象設置為null。

2、當聚集外面的對象屬性被修正後,再挪用remove()辦法時不起感化。

例如:

public static void main(String[] args)
{
Set<Person> set = new HashSet<Person>();
Person p1 = new Person("唐僧","pwd1",25);
Person p2 = new Person("孫悟空","pwd2",26);
Person p3 = new Person("豬八戒","pwd3",27);
set.add(p1);
set.add(p2);
set.add(p3);
System.out.println("總共有:"+set.size()+" 個元素!"); //成果:總共有:3 個元素!
p3.setAge(2); //修正p3的年紀,此時p3元素對應的hashcode值產生轉變
set.remove(p3); //此時remove不失落,形成內存洩露
set.add(p3); //從新添加,竟然添加勝利
System.out.println("總共有:"+set.size()+" 個元素!"); //成果:總共有:4 個元素!
for (Person person : set)
{
System.out.println(person);
}
}

3、監聽器

在java 編程中,我們都須要和監聽器打交道,平日一個運用傍邊會用到許多監聽器,我們會挪用一個控件的諸如addXXXListener()等辦法來增長監聽器,但常常在釋放對象的時刻卻沒有記住去刪除這些監聽器,從而增長了內存洩露的機遇。

4、各類銜接

好比數據庫銜接(dataSourse.getConnection()),收集銜接(socket)和io銜接,除非其顯式的挪用了其close()辦法將其銜接封閉,不然是不會主動被GC 收受接管的。關於Resultset 和Statement 對象可以不停止顯式收受接管,但Connection 必定要顯式收受接管,由於Connection 在任什麼時候候都沒法主動收受接管,而Connection一旦收受接管,Resultset 和Statement 對象就會立刻為NULL。然則假如應用銜接池,情形就紛歧樣了,除要顯式地封閉銜接,還必需顯式地封閉Resultset Statement 對象(封閉個中一個,別的一個也會封閉),不然就會形成年夜量的Statement 對象沒法釋放,從而惹起內存洩露。這類情形下普通都邑在try外面去的銜接,在finally外面釋放銜接。

5、外部類和內部模塊的援用

外部類的援用是比擬輕易遺忘的一種,並且一旦沒釋放能夠招致一系列的後繼類對象沒有釋放。另外法式員還要當心內部模塊不經意的援用,例如法式員A 擔任A 模塊,挪用了B 模塊的一個辦法如:

public void registerMsg(Object b);

這類挪用就要異常當心了,傳入了一個對象,極可能模塊B就堅持了對該對象的援用,這時候候就須要留意模塊B 能否供給響應的操作去除援用。

6、單例形式

不准確應用單例形式是惹起內存洩露的一個罕見成績,單例對象在初始化後將在JVM的全部性命周期中存在(以靜態變量的方法),假如單例對象持有內部的援用,那末這個對象將不克不及被JVM正常收受接管,招致內存洩露,斟酌上面的例子:

class A{
public A(){
B.getInstance().setA(this);
}
....
}
//B類采取單例形式
class B{
private A a;
private static B instance=new B();
public B(){}
public static B getInstance(){
return instance;
}
public void setA(A a){
this.a=a;
}
//getter...
}

明顯B采取singleton形式,它持有一個A對象的援用,而這個A類的對象將不克不及被收受接管。想象下假如A是個比擬龐雜的對象或許聚集類型會產生甚麼情形

Android中罕見的內存洩露匯總

聚集類洩露

聚集類假如僅唯一添加元素的辦法,而沒有響應的刪除機制,招致內存被占用。假如這個聚集類是全局性的變量 (好比類中的靜態屬性,全局性的 map 等即有靜態援用或 final 一向指向它),那末沒有響應的刪除機制,極可能招致聚集所占用的內存只增不減。好比下面的典范例子就是個中一種情形,固然現實上我們在項目中確定不會寫這麼 2B 的代碼,但略不留意照樣很輕易湧現這類情形,好比我們都愛好經由過程 HashMap 做一些緩存之類的事,這類情形就要多留一些心眼。

單例形成的內存洩露

因為單例的靜態特征使得其性命周期跟運用的性命周期一樣長,所以假如應用不適當的話,很輕易形成內存洩露。好比上面一個典范的例子,

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,所以這個Context的性命周期的長短相當主要:

1、假如此時傳入的是 Application 的 Context,由於 Application 的性命周期就是全部運用的性命周期,所以這將沒有任何成績。

2、假如此時傳入的是 Activity 的 Context,當這個 Context 所對應的 Activity 加入時,因為該 Context 的援用被單例對象所持有,其性命周期等於全部運用法式的性命周期,所以以後 Activity 加入時它的內存其實不會被收受接管,這就形成洩露了。

准確的方法應當改成上面這類方法:

public class AppManager {
private static AppManager instance;
private Context context;
private AppManager(Context context) {
this.context = context.getApplicationContext();// 應用Application 的context
}
public static AppManager getInstance(Context context) {
if (instance == null) {
instance = new AppManager(context);
}
return instance;
}
}

或許如許寫,連 Context 都不消傳出去了:

在你的 Application 中添加一個靜態辦法,getContext() 前往 Application 的 context,

...

context = getApplicationContext();
...
/**
* 獲得全局的context
* @return 前往全局context對象
*/
public static Context getContext(){
return context;
}
public class AppManager {
private static AppManager instance;
private Context context;
private AppManager() {
this.context = MyApplication.getContext();// 應用Application 的context
}
public static AppManager getInstance() {
if (instance == null) {
instance = new AppManager();
}
return instance;
}
}

匿名外部類/非靜態外部類和異步線程

非靜態外部類創立靜態實例形成的內存洩露

有的時刻我們能夠會在啟動頻仍的Activity中,為了不反復創立雷同的數據資本,能夠會湧現這類寫法:

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(mManager == null){
mManager = new TestResource();
}
//...
}
class TestResource {
//...
}
}

如許就在Activity外部創立了一個非靜態外部類的單例,每次啟動Activity時都邑應用該單例的數據,如許固然防止了資本的反復創立,不外這類寫法卻會形成內存洩露,由於非靜態外部類默許會持有內部類的援用,而該非靜態外部類又創立了一個靜態的實例,該實例的性命周期和運用的一樣長,這就招致了該靜態實例一向會持有該Activity的援用,招致Activity的內存資本不克不及正常收受接管。准確的做法為:

將該外部類設為靜態外部類或將該外部類抽掏出來封裝成一個單例,假如須要應用Context,請依照下面推舉的應用Application 的 Context。固然,Application 的 context 不是全能的,所以也不克不及隨意亂花,關於有些處所則必需應用 Activity 的 Context,關於Application,Service,Activity三者的Context的運用場景以下:

個中: NO1表現 Application 和 Service 可以啟動一個 Activity,不外須要創立一個新的 task 義務隊列。而關於 Dialog 而言,只要在 Activity 中能力創立

匿名外部類

android開辟常常會繼續完成Activity/Fragment/View,此時假如你應用了匿名類,並被異步線程持有了,那要當心了,假如沒有任何辦法如許必定會招致洩漏

public class MainActivity extends Activity {
...
Runnable ref1 = new MyRunable();
Runnable ref2 = new Runnable() {
@Override
public void run() {
}
};
...
}

ref1和ref2的差別是,ref2應用了匿名外部類。我們來看看運轉時這兩個援用的內存:

可以看到,ref1沒甚麼特殊的。

但ref2這個匿名類的完成對象外面多了一個援用:

this$0這個援用指向MainActivity.this,也就是說以後的MainActivity實例會被ref2持有,假如將這個援用再傳入一個異步線程,此線程和此Acitivity性命周期紛歧致的時刻,就形成了Activity的洩漏。

Handler 形成的內存洩露

Handler 的應用形成的內存洩露成績應當說是最為罕見了,許多時刻我們為了不 ANR 而不在主線程停止耗時操作,在處置收集義務或許封裝一些要求回調等api都借助Handler來處置,但 Handler 不是全能的,關於 Handler 的應用代碼編寫一不標准即有能夠形成內存洩露。別的,我們曉得 Handler、Message 和 MessageQueue 都是互相聯系關系在一路的,萬一 Handler 發送的 Message 還沒有被處置,則該 Message 及發送它的 Handler 對象將被線程 MessageQueue 一向持有。

因為 Handler 屬於 TLS(Thread Local Storage) 變量, 性命周期和 Activity 是紛歧致的。是以這類完成方法普通很難包管跟 View 或許 Activity 的性命周期堅持分歧,故很輕易招致沒法准確釋放。

舉個例子:

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.
mLeakyHandler.postDelayed(new Runnable() {
@Override
public void run() { /* ... */ }
}, 1000 * 60 * 10);
// Go back to the previous Activity.
finish();
}
}

在該 SampleActivity 中聲清楚明了一個延遲10分鐘履行的新聞 Message,mLeakyHandler 將其 push 進了新聞隊列 MessageQueue 裡。當該 Activity 被 finish() 失落時,延遲履行義務的 Message 還會持續存在於主線程中,它持有該 Activity 的 Handler 援用,所以此時 finish() 失落的 Activity 就不會被收受接管了從而形成內存洩露(因 Handler 為非靜態外部類,它會持有內部類的援用,在這裡就是指 SampleActivity)。

修復辦法:在 Activity 中防止應用非靜態外部類,好比下面我們將 Handler 聲明為靜態的,則其存活期跟 Activity 的性命周期就有關了。同時經由過程弱援用的方法引入 Activity,防止直接將 Activity 作為 context 傳出來,見上面代碼:

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();
}
}

綜述,即推舉應用靜態外部類 + WeakReference 這類方法。每次應用前留意判空。

後面提到了 WeakReference,所以這裡就簡略的說一下 Java 對象的幾種援用類型。

Java對援用的分類有 Strong reference, SoftReference, WeakReference, PhatomReference 四種。

在Android運用的開辟中,為了避免內存溢出,在處置一些占用內存年夜並且聲明周期較長的對象時刻,可以盡可能運用軟援用和弱援用技巧。

軟/弱援用可以和一個援用隊列(ReferenceQueue)結合應用,假如軟援用所援用的對象被渣滓收受接管器收受接管,Java虛擬機就會把這個軟援用參加到與之聯系關系的援用隊列中。應用這個隊列可以得知被收受接管的軟/弱援用的對象列表,從而為緩沖器消除已掉效的軟/弱援用。

假定我們的運用會用到年夜量的默許圖片,好比運用中有默許的頭像,默許游戲圖標等等,這些圖片許多處所會用到。假如每次都去讀取圖片,因為讀取文件須要硬件操作,速度較慢,會招致機能較低。所以我們斟酌將圖片緩存起來,須要的時刻直接從內存中讀取。然則,因為圖片占用內存空間比擬年夜,緩存許多圖片須要許多的內存,便可能比擬輕易產生OutOfMemory異常。這時候,我們可以斟酌應用軟/弱援用技巧來防止這個成績產生。以下就是高速緩沖器的雛形:

起首界說一個HashMap,保留軟援用對象。

private Map <String, SoftReference<Bitmap>> imageCache = new HashMap <String, SoftReference<Bitmap>> ();

再來界說一個辦法,保留Bitmap的軟援用到HashMap。

應用軟援用今後,在OutOfMemory異常產生之前,這些緩存的圖片資本的內存空間可以被釋放失落的,從而防止內存到達下限,防止Crash產生。

假如只是想防止OutOfMemory異常的產生,則可使用軟援用。假如關於運用的機能更在乎,想盡快收受接管一些占用內存比擬年夜的對象,則可使用弱援用。

別的可以依據對象能否常常應用來斷定選擇軟援用照樣弱援用。假如該對象能夠會常常應用的,就盡可能用軟援用。假如該對象不被應用的能夠性更年夜些,便可以用弱援用。

ok,持續回到主題。後面所說的,創立一個靜態Handler外部類,然後對 Handler 持有的對象應用弱援用,如許在收受接管時也能夠收受接管 Handler 持有的對象,然則如許做固然防止了 Activity 洩露,不外 Looper 線程的新聞隊列中照樣能夠會有待處置的新聞,所以我們在 Activity 的 Destroy 時或許 Stop 時應當移除新聞隊列 MessageQueue 中的新聞。

上面幾個辦法都可以移除 Message:

public final void removeCallbacks(Runnable r);
public final void removeCallbacks(Runnable r, Object token);
public final void removeCallbacksAndMessages(Object token);
public final void removeMessages(int what);
public final void removeMessages(int what, Object object);

盡可能防止應用 static 成員變量

假如成員變量被聲明為 static,那我們都曉得其性命周期將與全部app過程性命周期一樣。

這會招致一系列成績,假如你的app過程設計上是長駐內存的,那即便app切到後台,這部門內存也不會被釋放。依照如今手機app內存治理機制,占內存較年夜的後台過程將優先收受接管,yi'wei假如此app做過過程互保保活,那會形成app在後台頻仍重啟。當手機裝置了你介入開辟的app今後一夜時光手機被消費空了電量、流量,你的app不能不被用戶卸載或許靜默。

這裡修復的辦法是:

不要在類初始時初始化靜態成員。可以斟酌lazy初始化。
架構設計上要思慮能否真的有需要如許做,盡可能防止。假如架構須要這麼設計,那末此對象的性命周期你有義務治理起來。

防止 override finalize()

1、finalize 辦法被履行的時光不肯定,不克不及依附與它來釋放緊缺的資本。時光不肯定的緣由是:
虛擬機挪用GC的時光不肯定
Finalize daemon線程被調劑到的時光不肯定

2、finalize 辦法只會被履行一次,即便對象被回生,假如曾經履行過了 finalize 辦法,再次被 GC 時也不會再履行了,緣由是:

含有 finalize 辦法的 object 是在 new 的時刻由虛擬機生成了一個 finalize reference 在來援用到該Object的,而在 finalize 辦法履行的時刻,該 object 所對應的 finalize Reference 會被釋放失落,即便在這個時刻把該 object 回生(即用強援用援用住該 object ),再第二次被 GC 的時刻因為沒有了 finalize reference 與之對應,所以 finalize 辦法不會再履行。

3、含有Finalize辦法的object須要至多經由兩輪GC才有能夠被釋放。

資本未封閉形成的內存洩露

關於應用了BraodcastReceiver,ContentObserver,File,游標 Cursor,Stream,Bitmap等資本的應用,應當在Activity燒毀時實時封閉或許刊出,不然這些資本將不會被收受接管,形成內存洩露。

一些不良代碼形成的內存壓力

有些代碼其實不形成內存洩漏,然則它們,或是對沒應用的內存沒停止有用實時的釋放,或是沒有有用的應用已有的對象而是頻仍的請求新內存。

好比:

Bitmap 沒挪用 recycle()辦法,關於 Bitmap 對象在不應用時,我們應當先挪用 recycle() 釋放內存,然後才它設置為 null. 由於加載 Bitmap 對象的內存空間,一部門是 java 的,一部門 C 的(由於 Bitmap 分派的底層是經由過程 JNI 挪用的 )。 而這個 recyle() 就是針對 C 部門的內存釋放。
結構 Adapter 時,沒有應用緩存的 convertView ,每次都在創立新的 converView。這裡推舉應用 ViewHolder。

總結

對 Activity 等組件的援用應當掌握在 Activity 的性命周期以內; 假如不克不及就斟酌應用 getApplicationContext 或許 getApplication,以免 Activity 被內部永生命周期的對象援用而洩漏。

盡可能不要在靜態變量或許靜態外部類中應用非靜態內部成員變量(包含context ),即便要應用,也要斟酌合時把內部成員變量置空;也能夠在外部類中應用弱援用來援用內部類的變量。

關於性命周期比Activity長的外部類對象,而且外部類中應用了內部類的成員變量,可以如許做防止內存洩露:

將外部類改成靜態外部類

靜態外部類中應用弱援用來援用內部類的成員變量
Handler 的持有的援用對象最好應用弱援用,資本釋放時也能夠清空 Handler 外面的新聞。好比在 Activity onStop 或許 onDestroy 的時刻,撤消失落該 Handler 對象的 Message和 Runnable.

在 Java 的完成進程中,也要斟酌其對象釋放,最好的辦法是在不應用某對象時,顯式地將此對象賦值為 null,好比應用完Bitmap 後先挪用 recycle(),再賦為null,清空對圖片等資本有直接援用或許直接援用的數組(應用 array.clear() ; array = null)等,最好遵守誰創立誰釋放的准繩。

准確封閉資本,關於應用了BraodcastReceiver,ContentObserver,File,游標 Cursor,Stream,Bitmap等資本的應用,應當在Activity燒毀時實時封閉或許刊出。

堅持對對象性命周期的敏感,特殊留意單例、靜態對象、全局性聚集等的性命周期。

以上所述是小編給年夜家引見的Java中關於內存洩露湧現的緣由匯總及若何防止內存洩露(超具體版),願望對年夜家有所贊助,假如年夜家有任何疑問請給我留言,小編會實時答復年夜家的。在此也異常感激年夜家對網站的支撐!

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved