前言
?開發程序過程中常常涉及到內存的申請以及回收過程,由于表現形式不明顯而且Java有自動垃圾回收機制,普遍情況下不會過度關注內存,容易疏漏導致拋出異常。同時OOM等內存問題所拋出的堆棧信息有可能不是問題的直接原因,因此還存在排查難等問題。
1 分類
- 內存抖動:短時間內創建大量新生代對象導致GC頻繁,通過觀察Profiler Memory發現內存呈鋸齒狀。
- 內存泄漏:已分配內存由于某種原因未能被回收,導致可用內存逐漸減少。例如,界面被銷毀,但是Activity還被持有引用,導致Activity無法被回收。
- 內存溢出:無法申請到所需的內存空間時。例如,加載圖片過大導致發生程序異常,提示OutOfMemory。
2 工具使用
- Profiler
- Memory Analyzer(和Proiler差不多,不使用)
- LeakCanary
3 內存抖動
?首先使用Profiler進行排查,然后再根據記錄抖動位置的代碼進行排查。以下面這個方法為例:
/**
* 排序后打印二維數組,一行行打印
*/
fun imPrettySureSortingIsFree() {
val dimension = 300
val lotsOfInts =
Array(dimension) { IntArray(dimension) }
val randomGenerator = Random()
for (i in lotsOfInts.indices) {
for (j in lotsOfInts[i].indices) {
lotsOfInts[i][j] = randomGenerator.nextInt()
}
}
for (i in lotsOfInts.indices) {
var rowAsStr = ""
//排序
val sorted = getSorted(lotsOfInts[i])
//拼接打印
for (j in lotsOfInts[i].indices) {
rowAsStr += sorted[j]
if (j < lotsOfInts[i].size - 1) {
rowAsStr += ", "
}
}
}
}
fun getSorted(input: IntArray): IntArray {
val clone = input.clone()
Arrays.sort(clone)
return clone
}
1.運行后觀察Profiler發現內存GC頻繁,我們可以點擊record
記錄下內存的變化,查看這段時間的內存分配情況。
2.前面說過造成內存抖動的原因,因此可以根據內存分配的數量進行分析。點擊
Allocations
進行排序之后,發現char[]分配的對象是最多的,因此char[]成為了首先懷疑的對象。
3.點擊列表的實例查看char[]分配回調棧,通過對比發現內容都一致,因此可以基本斷定是該實例導致內存抖動。
- 最后發現可能是MemoryPerformanceActivity中Oncreate的handleMessage或者imPrettySureSortingIsFree方法引起的抖動,我們就可以點擊右鍵選擇Jump to spurce跳轉,并解決問題。
4 內存泄漏
?導致內存泄漏的原因有很多,例如Cursor、IO操作未關閉,Bitmap、Context未回收等等。我們可以通過Profler Memory堆轉儲功能進行內存泄漏分析。以下面方法為例:
class MemoryPerformanceActivity : AppCompatActivity(), CallBack {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_memory_performance)
val imageView: ImageView = findViewById(R.id.imageView)
val bitmap = BitmapFactory.decodeResource(resources, R.mipmap.ic_launcher)
imageView.setImageBitmap(bitmap)
CallBackManager.addCallBack(this)
}
override fun mack() {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
}
object CallBackManager {
private var mCallBacks = arrayListOf<CallBack>()
fun addCallBack(callBack: CallBack) {
mCallBacks.add(callBack)
}
fun removeCallBack(callBack: CallBack) {
mCallBacks.remove(callBack)
}
}
1.來回跳轉數次到MemoryPerformanceActivity,通過Profiler觀察內存的變化,主要是查看Activity結束時Total是否有減少,我們可以如果占用內存越來越高,則可能發生內存泄露。
2.猜測可能發生內存泄漏之后,我們可點擊
強制GC
去除引用,再點擊Dump java heap
,即可得到hprof文件
進行分析。
3.我們可以點擊
Arrange by packge
按包名進行排序或者點擊Filter
搜索myApplication。通過觀察可以發現強制GC之后MemoryPerformanceActivity仍然存在6個對象。我們想要看到的是1個對象,因此這并不是我們預期想要看到的。
4.點擊MemoryPerformanceActivity查看實例,可以看到每個Aactivity的實際內存分配為94000左右,引用深度Depth為3,再通過References查看引用,可以看出來是mCallBacks持有了6個Activity的引用,其Retained Size的大小為570803。最后看到
shadow$_klass_in_CallBackManager
,發現是CallBackManager
引用了mCallBacks
,點擊右鍵選擇Jump to source
跳轉到CallBackManager進行問題分析。
5.通過分析我們發現CallBackManager 是個靜態類,持有的對象與App生命周期一樣長,因此需要手動將CallBack移除。
override fun onDestroy() {
super.onDestroy()
CallBackManager.removeCallBack(this)
}
5 內存溢出
?當申請不到需要的內存時就會發生內存溢出(OOM),在開發過程中我們常見的OOM就有Bitmap加載不合理圖片造成的。我們可以在線下通過ARTHoot去監測出不合理的圖片,在線下就將問題暴露出來。
?從錯誤堆棧信息不一定能看出內存溢出的準確問題點,因為內存溢出可能只是表象,造成內存溢出的原因有可能是因為內存泄漏,導致可用內存越來越少,最后分配不到我們所需的空間,導致內存溢出。
6 LeakCanary
?leakcanary是由square開源的框架,可以幫助我們快速發現內存泄漏,減少OOM。集成很簡單,只需要下面這一步,原理就是利用了Provider的Oncreate比Application的生命周期還要早,內部實現了Provider并進行框架初始化。
dependencies {
// debugImplementation because LeakCanary should only run in debug builds.
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.2'
}
集成之后Leakcanary就會自動幫我們收集內存泄漏信息。
總結
- MAT和Profiler使用上差不多,因此建議使用Profiler即可,發現問題可直接跳轉到相應的代碼位置,提高排查效率。
- 在開發初期集成LeakCanary去收集內存泄漏信息,早發現早解決。