Java 程序運(yùn)行時的內(nèi)存分配策略有三種,分別是靜態(tài)分配,棧式分配,和堆式分配堤框,對應(yīng)的,三種存儲策略使用的內(nèi)存空間主要分別是靜態(tài)存儲區(qū)(也稱方法區(qū))纵柿、棧區(qū)和堆區(qū)蜈抓。
靜態(tài)存儲區(qū)(方法區(qū)):主要存放靜態(tài)數(shù)據(jù)、全局 static 數(shù)據(jù)和常量昂儒。這塊內(nèi)存在程序編譯時就已經(jīng)分配好资昧,并且在程序整個運(yùn)行期間都存在。
棧區(qū) :當(dāng)方法被執(zhí)行時荆忍,方法體內(nèi)的局部變量(其中包括基礎(chǔ)數(shù)據(jù)類型、對象的引用)都在棧上創(chuàng)建撤缴,并在方法執(zhí)行結(jié)束時這些局部變量所持有的內(nèi)存將會自動被釋放刹枉。因為棧內(nèi)存分配運(yùn)算內(nèi)置于處理器的指令集中,效率很高屈呕,但是分配的內(nèi)存容量有限微宝。
堆區(qū) : 又稱動態(tài)內(nèi)存分配,通常就是指在程序運(yùn)行時直接 new 出來的內(nèi)存虎眨,也就是對象的實例蟋软。這部分內(nèi)存在不使用時將會由 Java 垃圾回收器來負(fù)責(zé)回收。
棧與堆的區(qū)別:
1.堆內(nèi)存用來存放由new創(chuàng)建的對象和數(shù)組嗽桩。
2.棧內(nèi)存用來存放方法或者局部變量等
3.堆是先進(jìn)先出岳守,后進(jìn)后出
4.棧是后進(jìn)先出,先進(jìn)后出
demo:
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扑庞,而它自己存在于棧中譬重。
結(jié)論:
局部變量的基本數(shù)據(jù)類型和引用存儲于棧中,引用的對象實體存儲于堆中罐氨⊥喂妫—— 因為它們屬于方法中的變量,生命周期隨方法而結(jié)束栅隐。
成員變量全部存儲與堆中(包括基本數(shù)據(jù)類型塔嬉,引用和引用的對象實體)—— 因為它們屬于類玩徊,類對象終究是要被new出來使用的。
2.Java內(nèi)存泄漏引起的原因
內(nèi)存泄漏是指無用對象(不再使用的對象)持續(xù)占有內(nèi)存或無用對象的內(nèi)存得不到及時釋放邑遏,從而造成內(nèi)存空間的浪費(fèi)稱為內(nèi)存泄漏佣赖。內(nèi)存泄露有時不嚴(yán)重且不易察覺,這樣開發(fā)者就不知道存在內(nèi)存泄露记盒,但有時也會很嚴(yán)重憎蛤,會提示你Out of memory。
Android中常見的內(nèi)存泄漏匯總
1.單例造成的內(nèi)存泄漏
由于單例的靜態(tài)特性使得其生命周期跟應(yīng)用的生命周期一樣長纪吮,所以如果使用不恰當(dāng)?shù)脑捔┟剩苋菀自斐蓛?nèi)存泄漏。比如下面一個典型的例子
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;
}
}
這是一個普通的單例模式碾盟,當(dāng)創(chuàng)建這個單例的時候棚辽,由于需要傳入一個Context,所以這個Context的生命周期的長短至關(guān)重要:
1冰肴、如果此時傳入的是 Application 的 Context屈藐,因為 Application 的生命周期就是整個應(yīng)用的生命周期,所以這將沒有任何問題熙尉。
2联逻、如果此時傳入的是 Activity 的 Context,當(dāng)這個 Context 所對應(yīng)的 Activity 退出時检痰,由于該 Context 的引用被單例對象所持有包归,其生命周期等于整個應(yīng)用程序的生命周期,所以當(dāng)前 Activity 退出時它的內(nèi)存并不會被回收铅歼,這就造成泄漏了公壤。
2.匿名內(nèi)部類/非靜態(tài)內(nèi)部類和異步線程
非靜態(tài)內(nèi)部類創(chuàng)建靜態(tài)實例造成的內(nèi)存泄漏
比如:有的時候我們可能會在啟動頻繁的Activity中,為了避免重復(fù)創(chuàng)建相同的數(shù)據(jù)資源椎椰,可能會出現(xiàn)這種寫法:
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內(nèi)部創(chuàng)建了一個非靜態(tài)內(nèi)部類的單例厦幅,每次啟動Activity時都會使用該單例的數(shù)據(jù),這樣雖然避免了資源的重復(fù)創(chuàng)建慨飘,不過這種寫法卻會造成內(nèi)存泄漏慨削,因為非靜態(tài)內(nèi)部類默認(rèn)會持有外部類的引用,而該非靜態(tài)內(nèi)部類又創(chuàng)建了一個靜態(tài)的實例套媚,該實例的生命周期和應(yīng)用的一樣長缚态,這就導(dǎo)致了該靜態(tài)實例一直會持有該Activity的引用,導(dǎo)致Activity的內(nèi)存資源不能正车塘觯回收玫芦。
3.匿名內(nèi)部類
public class MainActivity extends Activity {
...
Runnable ref1 = new MyRunable();
Runnable ref2 = new Runnable() {
@Override
public void run() {
}
};
...
}
ref1和ref2的區(qū)別是,ref2使用了匿名內(nèi)部類本辐。我們來看看運(yùn)行時這兩個引用的內(nèi)存:
可以看到桥帆,ref1沒什么特別的医增。
但ref2這個匿名類的實現(xiàn)對象里面多了一個引用:
this$0這個引用指向MainActivity.this,也就是說當(dāng)前的MainActivity實例會被ref2持有老虫,如果將這個引用再傳入一個異步線程叶骨,此線程和此Acitivity生命周期不一致的時候,就造成了Activity的泄露祈匙。
總結(jié):因為匿名內(nèi)部類handler是持有外面Activity的引用的
a 匿名內(nèi)部類忽刽、非靜態(tài)內(nèi)部類都會隱性持有外部類引用,而靜態(tài)內(nèi)部類和匿名類的靜態(tài)實例是不會持有外部類引用的夺欲。
b 遇到生命周期比activity長的情形跪帝,內(nèi)存泄漏一定是個隱患。
c 盡量新建一個文件定義類亦或者利用弱引用拿到外部類引用些阅。
4.Handler 造成的內(nèi)存泄漏
Handler 的使用造成的內(nèi)存泄漏問題應(yīng)該說是最為常見了
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分鐘執(zhí)行的消息 Message伞剑,mLeakyHandler 將其 push 進(jìn)了消息隊列 MessageQueue 里。當(dāng)該 Activity 被 finish() 掉時市埋,延遲執(zhí)行任務(wù)的 Message 還會繼續(xù)存在于主線程中黎泣,它持有該 Activity 的 Handler 引用,所以此時 finish() 掉的 Activity 就不會被回收了從而造成內(nèi)存泄漏(因 Handler 為非靜態(tài)內(nèi)部類缤谎,它會持有外部類的引用聘裁,在這里就是指 SampleActivity)。
修復(fù)方法:在 Activity 中避免使用非靜態(tài)內(nèi)部類弓千,比如上面我們將 Handler 聲明為靜態(tài)的,則其存活期跟 Activity 的生命周期就無關(guān)了献起。同時通過弱引用的方式引入 Activity洋访,避免直接將 Activity 作為 context 傳進(jìn)去,見下面代碼:
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();
}
}
5.資源未關(guān)閉造成的內(nèi)存泄漏
對于使用了BraodcastReceiver谴餐,ContentObserver姻政,F(xiàn)ile,游標(biāo) Cursor岂嗓,Stream汁展,Bitmap等資源的使用,應(yīng)該在Activity銷毀時及時關(guān)閉或者注銷厌殉,否則這些資源將不會被回收食绿,造成內(nèi)存泄漏。
6.盡量避免使用 static 成員變量
如果成員變量被聲明為 static公罕,那我們都知道其生命周期將與整個app進(jìn)程生命周期一樣器紧。
這會導(dǎo)致一系列問題,如果你的app進(jìn)程設(shè)計上是長駐內(nèi)存的楼眷,那即使app切到后臺铲汪,這部分內(nèi)存也不會被釋放熊尉。按照現(xiàn)在手機(jī)app內(nèi)存管理機(jī)制,占內(nèi)存較大的后臺進(jìn)程將優(yōu)先回收掌腰,yi'wei如果此app做過進(jìn)程互保闭。活,那會造成app在后臺頻繁重啟齿梁。當(dāng)手機(jī)安裝了你參與開發(fā)的app以后一夜時間手機(jī)被消耗空了電量催植、流量,你的app不得不被用戶卸載或者靜默士飒。