Android中的內(nèi)存管理機(jī)制
分配機(jī)制
Android為每個(gè)進(jìn)程分配內(nèi)存的時(shí)候,采用了彈性的分配方式,也就是剛開(kāi)始并不會(huì)一下分配很多內(nèi)存給每個(gè)進(jìn)程,而是給每一個(gè)進(jìn)程分配一個(gè)“夠用”的量。這個(gè)量是根據(jù)每一個(gè)設(shè)備實(shí)際的物理內(nèi)存大小來(lái)決定的。隨著應(yīng)用的運(yùn)行,可能會(huì)發(fā)現(xiàn)當(dāng)前的內(nèi)存可能不夠使用了,這時(shí)候Android又會(huì)為每個(gè)進(jìn)程分配一些額外的內(nèi)存大小。但是這些額外的大小并不是隨意的,也是有限度的,系統(tǒng)不可能為每一個(gè)App分配無(wú)限大小的內(nèi)存。
Android系統(tǒng)的宗旨是最大限度的讓更多的進(jìn)程存活在內(nèi)存中,因?yàn)檫@樣的話,下一次用戶再啟動(dòng)應(yīng)用,不需要重新創(chuàng)建進(jìn)程,只需要恢復(fù)已有的進(jìn)程就可以了,減少了應(yīng)用的啟動(dòng)時(shí)間,提高了用戶體驗(yàn)。
回收機(jī)制
Android對(duì)內(nèi)存的使用方式是“盡最大限度的使用”,這一點(diǎn)繼承了Linux的優(yōu)點(diǎn)。Android會(huì)在內(nèi)存中保存盡可能多的數(shù)據(jù),即使有些進(jìn)程不再使用了,但是它的數(shù)據(jù)還被存儲(chǔ)在內(nèi)存中,所以Android現(xiàn)在不推薦顯式的“退出”應(yīng)用。因?yàn)檫@樣,當(dāng)用戶下次再啟動(dòng)應(yīng)用的時(shí)候,只需要恢復(fù)當(dāng)前進(jìn)程就可以了,不需要重新創(chuàng)建進(jìn)程,這樣就可以減少應(yīng)用的啟動(dòng)時(shí)間。只有當(dāng)Android系統(tǒng)發(fā)現(xiàn)內(nèi)存不夠使用,需要回收內(nèi)存的時(shí)候,Android系統(tǒng)就會(huì)需要?dú)⑺榔渌M(jìn)程,來(lái)回收足夠的內(nèi)存。但是Android也不是隨便殺死一個(gè)進(jìn)程,比如說(shuō)一個(gè)正在與用戶交互的進(jìn)程,這種后果是可怕的。所以Android會(huì)有限清理那些已經(jīng)不再使用的進(jìn)程,以保證最小的副作用。
Android殺死進(jìn)程有兩個(gè)參考條件:
進(jìn)程優(yōu)先級(jí):
Android為每一個(gè)進(jìn)程分配了優(yōu)先級(jí)的概念,優(yōu)先級(jí)越低的進(jìn)程,被殺死的概率就更大。Android中總共有5個(gè)進(jìn)程優(yōu)先級(jí)。具體含義這里不再給出。
- 前臺(tái)進(jìn)程:正常不會(huì)被殺死
- 可見(jiàn)進(jìn)程:正常不會(huì)被殺死
- 服務(wù)進(jìn)程:正常不會(huì)被殺死
- 后臺(tái)進(jìn)程:存放于一個(gè)LRU緩存列表中,先殺死處于列表尾部的進(jìn)程
- 空進(jìn)程:正常情況下,為了平衡系統(tǒng)整體性能,Android不保存這些進(jìn)程
回收收益:
當(dāng)Android系統(tǒng)開(kāi)始?xì)⑺繪RU緩存中的進(jìn)程時(shí),系統(tǒng)會(huì)判斷每個(gè)進(jìn)程殺死后帶來(lái)的回收收益。因?yàn)锳ndroid總是傾向于殺死一個(gè)能回收更多內(nèi)存的進(jìn)程,從而可以殺死更少的進(jìn)程,來(lái)獲取更多的內(nèi)存。殺死的進(jìn)程越少,對(duì)用戶體驗(yàn)的影響就越小。
官方推薦的App內(nèi)存使用方式
- 當(dāng)Service完成任務(wù)后,盡量停止它。因?yàn)橛蠸ervice組件的進(jìn)程,優(yōu)先級(jí)最低也是服務(wù)進(jìn)程,這會(huì)影響到系統(tǒng)的內(nèi)存回收。IntentService可以很好地完成這個(gè)任務(wù)。
- 在UI不可見(jiàn)的時(shí)候,釋放掉一些只有UI使用的資源。系統(tǒng)會(huì)根據(jù)onTrimMemory()回調(diào)方法的TRIM_MEMORY_UI_HIDDEN等級(jí)的事件,來(lái)通知App UI已經(jīng)隱藏了。這和onStop()方法還是有很大區(qū)別的,因?yàn)閛nStop()方法只是當(dāng)一個(gè)Activity完全不可見(jiàn)的時(shí)候就會(huì)調(diào)用,比如說(shuō)用戶打開(kāi)了我們程序中的另一個(gè)Activity。因此,我們可以在onStop()方法中去釋放一些Activity相關(guān)的資源,比如說(shuō)取消網(wǎng)絡(luò)連接或者注銷廣播接收器等,但是UI相關(guān)的資源等onTrimMemory(TRIM_MEMORY_UI_HIDDEN)這個(gè)回調(diào)之后才去釋放,這樣可以保證如果用戶只是從我們程序的一個(gè)Activity回到了另外一個(gè)Activity,界面相關(guān)的資源都不需要重新加載,從而提升響應(yīng)速度。
@Override
public void onTrimMemory(int level) {
super.onTrimMemory(level);
switch (level) {
case TRIM_MEMORY_UI_HIDDEN:
// 進(jìn)行資源釋放操作
break;
}
}
- 在系統(tǒng)內(nèi)存緊張的時(shí)候,盡可能多的釋放掉一些非重要資源。系統(tǒng)會(huì)根據(jù)onTrimMemory()回調(diào)方法來(lái)通知內(nèi)存緊張的狀態(tài),App應(yīng)該根據(jù)不同的內(nèi)存緊張等級(jí),來(lái)合理的釋放資源,以保證系統(tǒng)能夠回收更多內(nèi)存。當(dāng)系統(tǒng)回收到足夠多的內(nèi)存時(shí),就不用殺死進(jìn)程了。
- 檢查自己最大可用的內(nèi)存大小。這對(duì)一些緩存框架很有用,因?yàn)檎G闆r下,緩存框架的緩存池大小應(yīng)當(dāng)指定為最大內(nèi)存的百分比,這樣才能更好地適配更多的設(shè)備。通過(guò)getMemoryClass()和getLargeMemoryClass()來(lái)獲取可用內(nèi)存大小的信息。
- 避免濫用Bitmap導(dǎo)致的內(nèi)存浪費(fèi)。
根據(jù)當(dāng)前設(shè)備的分辨率來(lái)壓縮Bitmap是一個(gè)不錯(cuò)的選擇,在使用完Bitmap后,記得要使用recycle()來(lái)釋放掉Bitmap。使用軟引用或者弱引用來(lái)引用一個(gè)Bitmap,使用LRU緩存來(lái)對(duì)Bitmap進(jìn)行緩存。 - 使用針對(duì)內(nèi)存優(yōu)化過(guò)的數(shù)據(jù)容器。針對(duì)移動(dòng)設(shè)備內(nèi)存有限的問(wèn)題,Android提供了一套針對(duì)內(nèi)存優(yōu)化過(guò)的數(shù)據(jù)容器,來(lái)替代JDK原生提供的數(shù)據(jù)容器。但是缺點(diǎn)就是,時(shí)間復(fù)雜度被提高了。比如SparseArray、SparseBooleanArray、LongSparseArray、
- 意識(shí)到內(nèi)存的過(guò)度消耗。Enum類型占用的內(nèi)存是常量的兩倍多,所以避免使用enum,直接使用常量。
每一個(gè)Java的類(包括匿名內(nèi)部類)都需要500Byte的代碼。每一個(gè)類的實(shí)例都有12-16 Byte的額外內(nèi)存消耗。注意類似于HashMap這種,內(nèi)部還需要生成Class的數(shù)據(jù)容器,這會(huì)消耗更多內(nèi)存。 - 抽象代碼也會(huì)帶來(lái)更多的內(nèi)存消耗。如果你的“抽象”設(shè)計(jì)實(shí)際上并沒(méi)有帶來(lái)多大好處,那么就不要使用它。
- 使用nano protobufs 來(lái)序列化數(shù)據(jù)。Google設(shè)計(jì)的一個(gè)語(yǔ)言和平臺(tái)中立打的序列化協(xié)議,比XML更快、更小、更簡(jiǎn)單。
- 避免使用依賴注入的框架。依賴注入的框架需要開(kāi)啟額外的服務(wù),來(lái)掃描App中代碼的Annotation,所以需要額外的系統(tǒng)資源。
- 使用ZIP對(duì)齊的APK。對(duì)APK做Zip對(duì)齊,會(huì)壓縮其內(nèi)部的資源,運(yùn)行時(shí)會(huì)占用更少的內(nèi)存。
- 合理使用多進(jìn)程。
Android內(nèi)存泄漏分析及優(yōu)化
內(nèi)存泄漏的根本原因

如上圖所示,GC會(huì)選擇一些它了解還存活的對(duì)象作為內(nèi)存遍歷的根節(jié)點(diǎn)(GC Roots),比方說(shuō)thread stack中的變量,JNI中的全局變量,zygote中的對(duì)象(class loader加載)等,然后開(kāi)始對(duì)heap進(jìn)行遍歷。到最后,部分沒(méi)有直接或者間接引用到GC Roots的就是需要回收的垃圾,會(huì)被GC回收掉。如下圖藍(lán)色部分。

內(nèi)存泄漏指的是進(jìn)程中某些對(duì)象(垃圾對(duì)象)已經(jīng)沒(méi)有使用價(jià)值了,但是它們卻可以直接或間接地引用到gc roots導(dǎo)致無(wú)法被GC回收。無(wú)用的對(duì)象占據(jù)著內(nèi)存空間,使得實(shí)際可使用內(nèi)存變小,形象地說(shuō)法就是內(nèi)存泄漏了。下面分析一些可能導(dǎo)致內(nèi)存泄漏的情景。
Android中常見(jiàn)的內(nèi)存泄漏原因
1.使用static變量引起的內(nèi)存泄漏
因?yàn)閟tatic變量的生命周期是在類加載時(shí)開(kāi)始 類卸載時(shí)結(jié)束,也就是說(shuō)static變量是在程序進(jìn)程死亡時(shí)才釋放,如果在static變量中引用了Activity那么這個(gè)Activity由于被引用,便會(huì)隨static變量的生命周期一樣,一直無(wú)法被釋放,造成內(nèi)存泄漏。
一般解決辦法:
想要避免context相關(guān)的內(nèi)存泄漏,需要注意以下幾點(diǎn):
- 不要對(duì)activity的context長(zhǎng)期引用(一個(gè)activity的引用的生存周期應(yīng)該和activity的生命周期相同)
- 如果可以的話,盡量使用關(guān)于application的context來(lái)替代和activity相關(guān)的context
- 如果一個(gè)acitivity的非靜態(tài)內(nèi)部類的生命周期不受控制,那么避免使用它;正確的方法是使用一個(gè)靜態(tài)的內(nèi)部類,并且對(duì)它的外部類有一WeakReference,就像在ViewRootImpl中內(nèi)部類W所做的那樣,使用弱引用private final WeakReference<ViewRootImpl> mViewAncestor;。
下面的代碼存在內(nèi)存泄漏的問(wèn)題,非靜態(tài)內(nèi)部類的靜態(tài)實(shí)例導(dǎo)致內(nèi)存泄漏。
/**
mDemo會(huì)獲得并一直持有MemoryLeakActivity的引用。當(dāng)MemoryLeakActivity銷毀后重建,因?yàn)閙Demo持有引用,無(wú)法被GC回收的,進(jìn)程中會(huì)存在2個(gè)MemoryLeakActivity實(shí)例。所以,對(duì)于lauchMode不是singleInstance的Activity, 應(yīng)該避免在activity里面實(shí)例化其非靜態(tài)內(nèi)部類的靜態(tài)實(shí)例。
*/
public class MemoryLeakActivity extends AppCompatActivity{
private TextView view;
private static final String TAG = "MemoryLeakActivity";
static Demo mDemo;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
view = new TextView(MemoryLeakActivity.this);
view.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
view.setText("啟動(dòng)一個(gè)持有本對(duì)象的線程");
view.setTextSize(40);
view.setTextColor(Color.parseColor("#0000ff"));
setContentView(view);
mDemo = new Demo();
mDemo.run();
}
class Demo{
void run(){
Log.i(TAG, "run: ");
}
}
}
解決方法:將Demo改成靜態(tài)內(nèi)部類
因?yàn)槠胀ǖ膬?nèi)部類對(duì)象隱含地保存了一個(gè)引用,指向創(chuàng)建它的外圍類對(duì)象。然而,當(dāng)內(nèi)部類是static的時(shí),就不是這樣了。嵌套類意味著: 1. 嵌套類的對(duì)象,并不需要其外圍類的對(duì)象。 2. 不能從嵌套類的對(duì)象中訪問(wèn)非靜態(tài)的外圍類對(duì)象。
2.線程引起的內(nèi)存泄漏
下面的代碼存在內(nèi)存泄漏的問(wèn)題,啟動(dòng)線程的匿名內(nèi)部類會(huì)持有MemoryLeakActivity.this的引用。如果線程還沒(méi)有結(jié)束,Activity已經(jīng)銷毀那就會(huì)造成內(nèi)存泄漏。
public class MemoryLeakActivity extends AppCompatActivity{
private TextView view;
private static final String TAG = "MemoryLeakActivity";
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
view = new TextView(MemoryLeakActivity.this);
view.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
view.setText("啟動(dòng)一個(gè)持有本對(duì)象的線程");
view.setTextSize(40);
view.setTextColor(Color.parseColor("#0000ff"));
setContentView(view);
Runnable runnable = new Runnable() {
@Override
public void run() {
try {
Thread.sleep(8000000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
new Thread(runnable).start();
}
}
解決辦法:
1.合理安排線程執(zhí)行的時(shí)間,控制線程在Activity結(jié)束前結(jié)束。
2.將內(nèi)部類改為靜態(tài)內(nèi)部類,并使用弱引用WeakReference來(lái)保存Activity實(shí)例 因?yàn)槿跻?只要GC發(fā)現(xiàn)了 就會(huì)回收它 ,因此可盡快回收。
將匿名內(nèi)部類改成靜態(tài)類,避免了Activity context的內(nèi)存泄漏問(wèn)題
/**
* 示例通過(guò)將線程類聲明為私有的靜態(tài)內(nèi)部類避免了 Activity context 的內(nèi)存泄漏問(wèn)題,但
* 在配置發(fā)生改變后,線程仍然會(huì)執(zhí)行。原因在于,DVM 虛擬機(jī)持有所有運(yùn)行線程的引用,無(wú)論
* 這些線程是否被回收,都與 Activity 的生命周期無(wú)關(guān)。運(yùn)行中的線程只會(huì)繼續(xù)運(yùn)行,直到
* Android 系統(tǒng)將整個(gè)應(yīng)用進(jìn)程殺死
*/
public class MemoryLeakActivity extends AppCompatActivity{
private TextView view;
private static final String TAG = "MemoryLeakActivity";
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
view = new TextView(MemoryLeakActivity.this);
view.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
view.setText("啟動(dòng)一個(gè)持有本對(duì)象的線程");
view.setTextSize(40);
view.setTextColor(Color.parseColor("#0000ff"));
setContentView(view);
new MyThread().start();
}
private static class MyThread extends Thread{
@Override
public void run() {
try {
Thread.sleep(8000000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
在Activity生命周期的onDestory中結(jié)束線程運(yùn)行
/**
* 除了我們需要實(shí)現(xiàn)銷毀邏輯以保證線程不會(huì)發(fā)生內(nèi)存泄漏。在退出當(dāng)前
* Activity 前使用 onDestroy() 方法結(jié)束你的運(yùn)行中線程。
*/
public class MemoryLeakActivity extends AppCompatActivity{
private TextView view;
private static final String TAG = "MemoryLeakActivity";
private static boolean mRunnale = false;
private MyThread mThread;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
view = new TextView(MemoryLeakActivity.this);
view.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
view.setText("啟動(dòng)一個(gè)持有本對(duì)象的線程");
view.setTextSize(40);
view.setTextColor(Color.parseColor("#0000ff"));
setContentView(view);
new MyThread().start();
}
@Override
protected void onDestroy() {
super.onDestroy();
mThread.closeThread();
}
private static class MyThread extends Thread{
@Override
public void run() {
mRunnale = true;
while (true){
//TODO
Log.i(TAG, "run: do something");
}
}
public void closeThread(){
mRunnale = false;
}
}
}
3.Handler的使用造成的內(nèi)存泄漏
由于在Handler的使用中,handler會(huì)發(fā)送message對(duì)象到 MessageQueue中 然后 Looper會(huì)輪詢MessageQueue 然后取出Message執(zhí)行,但是如果一個(gè)Message長(zhǎng)時(shí)間沒(méi)被取出執(zhí)行,那么由于 Message中有 Handler的引用,而 Handler 一般來(lái)說(shuō)也是內(nèi)部類對(duì)象,Message引用 Handler ,Handler引用 Activity 這樣 使得 Activity無(wú)法回收?;蛘哒f(shuō)Handler在Activity退出時(shí)依然還有消息需要處理,那么這個(gè)Activity就不會(huì)被回收。
解決辦法:
依舊使用 靜態(tài)內(nèi)部類+弱引用的方式 可解決
例如下面的代碼
public class MemoryLeakActivity extends AppCompatActivity{
private TextView view;
private static final String TAG = "MemoryLeakActivity";
private MyHandler mHandler;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
view = new TextView(MemoryLeakActivity.this);
view.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
view.setText("啟動(dòng)一個(gè)持有本對(duì)象的線程");
view.setTextSize(40);
view.setTextColor(Color.parseColor("#0000ff"));
setContentView(view);
mHandler = new MyHandler(this);
mHandler.sendEmptyMessage(0);
}
@Override
protected void onDestroy() {
super.onDestroy();
//第三步,在Activity退出的時(shí)候移除回調(diào)
mHandler.removeCallbacksAndMessages(null);
}
//第一步,將Handler改成靜態(tài)內(nèi)部類。
static class MyHandler extends Handler{
//第二步,將需要引用Activity的地方,改成弱引用。
private WeakReference<MemoryLeakActivity> mActivityRef;
public MyHandler(MemoryLeakActivity activity){
mActivityRef = new WeakReference<MemoryLeakActivity>(activity);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
MemoryLeakActivity mla = mActivityRef == null ? null : mActivityRef.get();
if(mla == null || mla.isFinishing()){
return;
}
//TODO
mla.view.setText("do something");
}
}
}
4.資源未被及時(shí)關(guān)閉造成的內(nèi)存泄漏
比如一些Cursor 沒(méi)有及時(shí)close 會(huì)保存有Activity的引用,導(dǎo)致內(nèi)存泄漏
解決辦法:
在onDestory方法中及時(shí) close即可
5.BitMap占用過(guò)多內(nèi)存
bitmap的解析需要占用內(nèi)存,但是內(nèi)存只提供8M的空間給BitMap,如果圖片過(guò)多,并且沒(méi)有及時(shí) recycle bitmap 那么就會(huì)造成內(nèi)存溢出。
解決辦法:
及時(shí)recycle 壓縮圖片之后加載圖片
其中還有一些關(guān)于 集合對(duì)象沒(méi)移除,注冊(cè)的對(duì)象沒(méi)反注冊(cè),代碼壓力的問(wèn)題也可能產(chǎn)生內(nèi)存泄漏,但是使用上述的幾種解決辦法一般都是可以解決的。