內(nèi)存優(yōu)化主要是分析內(nèi)存泄露和內(nèi)存溢出鲜滩。將從內(nèi)存是怎么分配伴鳖,內(nèi)存怎么出現(xiàn)泄露和溢出,用工具判斷什么情況出現(xiàn)泄露徙硅,找出泄露點榜聂,定位到代碼中,然后進行代碼優(yōu)化嗓蘑。還有一些工具介紹须肆,和內(nèi)存泄露的例子。
1.內(nèi)存分配
內(nèi)存分配策略
靜態(tài)
1.內(nèi)存在程序編譯的時候已經(jīng)分配好了桩皿,這塊內(nèi)存在程序運行期間一直存在豌汇。
2.主要放靜態(tài)數(shù)據(jù),全局static數(shù)據(jù)和一些常量
棧
1.在執(zhí)行函數(shù)或方法時泄隔,函數(shù)內(nèi)部變量存儲都放在棧中拒贱,函數(shù)執(zhí)行結(jié)束這些存儲單元自動釋放
2.棧內(nèi)存運算速度快,但是數(shù)量有限
堆
也叫動態(tài)內(nèi)存分配佛嬉,是通過對象new出來的對象實例
區(qū)別
1.堆是不連續(xù)的內(nèi)存區(qū)域柜思,空間比較大
2.棧是一塊連續(xù)的內(nèi)存區(qū)域,大小由操作系統(tǒng)決定巷燥,隊列的實現(xiàn)方式赡盘,先進后出。
使用
public class Main{
int a = 1;
Student s = new Student();
public void XXX(){
int b = 1;//棧里面
Student s2 = new Student();
}
}
1.成員變量全部存在堆中(包括基本類型缰揪,引用及引用的對象實體)----類的對象最終是被new出來的
2.局部變量數(shù)據(jù)類型和引用存儲在棧中陨享,引用的實體對象在堆中-----他們屬于方法中的變量葱淳。
2.內(nèi)存泄露和內(nèi)存溢出
內(nèi)存泄露
1.內(nèi)存不在gc的掌控中
2.當(dāng)一個對象已經(jīng)不須需要使用時,本該被回收抛姑,而另外一個正在使用的對象持有它的引用從而導(dǎo)致對象不能被回收赞厕。
3.這就導(dǎo)致了本該被回收的對象不能被回收停留里在堆中,產(chǎn)生泄露.
內(nèi)存溢出
1.進程中某些對象沒有使用價值定硝,但是他們卻還直接或間接被引用到皿桑,GC root無法回收,內(nèi)存泄露過多時蔬啡,在加上應(yīng)用本身占用的內(nèi)存诲侮,日積月累就會導(dǎo)致oom.
2.應(yīng)用占用資源超過Dalvik虛擬機分配的內(nèi)存就出現(xiàn)內(nèi)存溢出.
gc的回收機制
某對象不再有任何的引用的時候才會進行回收。
回收策略
1.標(biāo)記-清除算法(缺點會產(chǎn)生碎片)
2.復(fù)制算法
復(fù)制收集器將內(nèi)存分為兩塊一樣大小空間箱蟆,某一個時刻沟绪,只有一個空間處于活躍的狀態(tài),當(dāng)活躍的空間滿的時候空猜,GC就會將活躍的對象復(fù)制到未使用的空間中去绽慈,原來不活躍的空間就變?yōu)榱嘶钴S的空間。
優(yōu)點:
1 只掃描可以到達的對象辈毯,不需要掃描所有的對象坝疼,從而減少了應(yīng)用暫停的時間
缺點:
1.需要額外的空間消耗,某一個時刻谆沃,總是有一塊內(nèi)存處于未使用狀態(tài)
2.復(fù)制對象需要一定的開銷
3.標(biāo)記-整理算法
標(biāo)記整理收集器汲取了標(biāo)記清除和復(fù)制收集器的優(yōu)點裙士,它分兩個階段執(zhí)行,在第一個階段管毙,首先掃描所有活躍的對象腿椎,并標(biāo)記所有活躍的對象,第二個階段首先清除未標(biāo)記的對象夭咬,然后將活躍的的對象復(fù)制到堆得底部
該算法極大的減少了內(nèi)存碎片啃炸,并且不需要像復(fù)制算法一樣需要兩倍的空間。
標(biāo)記-整理算法采用標(biāo)記-清除算法一樣的方式進行對象的標(biāo)記卓舵,但在清除時不同南用,在回收不存活的對象占用的空間后,會將所有的存活對象往左端空閑空間移動掏湾,并更新對應(yīng)的指針裹虫。標(biāo)記-整理算法是在標(biāo)記-清除算法的基礎(chǔ)上,又進行了對象的移動融击,因此成本更高筑公,但是卻解決了內(nèi)存碎片的問題。
算法:lrucache(最近最少使用先回收)
java中的種引用類型
強引用設(shè)置成null就會弱化尊浪。arrayList中的使用的就是個例子
private transient Object[] elementData;
public void clear() {
modCount++;
// Let gc do its work
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
3.如何判斷有內(nèi)存泄露匣屡?及定位泄露點
1.確定是否存在內(nèi)存泄露
1.Android Monitors(android profiler)的內(nèi)存分析
A.Activity和view數(shù)量
1.(使用的是android studio 3.0以前版本封救,3.0后沒有找著這個按鈕)
當(dāng)app退出的時候砚蓬,這個進程里面所有的對象應(yīng)該就都被回收了崩掘,尤其是很容易被泄露的(View,Activity)是否還內(nèi)存當(dāng)中缘滥∪辏可以讓app退出以后惩坑,查看系統(tǒng)該進程里面的所有的View、Activity對象是否為0.
Android Studio--Android Monitor--System Information Memory--Usage查看Objects里面的views和Activity的數(shù)量是否為0.
2.也可以使用命令行進行也拜。
adb shell & dumpsys meminfo [包名]
B.最直觀的看內(nèi)存增長情況以舒,知道該動作是否發(fā)生內(nèi)存泄露。
2.先找懷疑對象(哪些對象屬于泄露的)
1.MAT對比操作前后的hprof來定位內(nèi)存泄露是泄露了什么數(shù)據(jù)對象搪泳。(這樣做可以排除一些對象,不用后面去查看所有被引用的對象是否是嫌疑)
2.快速定位到操作前后所持有的對象哪些是增加了(GC后還是比之前多出來的對象就可能是泄露對象嫌疑犯)
3.技巧:Histogram中還可以對對象進行Group扼脐,比如選擇Group By Package更方便查看自己Package中的對象信息岸军。
3.MAT分析hprof來定位內(nèi)存泄露的原因所在。(哪個對象持有了上面懷疑出來的發(fā)生泄露的對象)
1)Dump出內(nèi)存泄露“當(dāng)時”的內(nèi)存鏡像hprof瓦侮,分析懷疑泄露的類艰赞;
2)把上面2得出的這些嫌疑犯一個一個排查個遍。
(1)進入Histogram肚吏,過濾出某一個嫌疑對象類
(2)然后分析持有此類對象引用的外部對象(在該類上面點擊右鍵List Objects--->with incoming references)
(3)再過濾掉一些弱引用方妖、軟引用、虛引用罚攀,因為它們遲早可以被GC干掉不屬于內(nèi)存泄露 (在類上面點擊右鍵Merge Shortest Paths to GC Roots--->exclude all phantom/weak/soft etc.references)
(4)逐個分析每個對象的GC路徑是否正常此時就要進入代碼分析此時這個對象的引用持有是否合理党觅,這就要考經(jīng)驗和體力了!
到此就得到了對應(yīng)的錯誤函數(shù) CommUtil
4.查找代碼斋泄,優(yōu)化代碼
上面是查詢內(nèi)存泄露的一個流程杯瞻,可以按照自己的代碼去確定泄露點,然后進行優(yōu)化炫掐。
5.使用快捷的定位方式(leakcanary)
Square公司 可以直接在手機端查看內(nèi)存泄露的工具魁莉。
//LeakCanary原理總結(jié)
//1. 在Activity的onDestory()中,將該Activity添加到內(nèi)存泄漏監(jiān)控隊列中
//2. 在后臺線程檢查引用是否被清除募胃,如果沒有旗唁,調(diào)用GC
//3. 如果引用還是未被清除,把heap內(nèi)存dump到一個 .hprof 文件中
//4. 使用一個獨立進程中的服務(wù)計算出到GC Roots的最短強引用路徑來判斷是否發(fā)生Leak
//5. 建立導(dǎo)致泄漏的引用鏈痹束,結(jié)果在Log中打印出來
Application
install()
LeakCanary
androidWatcher()
RefWatcher
new AndroidWatcherExecutor() --->dumpHeap()/analyze()(--->runAnalysis())--->Hprof文件分析
new AndroidHeapDumper()
new ServiceHeapDumpListener
//使用
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5'
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'
testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'
Application
LeakCanary.install(this);
6.內(nèi)存抖動
在極短的時間內(nèi)為對象分配內(nèi)存检疫。在Allocation Tracker中顯示連續(xù)的內(nèi)存分配。
減少內(nèi)存抖動的方法:
1.使用計算方法優(yōu)化算法
2.使用合適的數(shù)據(jù)結(jié)構(gòu)
StringBuffer 與 String
String是對象不是常量祷嘶,在字符進行拼接時候电谣,會新產(chǎn)生一個String
1.
String str1 = "1232";
String str2 = "22222";
String str3 = str1 + str2;
StringBuffer str = new StringBuidler.append(Str1).append(str2); //性能優(yōu)
2
//性能差的實現(xiàn)
StringBuffer str = new StringBuilder().append("Name:").append("GJRS");
//性能好的實現(xiàn)
String Str = "Name:" + "GJRS";
7.內(nèi)存泄露經(jīng)常出現(xiàn)的例子
1.靜態(tài)變量引起秽梅。當(dāng)調(diào)用單例時,如果傳入的context是Activity的context剿牺。盡量使用getAppationContext企垦。
public class CommUtil {
private static CommUtil instance;
private Context context;
private CommUtil(Context context){
this.context = context;
}
public static CommUtil getInstance(Context mContext){
if(instance == null){
instance = new CommUtil(mContext);
}
// else{
// instance.setContext(mcontext);
// }
return instance;
}
public void setContext(Context context) {
this.context = context;
}
}
調(diào)用
CommUtil commUtil = CommUtil.getInstance(this);
//解決辦法:使用Application的上下文
//CommonUtil生命周期跟MainActivity不一致,而是跟Application進程同生同死晒来。
//CommUtil commUtil = CommUtil.getInstance(getApplicationContext());
2.非靜態(tài)內(nèi)部類引起的內(nèi)存泄露(匿名內(nèi)部類)
錯誤的示范:
(1)
解決方案:
將非靜態(tài)內(nèi)部類修改為靜態(tài)內(nèi)部類钞诡。
(靜態(tài)內(nèi)部類不會隱式持有外部類)
//加上static,里面的匿名內(nèi)部類就變成了靜態(tài)匿名內(nèi)部類
public static void loadData(){//隱式持有MainActivity實例湃崩。MainActivity.this.a
new Thread(new Runnable() {
@Override
public void run() {
while(true){
try {
//int b=a;
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
(2)
new Timer().schedule(new TimerTask() {
@Override
public void run() {
while(true){
try {
//int b=a;
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}, 20000);//這個線程延遲5分鐘執(zhí)行
//意外重啟也不會調(diào)用
activity onDestroy把timer.cancel掉然后賦空
(3)
錯誤;
//mHandler是匿名內(nèi)部類的實例荧降,會引用外部對象MainActivity.this。如果Handler在Activity退出的時候攒读,它可能還活著朵诫,這時候就會一直持有Activity。
private Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what){
case 0:
//加載數(shù)據(jù)
break;
}
}
};
mHandler.sendEmptyMessage(0);
mHandler.sendMessageAtTime(msg,10000);//atTime
解決方法:
//mHandler是匿名內(nèi)部類的實例薄扁,會引用外部對象MainActivity.this剪返。如果Handler在Activity退出的時候,它可能還活著邓梅,這時候就會一直持有Activity脱盲。
private static class MyHandler extends Handler
{
//private MainActivity mActivity;//直接持有了一個外部類的強引用,會內(nèi)存泄露
private WeakReference<MainActivity> mActivity;//設(shè)置軟引用保存日缨,當(dāng)內(nèi)存一發(fā)生GC的時候就會回收钱反。
public MyHandler(MainActivity activity) {
this.mActivity = new WeakReference<MainActivity>(activity);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
MainActivity main = mActivity.get();
if(main==null||main.isFinishing()){
return;
}
switch (msg.what){
case 0:
//加載數(shù)據(jù) MainActivity.this.a;
// 有時候確實會有這樣的需求:需要引用外部類的資源。怎么辦匣距?
Button b = main.cacheButton;
break;
}
}
}
當(dāng)使用軟引用或者弱引用的時候面哥,MainActivity難道很容易或者可以被GC回收嗎?GC回收的機制是什么毅待?
當(dāng)MainActivity不被任何的對象引用幢竹。雖然Handler里面用的是軟引用/弱引用,但是并不意味著不存在其他的對象引用該MainActivity恩静。
原因:聲明周期不同步
3.Adapter中引用了Activity如何避免內(nèi)存泄漏
adapter不能持有activity的引用焕毫,否則可能會因為adapter里面可能會做一些耗時操作,當(dāng)activity finish時會因為被adapter持有引用而導(dǎo)致activity無法被回收驶乾,從而導(dǎo)致內(nèi)存泄漏邑飒。
解決方法:
(1)不持有Activity對象的情況下怎么和Activity交互
1.1 首先處理getView()中的inflate()
當(dāng)我們inflate一個xml時,完全可以使用parent的context级乐,實現(xiàn)如下:
1.2 點擊事件疙咸,可以用回調(diào)接口
使用回調(diào)接口的方式來實現(xiàn)不持有activity的情況下,與Activity愉快的交互风科,實現(xiàn)如下:
2.adapter寫在Activity里面的話只需要加個static關(guān)鍵字(變?yōu)殪o態(tài)內(nèi)部類)就行了撒轮,其他和寫在外面是一樣的乞旦。
4.不需要用的監(jiān)聽未移除會發(fā)生內(nèi)存泄露
- add和remove配對使用 放入一個數(shù)組中
cacheButton.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
@Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
//處理
//移除
cacheButton.removeOnLayoutChangeListener(this);
}
});
2.set 使用以后會自動釋放
3.register 和 unregister
SensorManager sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ALL);
SensorEventListener listener = new SensorEventListener() {
@Override
public void onSensorChanged(SensorEvent event) {
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
};
sensorManager.registerListener(listener,sensor,SensorManager.SENSOR_DELAY_FASTEST);
sensorManager.unregisterListener(listener);
5.資源未關(guān)閉引起的內(nèi)存泄露情況
BroadCastReceiver
Cursor
Bitmap
Io流
自定義的attribute attr.recycle()回收
當(dāng)不需要使用的時候,要及時釋放資源(消耗)
6.無限循環(huán)的動畫
在不使用的時候在OnDestroy中停止
7. 使用IntentService
如果Service停止失敗也會導(dǎo)致內(nèi)存泄漏题山。
因為系統(tǒng)會傾向于把這個Service所依賴的進程進行保留兰粉,如果這個進程很耗內(nèi)存,就會造成內(nèi)存泄漏顶瞳。
解決方案:
所以推薦使用IntentService玖姑,它會在后臺任務(wù)執(zhí)行結(jié)束后自動停止,從而避免了Service停止失敗導(dǎo)致發(fā)生內(nèi)存泄漏的可能性慨菱。
繼承service的抽象類
abstract class IntentService extends Service
在onCreate里面起了一個線程
HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
thread.start();
然后用thread的looper起了一個Handler
mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper);
在onStart里面焰络,將啟動的Intent交給ServiceHandler處理
@Override public void onStart(Intent intent, int startId) {
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
msg.obj = intent;
mServiceHandler.sendMessage(msg);
}
在ServiceHandler的handleMessage里面交給抽象方法onHandleIntent處理start的消息
@Override public void handleMessage(Message msg) {
onHandleIntent((Intent)msg.obj);
stopSelf(msg.arg1);
}
protected abstract void onHandleIntent(Intent intent);
在onHandleIntent完了stopSelf