内存泄漏简介
java可以保证当没有引用指向对象的时候,对象会被垃圾回收器回收,与c语言自己申请的内存自己释放相比,java程序员轻松了很多,但是并不代表java程序员不用担心内存泄漏。当java程序发生内存泄漏的时候往往具有隐蔽性。
定义
引用百度百科的定义:“用动态存储分配函数动态开辟的空间,在使用完毕后未释放,结果导致一直占据该内存单元。直到程序结束”。从程序猿的角度来看“内存泄漏”,其实就是一个对象的生命周期超出了程序员所预期的长度(就叫它“该死不死”吧!),那么这个对象就泄漏了。
android开发中的内存泄漏
android应用程序本身系统分配的内存很少,一旦发生泄漏,程序很快就会变得非常卡顿,直至OOM崩溃。接下来将通过一个案例(只是为了分析内存泄漏而设计的玩具程序,切勿模仿)来介绍内存泄漏分析工具MAT,以及内存分析的技巧。
公欲善其事,先利其器
准备内存泄漏的分析工具,可以安装eclipse插件mat。如果eclise安装mat不成功,那可能是缺少必要的libs,如果嫌找库麻烦,可以只勾选第二项安装,不过会缺少某些功能,但是也够用了。
在线安装:http://download.eclipse.org/mat/1.4/update-site/
下载安装:http://mirror.hust.edu.cn/eclipse//mat/1.4/MemoryAnalyzer-1.4.0.201406041413.zip
mat插件如何使用
如果已经成功安装好了mat工具,使用起来非常简单,首先将需要分析的应用程序跑起来,打开eclipse的devices视图你将会看到点击“Dump Hprof file”按钮,注意点击一下就可以了,然后等待(等待几秒)dump一个内存快照出来,接下来就会自动打开mat的视图了,如果mat没有安装成功,会让你保存一个.hprof文件到本地。看看下面的图例吧
dump hprof启动mat工具
人为制造一个内存泄漏
自定义一个ActivityManager,提供两个方法,分别用来注册与反注册Activity。源码下载
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
public
class
ActivityManager
{
private
List
<Activity>
mActivities
=
new
ArrayList
<>
(
)
;
private
static
ActivityManager
sInstance
;
private
ActivityManager
(
)
{
}
;
public
static
ActivityManager
instance
(
)
{
if
(
sInstance
==
null
)
{
sInstance
=
new
ActivityManager
(
)
;
}
return
sInstance
;
}
public
void
registActivity
(
Activity
activity
)
{
mActivities
.
add
(
activity
)
;
}
public
void
unRigistActivity
(
Activity
activity
)
{
mActivities
.
remove
(
activity
)
;
}
}
|
在MainActivity的onCreate与onDestroy中分别调用registActivity和registActivity方法进行注册与反注册。但是OtherActivity却只是注册了,而忘记了反注册。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
|
public
class
MainActivity
extends
Activity
{
public
static
final
String
TAG
=
MainActivity
.
class
.
getSimpleName
(
)
;
private
Button
mBtn
;
@Override
protected
void
onCreate
(
Bundle
savedInstanceState
)
{
super
.
onCreate
(
savedInstanceState
)
;
setContentView
(
R
.
layout
.
activity_main
)
;
mBtn
=
(
Button
)
findViewById
(
R
.
id
.
btn
)
;
mBtn
.
setOnClickListener
(
new
OnClickListener
(
)
{
@Override
public
void
onClick
(
View
v
)
{
Intent
intent
=
new
Intent
(
)
;
intent
.
setClass
(
MainActivity
.
this
,
OtherActivity
.
class
)
;
startActivity
(
intent
)
;
}
}
)
;
ActivityManager
.
instance
(
)
.
registActivity
(
this
)
;
}
@Override
protected
void
onDestroy
(
)
{
super
.
onDestroy
(
)
;
ActivityManager
.
instance
(
)
.
registActivity
(
this
)
;
}
public
class
OtherActivity
extends
Activity
{
public
static
final
String
TAG
=
OtherActivity
.
class
.
getSimpleName
(
)
;
private
Object
[
]
mObjs
=
new
Object
[
10000
]
;
//模拟快速消耗内存,使效果明显
private
Button
mBtn
;
@Override
protected
void
onCreate
(
Bundle
savedInstanceState
)
{
super
.
onCreate
(
savedInstanceState
)
;
setContentView
(
R
.
layout
.
activity_other
)
;
mBtn
=
(
Button
)
findViewById
(
R
.
id
.
btn
)
;
mBtn
.
setOnClickListener
(
new
OnClickListener
(
)
{
@Override
public
void
onClick
(
View
v
)
{
for
(
int
i
=
0
;
i
<
mObjs
.
length
;
i
++
)
{
mObjs
[
i
]
=
new
Object
(
)
;
}
finish
(
)
;
}
}
)
;
ActivityManager
.
instance
(
)
.
registActivity
(
this
)
;
}
@Override
protected
void
onDestroy
(
)
{
super
.
onDestroy
(
)
;
}
}
|
案例中的内存泄漏是人为构造的,所以我们事先已经知道有泄漏了,但是实际的开发过程中,内存泄漏是隐蔽的,一开始我们并不知道,所以我们需要通过一些手段来测试APP是否有内存泄漏。首先在Devices视图中选中需要测试的进程,然后点击Device视图面板的Update Heap按钮,然后打开Heap视图,点击Cause GC。然后反复的在MainActivyt和OtherActivity之间切换。观察Heap size的变化。你会发现内存一直在增加。没有稳定下来的趋势。这个时候你就有理由怀疑内存泄漏了。
Update heap观察heap size等变化情况
找出泄漏的对象
按照前面mat的使用步骤,dump一个内存快照出来,然后从分析报告中点击“Leak suspects”这里会列车可能泄漏的对象,其中你会发现“ com.vjson.leaks.OtherActivity”的身影。OtherActivity这个类有33个实例,作为代码的生产者,你应该一下子就会发现,原来是OtherActivity泄漏了。发现它泄漏之后,如何找出是哪一个对象持有了OtherActivity对象的引用呢?
可能泄漏的报告
找出引用链
使用OQL对象查询语言查询出泄漏的对象,写过SQL的同学一定对她有一种既陌生又熟悉的感觉,和SQL非常相似,语法简单易懂,但是非常强大select *from com.vjson.leaks.OtherActivity赛选出OtherActivity这一类对象,然后选择“exclude weak/soft references”赛选出除了软引用和弱引用之外的对象,也就是强引用了!。对象的引用类型不在本文的讲解范围,但是你一定要知道“强引用”,“软引用”,“弱引用”。“幽灵引用”,如果不知道自行脑补去吧!
OQL对象查询找出引用链
对象引用链
然后找出GC的根节点,从图二种可以看出,原来Activity对象被ActivityManager里面的ArrayList给hold住了,所以接下来的工作就是在OtherActivity的onDestroy中反注册,内存泄漏就被解决了。
Android开发中常见的内存泄漏
对象没有反注册
数据库cursor没有关闭
Bitmap没有回收
ListView item没有复用
Handler在Activity中定义为非static的匿名内部类
总结
如果耐心的看完本文,那么恭喜你妈妈再也不用担心内存泄漏了。其实只要掌握了分析问题的技巧与工具,内存泄漏so easy。文章中只是简单的介绍了工具与技巧,这其中还有很多技巧需要自己去摸索。
声明:转载请注明出处:vjson.com