轉(zhuǎn)載地址:http://www.reibang.com/p/402225fce4b2
JVM內(nèi)存管理
Java采用GC進(jìn)行內(nèi)存管理听绳。深入的JVM內(nèi)存管理知識漫贞,推薦《深入理解Java虛擬機(jī)》。關(guān)于內(nèi)存泄漏我們要知道椿每,JVM內(nèi)存分配的幾種策略萧朝。
- 靜態(tài)的
靜態(tài)的存儲區(qū)略荡,內(nèi)存在程序編譯的時候就已經(jīng)分配好了,這塊內(nèi)存在程序整個運(yùn)行期間都一直存在满哪,它主要存放靜態(tài)數(shù)據(jù)婿斥、全局的static數(shù)據(jù)和一些常量劝篷。
- 棧式的
在執(zhí)行方法時,方法一些內(nèi)部變量的存儲都可以放在棧上面創(chuàng)建民宿,方法執(zhí)行結(jié)束的時候這些存儲單元就會自動被注釋掉娇妓。棧 內(nèi)存包括分配的運(yùn)算速度很快,因為內(nèi)在在處理器里面活鹰。當(dāng)然容量有限哈恰,并且棧式一塊連續(xù)的內(nèi)存區(qū)域,大小是由操作系統(tǒng)決定的志群,他先進(jìn)后 出着绷,進(jìn)出完成不會產(chǎn)生碎片,運(yùn)行效率高且穩(wěn)定
- 堆式的
也叫動態(tài)內(nèi)存 锌云。我們通常使用new 來申請分配一個內(nèi)存荠医。這里也是我們討論內(nèi)存泄漏優(yōu)化的關(guān)鍵存儲區(qū)。GC會根據(jù)內(nèi)存的使用情況桑涎,對堆內(nèi)存里的垃圾內(nèi)存進(jìn)行回收彬向。堆內(nèi)存是一塊不連續(xù)的內(nèi)存區(qū)域,如果頻繁地new/remove會造成大量的內(nèi)存碎片石洗,GC頻繁的回收幢泼,導(dǎo)致內(nèi)存抖動,這也會消耗我們應(yīng)用的性能
我們知道可以調(diào)用 System.gc();進(jìn)行內(nèi)存回收讲衫,但是GC不一定會執(zhí)行缕棵。面對GC的機(jī)制,我們是否無能為力涉兽?其實我們可以通過聲明一些引用標(biāo)記來讓GC更好對內(nèi)存進(jìn)行回收招驴。
- StrongReference (強(qiáng)引用) 任何時候GC是不能回收他的,哪怕內(nèi)存不足時枷畏,系統(tǒng)會直接拋出異常OutOfMemoryError别厘,也不會去回收 進(jìn)程終止
- SoftReference (軟引用) 當(dāng)內(nèi)存足夠時不會回收這種引用類型的對象,只有當(dāng)內(nèi)存不夠用時才會回收 內(nèi)存不足拥诡,進(jìn)行GC的時候
- WeakReference (弱引用) GC一運(yùn)行就會把給回收了 GC后終止
- PhantomReference (虛引用) 如果一個對象與虛引用關(guān)聯(lián)触趴,則跟沒有引用與之關(guān)聯(lián)一樣,在任何時候都可能被垃圾回收器回收 任何時候都有可能
開發(fā)時渴肉,為了防止內(nèi)存溢出冗懦,處理一些比較占用內(nèi)存并且生命周期長的對象時,可以盡量使用軟引用和弱引用仇祭。
Tip
成員變量全部存儲在堆中(包括基本數(shù)據(jù)類型披蕉,引用及引用的對象實體),因為他們屬于類,類對象最終還是要被new出來的
局部變量的基本數(shù)據(jù)類型和引用存在棧中没讲,應(yīng)用的對象實體存儲在堆中眯娱。因為它們屬于方法當(dāng)中的變量,生命周期會隨著方法一起結(jié)束
內(nèi)存泄漏的定義
當(dāng)一個對象已經(jīng)不需要使用了爬凑,本該被回收時徙缴,而有另外一個正在使用的對象持有它的引用,從而導(dǎo)致了對象不能被GC回收嘁信。這種導(dǎo)致了本該被回收的對象不能被回收而停留在堆內(nèi)存中娜搂,就產(chǎn)生了內(nèi)存泄漏
內(nèi)存泄漏與內(nèi)存溢出的區(qū)別
內(nèi)存泄漏(Memory Leak)
進(jìn)程中某些對象已經(jīng)沒有使用的價值了,但是他們卻還可以直接或間接地被引用到GC Root導(dǎo)致無法回收吱抚。當(dāng)內(nèi)存泄漏過多的時候百宇,再加上應(yīng)用本身占用的內(nèi)存,日積月累最終就會導(dǎo)致內(nèi)存溢出OOM內(nèi)存溢出(OOM)
當(dāng) 應(yīng)用的heap資源超過了Dalvik虛擬機(jī)分配的內(nèi)存就會內(nèi)存溢出
內(nèi)存泄漏帶來的影響
應(yīng)用卡頓
泄漏的內(nèi)存影響了GC的內(nèi)存分配秘豹,過多的內(nèi)存泄漏會影響應(yīng)用的執(zhí)行效率應(yīng)用異常(OOM)
過多的內(nèi)存泄漏携御,最終會導(dǎo)致 Dalvik分配的內(nèi)存,出現(xiàn)OOM
Android開發(fā)常見的內(nèi)存泄漏
單例造成的內(nèi)存泄漏
- 錯誤示例
當(dāng)調(diào)用getInstance時既绕,如果傳入的context是Activity的context啄刹。只要這個單例沒有被釋放,那么這個
Activity也不會被釋放一直到進(jìn)程退出才會釋放凄贩。
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);
}
return instance;
}
- 解決方案
能使用Application的Context就不要使用Activity的Content誓军,Application的生命周期伴隨著整個進(jìn)程的周期
非靜態(tài)內(nèi)部類創(chuàng)建靜態(tài)實例造成的內(nèi)存泄漏
- 錯誤示例
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 {
}
- 解決方案
將非靜態(tài)內(nèi)部類修改為靜態(tài)內(nèi)部類。(靜態(tài)內(nèi)部類不會隱式持有外部類)
Handler造成的內(nèi)存泄漏
- 錯誤示例
mHandler是Handler的非靜態(tài)匿名內(nèi)部類的實例疲扎,所以它持有外部類Activity的引用昵时,我們知道消息隊列是在一個Looper線程中不斷輪詢處理消息,那么當(dāng)這個Activity退出時消息隊列中還有未處理的消息或者正在處理消息椒丧,而消息隊列中的Message持有mHandler實例的引用壹甥,mHandler又持有Activity的引用,所以導(dǎo)致該Activity的內(nèi)存資源無法及時回收壶熏,引發(fā)內(nèi)存泄漏句柠。
private MyHandler mHandler = new MyHandler(this);
private TextView mTextView ;
private static class MyHandler extends Handler {
private WeakReference<Context> reference;
public MyHandler(Context context) {
reference = new WeakReference<>(context);
}
@Override
public void handleMessage(Message msg) {
MainActivity activity = (MainActivity) reference.get();
if(activity != null){
activity.mTextView.setText("");
}
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextView = (TextView)findViewById(R.id.textview);
loadData();
}
private void loadData() {
Message message = Message.obtain();
mHandler.sendMessage(message);
}
- 解決方案
創(chuàng)建一個靜態(tài)Handler內(nèi)部類,然后對Handler持有的對象使用弱引用棒假,這樣在回收時也可以回收Handler持有的對象溯职,這樣雖然避免了Activity泄漏,不過Looper線程的消息隊列中還是可能會有待處理的消息帽哑,所以我們在Activity的Destroy時或者Stop時應(yīng)該移除消息隊列中的消息
private MyHandler mHandler = new MyHandler(this);
private TextView mTextView ;
private static class MyHandler extends Handler {
private WeakReference<Context> reference;
public MyHandler(Context context) {
reference = new WeakReference<>(context);
}
@Override
public void handleMessage(Message msg) {
MainActivity activity = (MainActivity) reference.get();
if(activity != null){
activity.mTextView.setText("");
}
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextView = (TextView)findViewById(R.id.textview);
loadData();
}
private void loadData() {
//...request
Message message = Message.obtain();
mHandler.sendMessage(message);
}
@Override
protected void onDestroy() {
super.onDestroy();
mHandler.removeCallbacksAndMessages(null);
}
}
線程造成的內(nèi)存泄漏
- 錯誤示例
異步任務(wù)和Runnable都是一個匿名內(nèi)部類谜酒,因此它們對當(dāng)前Activity都有一個隱式引用。如果Activity在銷毀之前祝拯,任務(wù)還未完成甚带, 那么將導(dǎo)致Activity的內(nèi)存資源無法回收,造成內(nèi)存泄漏
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
SystemClock.sleep(10000);
return null;
}
}.execute();
new Thread(new Runnable() {
@Override
public void run() {
SystemClock.sleep(10000);
}
}).start();
- 解決方案
使用 靜態(tài)內(nèi)部類佳头,避免了Activity的內(nèi)存資源泄漏鹰贵,當(dāng)然在Activity銷毀時候也應(yīng)該取消相應(yīng)的任務(wù)AsyncTask::cancel(),避免任務(wù)在后臺執(zhí)行浪費(fèi)資源
static class MyAsyncTask extends AsyncTask<Void, Void, Void> {
private WeakReference<Context> weakReference;
public MyAsyncTask(Context context) {
weakReference = new WeakReference<>(context);
}
@Override
protected Void doInBackground(Void... params) {
SystemClock.sleep(10000);
return null;
}
@Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
MainActivity activity = (MainActivity) weakReference.get();
if (activity != null) {
//...
}
}
}
static class MyRunnable implements Runnable{
@Override
public void run() {
SystemClock.sleep(10000);
}
}
new Thread(new MyRunnable()).start();
new MyAsyncTask(this).execute();
資源未關(guān)閉造成的內(nèi)存泄漏
- 錯誤示例
對于使用了BraodcastReceiver康嘉,ContentObserver碉输,F(xiàn)ile,Cursor亭珍,Stream敷钾,Bitmap等資源的使用,應(yīng)該在Activity銷毀時及時關(guān)閉或者注銷肄梨,否則這些資源將不會被回收阻荒,造成內(nèi)存泄漏
- 解決方案
在Activity銷毀時及時關(guān)閉或者注銷
使用了靜態(tài)的Activity和View
- 錯誤示例
static view;
void setStaticView() {
view = findViewById(R.id.sv_button);
}
View svButton = findViewById(R.id.sv_button);
svButton.setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View v) {
setStaticView();
nextActivity();
}
});
static Activity activity;
void setStaticActivity() {
activity = this;
}
View saButton = findViewById(R.id.sa_button);
saButton.setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View v) {
setStaticActivity();
nextActivity();
}
});
- 解決方案
應(yīng)該及時將靜態(tài)的應(yīng)用 置為null,而且一般不建議將View及Activity設(shè)置為靜態(tài)
注冊了系統(tǒng)的服務(wù)众羡,但onDestory未注銷
- 錯誤示例
SensorManager sensorManager = getSystemService(SENSOR_SERVICE);
Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ALL);
sensorManager.registerListener(this,sensor,SensorManager.SENSOR_DELAY_FASTEST);
-- 解決方案
//不需要用的時候記得移除監(jiān)聽
sensorManager.unregisterListener(listener);
不需要用的監(jiān)聽未移除會發(fā)生內(nèi)存泄露
- 錯誤示例
//add監(jiān)聽侨赡,放到集合里面
tv.getViewTreeObserver().addOnWindowFocusChangeListener(new ViewTreeObserver.OnWindowFocusChangeListener() {
@Override
public void onWindowFocusChanged(boolean b) {
//監(jiān)聽view的加載,view加載出來的時候粱侣,計算他的寬高等羊壹。
}
});
- 解決方案
//計算完后,一定要移除這個監(jiān)聽
tv.getViewTreeObserver().removeOnWindowFocusChangeListener(this);
//Tip
tv.setOnClickListener();//監(jiān)聽執(zhí)行完回收對象齐婴,不用考慮內(nèi)存泄漏
tv.getViewTreeObserver().addOnWindowFocusChangeListene,add監(jiān)聽油猫,放到集合里面,需要考慮內(nèi)存泄漏