一、內(nèi)存抖動(dòng)介紹
定義:內(nèi)存頻繁分配和回收導(dǎo)致內(nèi)存不穩(wěn)定
表現(xiàn):頻繁 GC、內(nèi)存曲線呈鋸齒狀
危害:導(dǎo)致卡頓、OOM
二、內(nèi)存抖動(dòng)導(dǎo)致OOM原因
- 頻繁創(chuàng)建對(duì)象,導(dǎo)致內(nèi)存不足及碎片(不連續(xù))
- 不連續(xù)的內(nèi)存片無(wú)法被分配,導(dǎo)致OOM
三、內(nèi)存抖動(dòng)解決實(shí)戰(zhàn)
- 使用 Memory Profiler 初步排查
- 使用 Memory Profiler 或 CPU Profiler 結(jié)合代碼排查
首先模擬內(nèi)存抖動(dòng)的實(shí)例,如下所示:
/**
* @desciption: 模擬內(nèi)存抖動(dòng)的界面
*/
public class MemoryShakeActivity extends AppCompatActivity implements View.OnClickListener {
@SuppressLint("HandlerLeak")
private static Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
// 創(chuàng)造內(nèi)存抖動(dòng)
for (int index = 0; index <= 100; index++) {
String arg[] = new String[100000];
}
mHandler.sendEmptyMessageDelayed(0, 30);
}
};
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_memory_shake);
initView();
}
@Override
protected void onDestroy() {
super.onDestroy();
mHandler.removeCallbacksAndMessages(null);
}
private void initView() {
AppCompatButton mBtnExecute = (AppCompatButton) findViewById(R.id.btn_execute);
mBtnExecute.setOnClickListener(this);
}
@Override
public void onClick(View v) {
if (v.getId() == R.id.btn_execute) {
mHandler.sendEmptyMessage(0);
}
}
}
運(yùn)行效果如下圖:

在點(diǎn)擊“執(zhí)行任務(wù)”按鈕之前的內(nèi)存效果圖如下:

從圖中可以看出內(nèi)存一直穩(wěn)定在83.1MB。
在點(diǎn)擊“執(zhí)行任務(wù)”按鈕之后內(nèi)存效果圖如下:

從圖中可以看到內(nèi)存忽高忽低,呈鋸齒狀,這說(shuō)明有內(nèi)存抖動(dòng)的現(xiàn)象。
查看導(dǎo)致內(nèi)存抖動(dòng)的代碼位置
首先看下圖所示:

根據(jù)圖中箭頭指示可以找到內(nèi)存抖動(dòng)的位置,點(diǎn)擊‘Jump to Source’ 即可跳轉(zhuǎn)到發(fā)送內(nèi)存抖動(dòng)的代碼位置。
內(nèi)存抖動(dòng)解決技巧
- 找循環(huán)或者頻繁調(diào)用的地方