內(nèi)存簡(jiǎn)介
RAM(random access memory)隨機(jī)存取存儲(chǔ)器。說白了就是內(nèi)存。
一般Java在內(nèi)存分配時(shí)會(huì)涉及到以下區(qū)域:
- 寄存器(Registers):速度最快的存儲(chǔ)場(chǎng)所,因?yàn)榧拇嫫魑挥谔幚砥鲀?nèi)部唉堪,我們?cè)诔绦蛑袩o法控制
- 棧(Stack):存放基本類型的數(shù)據(jù)和對(duì)象的引用院喜,但對(duì)象本身不存放在棧中,而是存放在堆中
- 堆(Heap):堆內(nèi)存用來存放由new創(chuàng)建的對(duì)象和數(shù)組录豺。在堆中分配的內(nèi)存朦肘,由Java虛擬機(jī)的自動(dòng)垃圾回收器(GC)來管理。
- 靜態(tài)域(static field): 靜態(tài)存儲(chǔ)區(qū)域就是指在固定的位置存放應(yīng)用程序運(yùn)行時(shí)一直存在的數(shù)據(jù)双饥,Java在內(nèi)存中專門劃分了一個(gè)靜態(tài)存儲(chǔ)區(qū)域來管理一些特殊的數(shù)據(jù)變量如靜態(tài)的數(shù)據(jù)變量
- 常量池(constant pool):虛擬機(jī)必須為每個(gè)被裝載的類型維護(hù)一個(gè)常量池媒抠。常量池就是該類型所用到常量的一個(gè)有序集和,包括直接常量(string,integer和floating point常量)和對(duì)其他類型咏花,字段和方法的符號(hào)引用趴生。
- 非RAM存儲(chǔ):硬盤等永久存儲(chǔ)空間
堆棧特點(diǎn)對(duì)比:
由于篇幅原因,下面只簡(jiǎn)單的介紹一下堆棧的一些特性昏翰。
棧:當(dāng)定義一個(gè)變量時(shí)冲秽,Java就在棧中為這個(gè)變量分配內(nèi)存空間,當(dāng)該變量退出該作用域后矩父,Java會(huì)自動(dòng)釋放掉為該變量所分配的內(nèi)存空間锉桑,該內(nèi)存空間可以立即被另作他用。
堆:當(dāng)堆中的new產(chǎn)生數(shù)組和對(duì)象超出其作用域后窍株,它們不會(huì)被釋放民轴,只有在沒有引用變量指向它們的時(shí)候才變成垃圾,不能再被使用球订。即使這樣后裸,所占內(nèi)存也不會(huì)立即釋放,而是等待被垃圾回收器收走冒滩。這也是Java比較占內(nèi)存的原因微驶。棧:存取速度比堆要快,僅次于寄存器开睡。但缺點(diǎn)是因苹,存在棧中的數(shù)據(jù)大小與生存期必須是確定的,缺乏靈活性篇恒。
堆:堆是一個(gè)運(yùn)行時(shí)數(shù)據(jù)區(qū)扶檐,可以動(dòng)態(tài)地分配內(nèi)存大小,因此存取速度較慢胁艰。也正因?yàn)檫@個(gè)特點(diǎn)款筑,堆的生存期不必事先告訴編譯器,而且Java的垃圾收集器會(huì)自動(dòng)收走這些不再使用的數(shù)據(jù)腾么。棧:棧中的數(shù)據(jù)可以共享奈梳, 它是由編譯器完成的,有利于節(jié)省空間解虱。
例如:需要定義兩個(gè)變量int a = 3攘须;int b = 3;
編譯器先處理int a = 3饭寺;首先它會(huì)在棧中創(chuàng)建一個(gè)變量為a的引用阻课,然后查找棧中是否有3這個(gè)值,如果沒找到艰匙,就將3存放進(jìn)來限煞,然后將a指向3。接著處理int b = 3员凝;在創(chuàng)建完b的引用變量后署驻,因?yàn)樵跅V幸呀?jīng)有3這個(gè)值,便將b直接指向3健霹。這樣旺上,就出現(xiàn)了a與b同時(shí)均指向3的情況。這時(shí)糖埋,如果再讓a=4宣吱;那么編譯器會(huì)重新搜索棧中是否有4值,如果沒有瞳别,則將4存放進(jìn)來征候,并讓a指向4;如果已經(jīng)有了祟敛,則直接將a指向這個(gè)地址疤坝。因此a值的改變不會(huì)影響到b的值。
堆:例如上面棧中a的修改并不會(huì)影響到b, 而在堆中一個(gè)對(duì)象引用變量修改了這個(gè)對(duì)象的內(nèi)部狀態(tài)馆铁,會(huì)影響到另一個(gè)對(duì)象引用變量跑揉。
Android中的垃圾回收機(jī)制
Android平臺(tái)最吸引開發(fā)者的一個(gè)特性:有垃圾回收機(jī)制,無需手動(dòng)管理內(nèi)存埠巨,Android系統(tǒng)會(huì)自動(dòng)跟蹤所有的
Young Generation
- 大多數(shù)新建的對(duì)象都位于Eden區(qū)
- 當(dāng)Eden區(qū)被對(duì)象填滿時(shí)历谍,就會(huì)執(zhí)行Minor GC.并把所有存活下來的對(duì)象轉(zhuǎn)移到其中一個(gè)survivor區(qū)
- Survivor Space: S0、S1有兩個(gè)辣垒,存放每次垃圾回收所存活的對(duì)象
- Minor GC同樣會(huì)檢查survivor區(qū)中存活下來的對(duì)象扮饶,并把它們轉(zhuǎn)移到另一個(gè)survivor區(qū)。這樣在一段時(shí)間內(nèi)乍构,總會(huì)有一個(gè)空的survivor區(qū)甜无。
Old Generation
- 存放長(zhǎng)期存活的對(duì)象和經(jīng)過多次Minor GC后依然存活下來的對(duì)象
- 滿了進(jìn)行Major GC
Parmanent Generation:
- 存放方法區(qū),方法區(qū)中有要加載的類信息哥遮、靜態(tài)變量岂丘、final類型的常量、屬性和方法信息
垃圾回收機(jī)制&FPS
- Android系統(tǒng)每隔16ms發(fā)出VSYNC信號(hào)眠饮,觸發(fā)對(duì)UI進(jìn)行渲染奥帘,那么整個(gè)過程如果保證在16ms以內(nèi)就能達(dá)到一個(gè)流暢的畫面即為60FPS。
- 如果某一幀的操作超過了16ms就會(huì)讓用戶感覺到卡頓仪召。
- UI渲染過程發(fā)生GC寨蹋,導(dǎo)致某一幀繪制時(shí)間超過16ms.
內(nèi)存泄漏
內(nèi)存泄漏是指某一段內(nèi)存在程序里功能上已經(jīng)不需要了松蒜,但是垃圾回收機(jī)制回收內(nèi)存時(shí)檢測(cè)那段內(nèi)存還是被需要的,不能被回收已旧,這種在程序中在沒有使用的但是又不能被回收的內(nèi)存就是被泄漏的內(nèi)存秸苗,那為什么會(huì)這樣呢?
正常的話應(yīng)該是程序里不需要的內(nèi)存就可以被回收运褪,這是垃圾回收機(jī)制做的事呀惊楼,如果垃圾回收機(jī)制正常運(yùn)行的情況下,不應(yīng)該這樣啊秸讹,但是實(shí)際就是垃圾回收機(jī)制正常的情況下發(fā)生的內(nèi)存泄漏檀咙。其實(shí)到這里java程序員就得知道垃圾回收機(jī)制中,判斷一段內(nèi)存是否是垃圾璃诀,是否可回收的條件弧可,這個(gè)條件是通過檢查這段內(nèi)存是否存在引用和被引用關(guān)系,不存在這關(guān)系時(shí)劣欢,就認(rèn)為可回收侣诺,若還存在引用或被引用關(guān)系,就認(rèn)為不可回收氧秘,現(xiàn)在就可以知道導(dǎo)致內(nèi)存泄漏的原因是程序員沒有將不用的內(nèi)存去掉引用關(guān)系(因?yàn)槌绦蛑写蠖鄡?nèi)存石油對(duì)象指向的年鸳,所以去掉引用關(guān)系就是置空)。內(nèi)存泄漏會(huì)導(dǎo)致一些內(nèi)存沒法被正常利用丸相,話句話就是可以使用內(nèi)存變少了搔确,這樣輕則增加垃圾回收機(jī)制運(yùn)行頻率,重則內(nèi)存溢出(當(dāng)系統(tǒng)需要分配一段內(nèi)存灭忠,但是現(xiàn)有內(nèi)存在垃圾回收運(yùn)行后任然不足時(shí)膳算,就會(huì)內(nèi)存溢出);為避免內(nèi)存泄漏弛作,在寫程序時(shí)已經(jīng)確定不需要的引用型變量涕蜂,就置空;雖然即使內(nèi)存沒泄露映琳,也有可能出現(xiàn)內(nèi)存溢出机隙,這時(shí)的內(nèi)存溢出就是有別的問題導(dǎo)致的。
- 應(yīng)用程序分配了大量不能被回收的對(duì)象
- 系統(tǒng)可分配內(nèi)存越來越少
- 新對(duì)象的創(chuàng)建需要的內(nèi)存不夠
- GC之后再分配
- 60fps
舉例
- 非靜態(tài)內(nèi)部類的靜態(tài)實(shí)例造成的泄露
public class MainActivity extends Acitivity{
private static TestResource sresource=null;
@Override
protected void onCreate(Bundle saveInstanceState){
super.onCreate(saveInstanceState);
setContentView(R.layout.activity_main);
if (sresource==null){
sresource=new TestResource();
}
}
class TestResource{
//...
}
上面的代碼中的sresource實(shí)例類型為靜態(tài)實(shí)例萨西,在第一個(gè)MainActivity act1實(shí)例創(chuàng)建時(shí)有鹿,sresource會(huì)獲得并一直持有act1的引用。當(dāng)MainAcitivity銷毀后重建谎脯,因?yàn)閟resource持有act1的引用葱跋,所以act1是無法被GC回收的,進(jìn)程中會(huì)存在2個(gè)MainActivity實(shí)例(act1和重建后的MainActivity實(shí)例),這個(gè)act1對(duì)象就是一個(gè)無用的但一直占用內(nèi)存的對(duì)象娱俺,即無法回收的垃圾對(duì)象稍味。所以,對(duì)于啟動(dòng)模式不是單一模式的Activity荠卷, 應(yīng)該避免在activity里面實(shí)例化其非靜態(tài)內(nèi)部類的靜態(tài)實(shí)例模庐。
調(diào)用Context
-
Handler的引用(非靜態(tài)內(nèi)部類是持有外部類類引用)
public class SampleActivity extends Activity {private final Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { // ... } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 發(fā)送一個(gè)10分鐘后執(zhí)行的一個(gè)消息 mHandler.postDelayed(new Runnable() { @Override public void run() { } }, 600000); // 結(jié)束當(dāng)前的Activity finish(); } }
解決方法
public class SampleActivity extends Activity {
private final WeakReference<SampleActivity> mActivity;
/**
* 使用靜態(tài)的內(nèi)部類,不會(huì)持有當(dāng)前對(duì)象的引用
*/
private static class MyHandler extends Handler {
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);
/**
* 使用靜態(tài)的內(nèi)部類僵朗,不會(huì)持有當(dāng)前對(duì)象的引用
*/
private static final Runnable sRunnable = new Runnable() {
@Override
public void run() { }
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 發(fā)送一個(gè)10分鐘后執(zhí)行的一個(gè)消息
mHandler.postDelayed(sRunnable, 600000);
// 結(jié)束
finish();
}
}
線程引發(fā)的內(nèi)存泄漏---變成靜態(tài)
集合對(duì)象沒有清理
將集合設(shè)為null
資源對(duì)象沒有關(guān)閉(在onDestory中關(guān)閉)
避免內(nèi)存泄露的方法:
- 盡量不要讓靜態(tài)變量引用Activity
- 使用WeakReference
- 使用靜態(tài)內(nèi)部類代替內(nèi)部類
- 靜態(tài)內(nèi)部類使用弱引用來引用外部類
- 在聲名周期結(jié)束的時(shí)候釋放資源
內(nèi)存抖動(dòng)
指在短時(shí)間內(nèi)有大量的對(duì)象被創(chuàng)建或者被回收的現(xiàn)象,內(nèi)存抖動(dòng)出現(xiàn)原因主要是頻繁(很重要)在循環(huán)里創(chuàng)建對(duì)象(導(dǎo)致大量對(duì)象在短時(shí)間內(nèi)被創(chuàng)建屑彻,由于新對(duì)象是要占用內(nèi)存空間的而且是頻繁验庙,如果一次或者兩次在循環(huán)里創(chuàng)建對(duì)象對(duì)內(nèi)存影響不大,不會(huì)造成嚴(yán)重內(nèi)存抖動(dòng)這樣可以接受也不可避免社牲,頻繁的話就很內(nèi)存抖動(dòng)很嚴(yán)重)粪薛,內(nèi)存抖動(dòng)的影響是如果抖動(dòng)很頻繁,會(huì)導(dǎo)致垃圾回收機(jī)制頻繁運(yùn)行(短時(shí)間內(nèi)產(chǎn)生大量對(duì)象搏恤,需要大量?jī)?nèi)存违寿,而且還是頻繁抖動(dòng),就可能會(huì)需要回收內(nèi)存以用于產(chǎn)生對(duì)象熟空,垃圾回收機(jī)制就自然會(huì)頻繁運(yùn)行了)藤巢。綜上就是頻繁內(nèi)存抖動(dòng)會(huì)導(dǎo)致垃圾回收頻繁運(yùn)行。
內(nèi)存檢測(cè)工具
Memory Monitor
方便顯示內(nèi)存使用和GC情況
快速定位卡頓是否和GC有關(guān)
快速定位Crash是否和內(nèi)存占用過高有關(guān)
快速定位潛在的內(nèi)存泄漏問題
簡(jiǎn)單易用
不能準(zhǔn)確定位問題Allocation Tracker
定位代碼中分配的對(duì)象的類型息罗,大小掂咒、時(shí)間、線程迈喉、堆棧等信息
定位內(nèi)存抖動(dòng)問題
配合Heap Viewer一起定位內(nèi)存泄漏問題Heap Viewer
內(nèi)存快照信息
每次GC之后收集
減少內(nèi)存使用
- 使用更輕量的數(shù)據(jù)結(jié)構(gòu)(比如SpareArray代替HashMap)
- 避免在onDraw方法中創(chuàng)建對(duì)象
- 對(duì)象池(Message.obtin())
- LRUCache
- Bitmap內(nèi)存復(fù)用绍刮,壓縮(inSampleSize,inBitmap)
- StringBuilder(字符串的拼接)
視圖優(yōu)化
1.降低View層級(jí)
- LinearLayout VS RelativeLayout
- merge
- 不必要的背景
2.去掉window默認(rèn)的背景(getWindow().setBackgroundDrawable(null))
去掉不必要的背景(每次添加背景都會(huì)再繪制一次)
ClipRect&QuickReject(尤其在自定義控件時(shí)使用)
ViewStub(控件某些條件才展示)
.9圖用作背景(例如ImageView)
電量消耗
25~30%消耗用在核心功能上
- 畫圖
- 布局
- 動(dòng)畫
剩下的75%左右
- 上傳統(tǒng)計(jì)數(shù)據(jù)
- 檢查位置信息
- 輪訓(xùn)服務(wù)器,拉取廣告信息
網(wǎng)絡(luò)
- Android網(wǎng)絡(luò)模塊一段時(shí)間一直在運(yùn)行
WakeLock
- 阻止系統(tǒng)進(jìn)入睡眠狀態(tài)(謹(jǐn)慎使用挨摸、釋放很難)
AlarmManager里AlarmManager.setInexact()不會(huì)嚴(yán)格按照時(shí)間會(huì)把相鄰的時(shí)間放到一起進(jìn)行
JobScheduler
制定幾乎任意一個(gè)場(chǎng)景去喚醒
非即時(shí)的任務(wù)
Battery Stats
Battery Historian
更詳細(xì)的數(shù)據(jù)
網(wǎng)絡(luò)優(yōu)化
- 何時(shí)請(qǐng)求(是否是即時(shí)請(qǐng)求)
- 如何請(qǐng)求(一次發(fā)送所有相關(guān)的請(qǐng)求)
解決辦法 利用WIFI
預(yù)取數(shù)據(jù)
避免輪詢服務(wù)器(使用googole的GSM或國內(nèi)第三方的推送服務(wù))
數(shù)據(jù)壓縮(從減少網(wǎng)絡(luò)請(qǐng)求消耗的時(shí)間孩革,但會(huì)增加一些解析數(shù)據(jù)的時(shí)間,不過是可以接受的)
一些補(bǔ)充
WeakReference與SoftReference
通常用于Cache.如果Cache中的對(duì)象要長(zhǎng)期保存用強(qiáng)引用得运,只是臨時(shí)使用可以用軟引用如:下載圖片到本地膝蜈。