Android中的內(nèi)存管理機(jī)制
分配機(jī)制
Android為每個(gè)進(jìn)程分配內(nèi)存的時(shí)候,采用了彈性的分配方式郎嫁,也就是剛開始并不會(huì)一下分配很多內(nèi)存給每個(gè)進(jìn)程秉继,而是給每一個(gè)進(jìn)程分配一個(gè)“夠用”的量。這個(gè)量是根據(jù)每一個(gè)設(shè)備實(shí)際的物理內(nè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分配無限大小的內(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)程,來回收足夠的內(nèi)存置谦。但是Android也不是隨便殺死一個(gè)進(jìn)程堂鲤,比如說一個(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ì)被殺死
- 可見進(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)開始?xì)⑺繪RU緩存中的進(jìn)程時(shí),系統(tǒng)會(huì)判斷每個(gè)進(jìn)程殺死后帶來的回收收益冠摄。因?yàn)锳ndroid總是傾向于殺死一個(gè)能回收更多內(nèi)存的進(jìn)程糯崎,從而可以殺死更少的進(jìn)程,來獲取更多的內(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不可見的時(shí)候食拜,釋放掉一些只有UI使用的資源。系統(tǒng)會(huì)根據(jù)onTrimMemory()回調(diào)方法的TRIM_MEMORY_UI_HIDDEN等級(jí)的事件副编,來通知App UI已經(jīng)隱藏了负甸。這和onStop()方法還是有很大區(qū)別的,因?yàn)閛nStop()方法只是當(dāng)一個(gè)Activity完全不可見的時(shí)候就會(huì)調(diào)用,比如說用戶打開了我們程序中的另一個(gè)Activity呻待。因此打月,我們可以在onStop()方法中去釋放一些Activity相關(guān)的資源,比如說取消網(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)方法來通知內(nèi)存緊張的狀態(tài)肺稀,App應(yīng)該根據(jù)不同的內(nèi)存緊張等級(jí),來合理的釋放資源应民,以保證系統(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è)備。通過getMemoryClass()和getLargeMemoryClass()來獲取可用內(nèi)存大小的信息蔓倍。
- 避免濫用Bitmap導(dǎo)致的內(nèi)存浪費(fèi)悬钳。
根據(jù)當(dāng)前設(shè)備的分辨率來壓縮Bitmap是一個(gè)不錯(cuò)的選擇,在使用完Bitmap后偶翅,記得要使用recycle()來釋放掉Bitmap默勾。使用軟引用或者弱引用來引用一個(gè)Bitmap,使用LRU緩存來對(duì)Bitmap進(jìn)行緩存聚谁。 - 使用針對(duì)內(nèi)存優(yōu)化過的數(shù)據(jù)容器母剥。針對(duì)移動(dòng)設(shè)備內(nèi)存有限的問題,Android提供了一套針對(duì)內(nèi)存優(yōu)化過的數(shù)據(jù)容器形导,來替代JDK原生提供的數(shù)據(jù)容器环疼。但是缺點(diǎn)就是,時(shí)間復(fù)雜度被提高了朵耕。比如SparseArray炫隶、SparseBooleanArray、LongSparseArray阎曹、
- 意識(shí)到內(nèi)存的過度消耗伪阶。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ì)帶來更多的內(nèi)存消耗坛缕。如果你的“抽象”設(shè)計(jì)實(shí)際上并沒有帶來多大好處墓猎,那么就不要使用它。
- 使用nano protobufs 來序列化數(shù)據(jù)祷膳。Google設(shè)計(jì)的一個(gè)語(yǔ)言和平臺(tái)中立打的序列化協(xié)議陶衅,比XML更快、更小直晨、更簡(jiǎn)單搀军。
- 避免使用依賴注入的框架。依賴注入的框架需要開啟額外的服務(wù)勇皇,來掃描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)捕虽,比方說thread stack中的變量慨丐,JNI中的全局變量,zygote中的對(duì)象(class loader加載)等泄私,然后開始對(duì)heap進(jìn)行遍歷房揭。到最后,部分沒有直接或者間接引用到GC Roots的就是需要回收的垃圾晌端,會(huì)被GC回收掉捅暴。如下圖藍(lán)色部分。
內(nèi)存泄漏指的是進(jìn)程中某些對(duì)象(垃圾對(duì)象)已經(jīng)沒有使用價(jià)值了咧纠,但是它們卻可以直接或間接地引用到gc roots導(dǎo)致無法被GC回收蓬痒。無用的對(duì)象占據(jù)著內(nèi)存空間,使得實(shí)際可使用內(nèi)存變小漆羔,形象地說法就是內(nèi)存泄漏了梧奢。下面分析一些可能導(dǎo)致內(nèi)存泄漏的情景瞪讼。
Android中常見的內(nèi)存泄漏原因
1.使用static變量引起的內(nèi)存泄漏
因?yàn)閟tatic變量的生命周期是在類加載時(shí)開始 類卸載時(shí)結(jié)束,也就是說static變量是在程序進(jìn)程死亡時(shí)才釋放粹断,如果在static變量中引用了Activity那么這個(gè)Activity由于被引用,便會(huì)隨static變量的生命周期一樣嫡霞,一直無法被釋放瓶埋,造成內(nèi)存泄漏。
一般解決辦法:
想要避免context相關(guān)的內(nèi)存泄漏诊沪,需要注意以下幾點(diǎn):
- 不要對(duì)activity的context長(zhǎng)期引用(一個(gè)activity的引用的生存周期應(yīng)該和activity的生命周期相同)
- 如果可以的話养筒,盡量使用關(guān)于application的context來替代和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)存泄漏的問題巫湘,非靜態(tài)內(nèi)部類的靜態(tài)實(shí)例導(dǎo)致內(nèi)存泄漏。
/**
mDemo會(huì)獲得并一直持有MemoryLeakActivity的引用昏鹃。當(dāng)MemoryLeakActivity銷毀后重建尚氛,因?yàn)閙Demo持有引用,無法被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ì)象中訪問非靜態(tài)的外圍類對(duì)象怠晴。
2.線程引起的內(nèi)存泄漏
下面的代碼存在內(nèi)存泄漏的問題遥金,啟動(dòng)線程的匿名內(nèi)部類會(huì)持有MemoryLeakActivity.this的引用。如果線程還沒有結(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來保存Activity實(shí)例 因?yàn)槿跻?只要GC發(fā)現(xiàn)了 就會(huì)回收它 页眯,因此可盡快回收。
將匿名內(nèi)部類改成靜態(tài)類厢呵,避免了Activity context的內(nèi)存泄漏問題
/**
* 示例通過將線程類聲明為私有的靜態(tài)內(nèi)部類避免了 Activity context 的內(nèi)存泄漏問題窝撵,但
* 在配置發(fā)生改變后,線程仍然會(huì)執(zhí)行襟铭。原因在于碌奉,DVM 虛擬機(jī)持有所有運(yùn)行線程的引用,無論
* 這些線程是否被回收寒砖,都與 Activity 的生命周期無關(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í)間沒被取出執(zhí)行咐汞,那么由于 Message中有 Handler的引用,而 Handler 一般來說也是內(nèi)部類對(duì)象儒鹿,Message引用 Handler 碉考,Handler引用 Activity 這樣 使得 Activity無法回收⊥ι恚或者說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 沒有及時(shí)close 會(huì)保存有Activity的引用府寒,導(dǎo)致內(nèi)存泄漏
解決辦法:
在onDestory方法中及時(shí) close即可
5.BitMap占用過多內(nèi)存
bitmap的解析需要占用內(nèi)存魁衙,但是內(nèi)存只提供8M的空間給BitMap,如果圖片過多株搔,并且沒有及時(shí) recycle bitmap 那么就會(huì)造成內(nèi)存溢出剖淀。
解決辦法:
及時(shí)recycle 壓縮圖片之后加載圖片
其中還有一些關(guān)于 集合對(duì)象沒移除,注冊(cè)的對(duì)象沒反注冊(cè)纤房,代碼壓力的問題也可能產(chǎn)生內(nèi)存泄漏纵隔,但是使用上述的幾種解決辦法一般都是可以解決的。