此篇主要記錄了兩點:
1、內(nèi)存泄漏與內(nèi)存溢出的區(qū)別
2狭握、引起內(nèi)存泄漏的幾種情況
3、和內(nèi)存相關的一些知識點記錄疯溺,這部分另寫筆記论颅。
一、技術點
1囱嫩、內(nèi)存泄漏與內(nèi)存溢出的區(qū)別
- 內(nèi)存泄漏:指程序在申請內(nèi)存后恃疯,被某個對象一直持有,導致無法釋放已申請的內(nèi)存空間挠说。一次內(nèi)存泄漏的危害可以忽略澡谭,但內(nèi)存泄漏堆積后果很嚴重,無論多少內(nèi)存损俭,遲早會被占光。
- 內(nèi)存溢出:指程序在申請內(nèi)存時潘酗,沒有足夠的內(nèi)存空間供其使用杆兵,出現(xiàn)out of memory。Android系統(tǒng)為每個應用程序申請到的內(nèi)存有限仔夺,一般為64M或者128M等琐脏,我們可以在清單文件中進行配置,android:largeheap = "true" 從而給APP申請更大的內(nèi)存空間缸兔。
虛擬機:JVM的作用是把平臺無關的.class里面的字節(jié)碼翻譯成平臺相關的機器碼日裙,來實現(xiàn)跨平臺。Dalvik和Art時安卓中使用的虛擬機惰蜜。
注:本地方法棧昂拂,屬于native層,暫不需要管抛猖,它和Java層是不一樣的垃圾回收機制格侯。
內(nèi)存溢出會發(fā)生在堆內(nèi)存和虛擬機棧:
-
堆內(nèi)存溢出
例1:
例2:如生產(chǎn)者與消費者模型鼻听,如注冊回調(diào),忘記注銷联四。添加到隊列撑碴,忘記控制隊列大小。
例3:fastjson解析(間接)循環(huán)引用朝墩。(這里例子還不是很理解醉拓,沒有遇到過) 棧(虛擬棧)內(nèi)存溢出
方法遞歸。
內(nèi)存泄漏會發(fā)生在方法區(qū)收苏、堆內(nèi)存和虛擬機棧亿卤。
具體情況見Q3。
2倒戏、垃圾回收機制的原理是什么
3怠噪、詳細說說什么情況下會出現(xiàn)Android內(nèi)存泄漏(六大類)
(1)單例使用不當(生命周期不一樣)
??說明:單例的靜態(tài)特性使得它的生命周期同應用的生命周期一樣長,如果一個對象已經(jīng)沒有用處了杜跷,但是單例還持有它的引用傍念,那么在整個應用程序的生命周期它都不能正常被回收,從而導致內(nèi)存泄漏葛闷。
public class AppSetting {
private static AppSetting mInstance;
private Context mContext;
private AppSetting(Context context) {
this.mContext = context;
}
public static AppSetting getInstance(Context context) {
if (mInstance == null) {
mInstance = new AppSetting(context);
}
return mInstance;
}
}
//使用
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
AppSetting.getInstance(this);
}
}
解決:
將AppSetting修改如下:
public class AppSetting {
private static AppSetting mInstance;
private Context mContext;
private AppSetting(Context context) {
this.mContext = context.getApplicationContext();
}
public static AppSetting getInstance(Context context) {
if (mInstance == null) {
mInstance = new AppSetting(context);
}
return mInstance;
}
}
(2)靜態(tài)變量導致內(nèi)存泄漏
靜態(tài)變量存儲在方法區(qū)憋槐,它的生命周期從類加載開始,到整個進程結束淑趾。一旦靜態(tài)變量初始化后阳仔,它所持有的引用只有等到進程結束才會釋放。
public class MainActivity extends AppCompatActivity {
private static Info sInfo;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
sInfo = new Info(this);
}
}
class Info {
Activity mActivity;
public Info(Activity activity) {
mActivity = activity;
}
}
分析:
Info作為Activity的靜態(tài)成員扣泊,并且持有Activity的引用近范,但是sInfo作為靜態(tài)變量,生命周期肯定比Activity長延蟹。所有當Activity退出后评矩,sInfo仍然引用了Activity,導致Activity不能被回收阱飘,引起內(nèi)存泄漏斥杜。
解決:
在Activity退出時,可以在onDestory中沥匈,把靜態(tài)引用變量置為null蔗喂。
@Override
protected void onDestroy() {
super.onDestroy();
if (sInfo != null) {
sInfo = null;
}
}
(3)非靜態(tài)內(nèi)部類導致內(nèi)存泄漏
非靜態(tài)內(nèi)部類(包括匿名內(nèi)部類)默認就會持有外部類的引用,當非靜態(tài)內(nèi)部類對象的生命周期比外部類對象的生命周期長時高帖,就會導致內(nèi)存泄漏缰儿。常見于Handler、Thread棋恼、AsyncTask返弹。
例1:Handler
public class MainActivity1 extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
start();
}
private void start() {
Message msg = Message.obtain();
msg.what = 1;
mHandler.sendMessage(msg);
}
private Handler mHandler = new Handler() {
@Override
public void handleMessage(@NonNull Message msg) {
switch (msg.what) {
}
}
};
}
分析:
在此例中锈玉,mHandler會默認持有外部類(MainActivity1)的引用,導致MainActivity1不能被回收义起,引起內(nèi)存泄漏拉背。
解決:
通過弱引用解決。
public class MainActivity1 extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
start();
}
private void start() {
Message msg = Message.obtain();
msg.what = 1;
mHandler.sendMessage(msg);
}
private Handler mHandler = new MyHandler(this);
private static class MyHandler extends Handler {
private WeakReference<MainActivity1> mActivity1WeakReference;
public MyHandler(MainActivity1 activity1) {
mActivity1WeakReference = new WeakReference<>(activity1);
}
@Override
public void handleMessage(@NonNull Message msg) {
MainActivity1 activity1 = mActivity1WeakReference.get();
switch (msg.what) {
//處理邏輯
}
}
}
}
例2:Thread
public class MainActivity1 extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
//處理任務
SystemClock.sleep(1000L);
}
}
}).start();
}
}
分析:
1默终、內(nèi)部匿名的Thread實例會長久運行椅棺,不會被系統(tǒng)GC回收。
2齐蔽、非靜態(tài)內(nèi)部類會持有外部類的引用两疚。
在此例中,即使MainActivity1退出了含滴,但是始終有一個thread持有它的引用诱渤,導致MainActivity1不能被回收,引起內(nèi)存泄漏谈况。
解決:
1勺美、加上static后,內(nèi)部類就不會持有MainActivity1的隱式引用了碑韵。
public class MainActivity1 extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new MyThread().start();
}
private static class MyThread extends Thread {
@Override
public void run() {
while (true) {
//處理任務
SystemClock.sleep(1000L);
}
}
}
}
2赡茸、解決Thread無法回收問題
public class MainActivity1 extends AppCompatActivity {
private MyThread mMyThread;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mMyThread = new MyThread();
mMyThread.start();
}
private static class MyThread extends Thread {
private boolean mIsRunning = false;
@Override
public void run() {
mIsRunning = true;
while (mIsRunning) {
//處理任務
SystemClock.sleep(1000L);
}
}
public void close() {
mIsRunning = false;
}
}
@Override
protected void onDestroy() {
super.onDestroy();
mMyThread.close();
}
}
分析:
給Thread添加結束的標志位。當thread任務執(zhí)行完成后祝闻,Java會幫我們把回收線程占卧。因此,我們要養(yǎng)成為thread設置退出邏輯的習慣联喘,保證thread可以運行結束华蜒。
注:
Java threads會一直存在,只有當線程運行完成或被殺死掉豁遭,線程才會被回收友多。
(4)未取消注冊或回調(diào)導致內(nèi)存泄漏
我們在Activity中注冊廣播后,如果在Activity銷毀后不取消注冊堤框,那么這個廣播會一直存在,同上面所說的非靜態(tài)內(nèi)部類一樣持有Activity引用纵柿,導致內(nèi)存泄漏。因此注冊廣播后一定要在Activity銷毀后取消注冊。
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
this.registerReceiver(mReceiver, new IntentFilter());
}
private BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
//收到廣播時的處理
}
};
@Override
protected void onDestroy() {
super.onDestroy();
this.unregisterReceiver(mReceiver);
}
}
(5)資源未關閉或釋放導致內(nèi)存泄漏
在使用流等資源時要及時關閉爱谁,這些資源在進行讀寫時通常都使用了緩沖瓶蝴,如果不及時關閉,這些緩存對象就會一直被占用渊跋,引起內(nèi)存泄漏腊嗡。
4着倾、什么是內(nèi)存抖動,產(chǎn)生的本質(zhì)是什么燕少?(年輕堆與老年堆機制)
5卡者、你是怎么處理crash異常的,對于不能定位行數(shù)的問題怎么解決
6客们、你是怎么對內(nèi)存進行管理的崇决?(Bitmap,對象)
二底挫、相關知識
1恒傻、內(nèi)存詳解
2、垃圾回收機制
3建邓、GcRoot原理詳解
參考:
1盈厘、面試官:內(nèi)存泄漏連環(huán)問。被問懵了官边?來看看這部視頻_嗶哩嗶哩 (゜-゜)つロ 干杯~-bilibili
2沸手、內(nèi)存泄露:Thread是如何造成內(nèi)存泄露的 - 簡書 (jianshu.com)