安卓性能测试之应用内存泄漏总结

pre { direction: ltr; color: rgb(0, 0, 10); text-align: left }
pre.western { font-family: "Liberation Serif", serif; font-size: 12pt }
pre.cjk { font-family: "Droid Sans Fallback"; font-size: 12pt }
pre.ctl { font-family: "Droid Sans Fallback"; font-size: 12pt }
p { margin-bottom: 0.25cm; direction: ltr; color: rgb(0, 0, 10); line-height: 120%; text-align: left }
p.western { font-family: "Liberation Serif", serif; font-size: 12pt }
p.cjk { font-family: "Droid Sans Fallback"; font-size: 12pt }
p.ctl { font-family: "Droid Sans Fallback"; font-size: 12pt }
code.ctl { font-family: "Liberation Mono", monospace }
a:link { }

内存泄漏总结

一.
内存泄漏定义

Java内存泄漏指的是进程中某些对象(垃圾对象)已经没有使用价值了,但是它们却可以直接或间接地引用到gc
roots导致无法被GC回收。无用的对象占据着内存空间,使得实际可使用内存变小,形象地说法就是内存泄漏了。

二.
内存泄漏对应用的影响

在android里面,出现内存泄漏会导致系统为应用分配的内存会不断减少,从而造成app在运行时会出现卡断(内存占用高时JVM虚拟机会频繁触发GC),影响用户体验。同时,可能会引起OOM(内存溢出),从而导致应用程序崩溃!

三.
引发原因

1.
非静态内部类的静态实例容易造成内存泄漏

实例:

public
class MainActivity extends Activity

{

static
Demo sInstance = null;

@Override

public
void onCreate(BundlesavedInstanceState)

{

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

if
(sInstance == null)

{

sInstance=
new Demo();

}

}

class
Demo

{

voiddoSomething()

{

System.out.print("dosth.");

}

}

}

分析:

上面的代码中的sInstance实例类型为静态实例,在第一个MainActivity
act1实例创建时,sInstance会获得并一直持有act1的引用。当MainAcitivity销毁后重建,因为sInstance持有act1
的引用,所以act1是无法被GC回收的,进程中会存在2个MainActivity实例(act1和重建后的MainActivity实例),这个
act1对象就是一个无用的但一直占用内存的对象,即无法回收的垃圾对象。所以,对于lauchMode不是singleInstance的
Activity,
应该避免在activity里面实例化其非静态内部类的静态实例。

解决方法:将该内部类设为静态内部类或将该内部类抽取出来封装成一个单例,如果需要使用Context,就使用Application的Context。

2.
A
ctivity使用静态成员

实例:


private
static Drawable sBackground;


@Override


protected
void onCreate(Bundle state) {


super.onCreate(state);


TextView
label = new TextView(this);


label.setText("Leaks
are bad");


if
(sBackground == null) {


sBackground
= getDrawable(R.drawable.large_bitmap);


}


label.setBackgroundDrawable(sBackground);


setContentView(label);


}

分析:

由于用静态成员sBackground 缓存了drawable对象,所以activity加载速度会加快,但是这样做是错误的。因为在Android
2.3系统上,它会导致activity销毁后无法被系统回收。

label
.setBackgroundDrawable函数调用会将label赋值给sBackground的成员变量mCallback。

上面代码意味着:sBackground(GC
Root)会持有TextView对象,而TextView持有Activity对象。所以导致Activity对象无法被系统回收。

避免方法:

·不要对activity的context长期引用(一个activity的引用的生存周期应该和activity的生命周期相同)

·如果可以的话,尽量使用关于application的context来替代和activity相关的context

·如果一个acitivity的非静态内部类的生命周期不受控制,那么避免使用它;正确的方法是使用一个静态的内部类,并且对它的外部类有一WeakReference,就像在ViewRootImpl中内部类W所做的那样。

3.
单例造成的内存泄漏

由于单例的静态特性使得其生命周期和应用的生命周期一样长,如果一个对象已经不再需要使用了,而单例对象还持有该对象的引用,就会使得该对象不能被正常回收,从而导致了内存泄漏。

正确的实例:

//
使用了单例模式
public
class AppManager {
    private
static AppManager instance;
    private
Context context;
    private
AppManager(Context context) {
        this.context
= context;
    }
    public
static AppManager getInstance(Context context) {
        if
(instance != null) {
            instance
= new AppManager(context);
        }
        return
instance;
    }
}

这样不管传入什么Context最终将使用Application的Context,而单例的生命周期和应用的一样长,这样就防止了内存泄漏。

4.
Handler造成的内存泄漏

示例:创建匿名内部类的静态对象

public
class MainActivity extends AppCompatActivity {

    private
final Handler handler = new Handler() {
        @Override
        public
void handleMessage(Message msg) {
            //
...
        }
    };

    @Override
    protected
void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

        new
Thread(new Runnable() {
            @Override
            public
void run() {
                //
...

handler.sendEmptyMessage(0x123);
            }
        });
    }
}

分析:

当MainActivity结束时,未处理的消息持有handler的引用,而handler又持有它所属的外部类也就是
MainActivity的引用。这条引用关系会一直保持直到消息得到处理,这样阻止了MainActivity被垃圾回收器回收,从而造成了内存泄漏。

解决方法

将Handler类独立出来或者使用静态内部类,这样便可以避免内存泄漏。

5.
资源未关闭造成的内存泄漏

对于使用了BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等资源,应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,从而造成内存泄漏。

1)比如在Activity中register了一个BraodcastReceiver,但在Activity结束后没有unregister该BraodcastReceiver。
2)
资源性对象比如Cursor,Stream、File文件等往往都用了一些缓冲,我们在不使用的时候,应该及时关闭它们,以便它们的缓冲及时回收内存。它
们的缓冲不仅存在于
java虚拟机内,还存在于java虚拟机外。如果我们仅仅是把它的引用设置为null,而不关闭它们,往往会造成内存泄漏。
3)对于资源性对象在不使用的时候,应该调用它的close()函数将其关闭掉,然后再设置为null。在我们的程序退出时一定要确保我们的资源性对象已经关闭。
4)Bitmap对象不在使用时调用recycle()释放内存。2.3以后的bitmap应该是不需要手动recycle了,内存已经在java层了。

6.
线程造成的内存泄漏

示例:AsyncTask和Runnable

分析:

AsyncTask和Runnable都使用了匿名内部类,那么它们将持有其所在Activity的隐式引用。如果任务在Activity销毁之前还未完成,那么将导致Activity的内存资源无法被回收,从而造成内存泄漏。

解决方法

将AsyncTask和Runnable类独立出来或者使用静态内部类,这样便可以避免内存泄漏。

7.
使用ListView时造成的内存泄漏

初始时ListView会从BaseAdapter中根据当前的屏幕布局实例化一定数量的View对象,同时ListView会将这些View对象
缓存起来。当向上滚动ListView时,原先位于最上面的Item的View对象会被回收,然后被用来构造新出现在下面的Item。这个构造过程就是由
getView()方法完成的,getView()的第二个形参convertView就是被缓存起来的Item的View对象(初始化时缓存中没有
View对象则convertView是null)。

构造Adapter时,没有使用缓存的convertView。
解决方法:在构造Adapter时,使用缓存的convertView。

8.
集合容器中的内存泄露

我们通常把一些对象的引用加入到了集合容器(比如ArrayList)中,当我们不需要该对象时,并没有把它的引用从集合中清理掉,这样这个集合就会越来越大。如果这个集合是static的话,那情况就更严重了。
解决方法:在退出程序之前,将集合里的东西clear,然后置为null,再退出程序。

9.WebView造成的泄露

当我们不要使用WebView对象时,应该调用它的destory()函数来销毁它,并释放其占用的内存,否则其长期占用的内存也不能被回收,从而造成内存泄露。
解决方法:为WebView另外开启一个进程,通过AIDL与主线程进行通信,WebView所在的进程可以根据业务的需要选择合适的时机进行销毁,从而达到内存的完整释放。

10.
一些不良代码成内存压力

有些代码并不造成内存泄露,但是它们或是对没使用的内存没进行有效及时的释放,或是没有有效的利用已有的对象而是频繁的申请新内存,对内存的回收和分配造成很大影响的,容易迫使虚拟机不得不给该应用进程分配更多的内存,增加vm的负担,造成不必要的内存开支。

10.1
. Bitmap使用不当

第一、及时的销毁。

虽然,系统能够确认Bitmap分配的内存最终会被销毁,但是由于它占用的内存过多,所以很可能会超过Java堆的限制。因此,在用完Bitmap时,要
及时的recycle掉。recycle并不能确定立即就会将Bitmap释放掉,但是会给虚拟机一个暗示:“该图片可以释放了”。

第二、设置一定的采样率。

有时候,我们要显示的区域很小,没有必要将整个图片都加载出来,而只需要记载一个缩小过的图片,这时候可以设置一定的采样率,那么就可以大大减小占用的内存

第三、巧妙的运用软引用(SoftRefrence)

有些时候,我们使用Bitmap后没有保留对它的引用,因此就无法调用Recycle函数。这时候巧妙的运用软引用,可以使Bitmap在内存快不足时得到有效的释放。

10.2,构造Adapter时,没有使用缓存的
convertView

  以构造ListView的BaseAdapter为例,在BaseAdapter中提共了方法:

  public
View getView(intposition, View convertView, ViewGroup parent)

  来向ListView提供每一个item所需要的view对象。

如果我们不去使用convertView,而是每次都在getView()中重新实例化一个View对象的话,即浪费时间,也造成内存垃圾,给垃圾回收增加压力,如果垃圾回收来不及的话,虚拟机将不得不给该应用进程分配更多的内存,造成不必要的内存开支。

10.3、不要在经常调用的方法中创建对象,尤其是忌讳在循环中创建对象。可以适当的使用
hashtable ,
vector
创建一组对象容器,然后从容器中去取那些对象,而不用每次
new 之后又丢弃。

.
内存泄漏的检测

方法一:


实时抓取hprof
文件,观察当前时刻内存占用情况。


工具:Eclipse
的DDMS插件。

步骤:

1.
打开Eclipse,切换到DDMS模式,如下图。

2.
手机设备连接电脑,Eclipse左侧会显示出设备上运行的进程包名,选中要检测的进程。

3.
点击左上方
“Dump
HPROF file”
按钮,带红色箭头的,如下图。

4.
右侧会显示出Overview
界面,首先是一个扇形图,如下图。

鼠标放到每个扇形上,左下角就会显示对应的资源类型。

5.
找到除了Remainder之外最大的扇形,点击-->
选中
Path
to GC roots -->
选择exclude
weak/ soft references

6.
在进入的新界面查看资源,寻找和要检测的应用相关的资源即可。

方法二


执行monkey
结束之后抓取hprof文件,然后分析结果。

工具:Eclipse
的Memory
Analysys
插件;


win7
虚拟机安装adb命令;


SDK
中的hprof-conv工具可以使用。

准备工作:


1.
Eclipse
安装Memory
Analysys
插件,通过Eclipse
Marketplace
安装。


2.

win7虚拟机安装adb命令,参考:http://blog.sina.com.cn/s/blog_60bdd37d0101ezbg.html


3.
SDK
中的hprof-conv工具,如果在tools目录下,拷贝到platform-tools目录。

操作步骤:

1.
首先手机连接电脑,实体机虚拟机都可以,只要安装了adb命令就可以跑monkey。

执行monkey命令:

adb
shell monkey -c android.intent.category.LAUNCHER -c
android.intent.category.MONKEY -c android.intent.category.DEFAULT -c
android.intent.category.BROWSABLE -c android.intent.category.TAB -c
android.intent.category.ALTERNATIVE -c
android.intent.category.SELECTED_ALTERNATIVE -c
android.intent.category.INFO -c android.intent.category.HOME -c
android.intent.category.PREFERENCE -c android.intent.category.TEST -c
android.intent.category.CAR_DOCK -c android.intent.category.DESK_DOCK
-c android.intent.category.CAR_MODE -p com.android.settings
--ignore-crashes --ignore-timeouts --ignore-security-exceptions
--ignore-native-crashes --monitor-native-crashes -s 800 -v -v -v
--throttle 1000 100000

红底的是包名,需要换成要检测的应用的包名,这是一句命令,如果要抓取普通的log,可以在后边加上要保存的位置。

执行monkey的时候,可以另起一个终端,通过
adb
shell ;ps

命令查看应用当前占用内存的情况。

如果要通过ps命令查看内存使用情况,就必须要了解:

USER
进程所属用户

PID
进程ID

%CPU
进程占用CPU百分比

%MEM
进程占用内存百分比

VSIZE
虚拟内存占用大小 单位:kb(killobytes)

RSS
实际内存占用大小 单位:kb(killobytes)

TTY
终端类型

STAT
进程状态

START
进程启动时刻

TIME
进程运行时长

COMMAND
启动进程的命令

尤其是VSIZE
和RSS,查看内存泄漏以实际内存占用大小为衡量标准。

还可以通过命令行
”adb
shell dumpsys meminfo <进程名>”
查看详细的内存使用情况。

2.
抓取hprof文件。


在虚拟机上执行命令行:
adb
shell am dumpheap <
进程名>
<
保存路径>


命令中的<
保存路径>指的是手机目录。

3.
将手机上的hprof文件pull出到电脑上。

4.
pull出来的hprof文件是不能被pc机识别的,需要转码。

通过命令:”hprof-conv
<原HPROF文件路径>
<转换后的HPROF文件路径>”

5.
打开Eclipse,切换到Memory
Analysys
模式,点击左上角的
“Open
Dump Heap”
按钮,打开转码后的hprof文件,
按照方法一的方式分析即可。

时间: 2024-10-27 19:21:35

安卓性能测试之应用内存泄漏总结的相关文章

Android 性能优化之内存泄漏检测以及内存优化(上)

在 Java 中,内存的分配是由程序完成的,而内存的释放则是由 Garbage Collecation(GC) 完成的,Java/Android 程序员不用像 C/C++ 程序员一样手动调用相关函数来管理内存的分配和释放,虽然方便了很多,但是这也就造成了内存泄漏的可能性,所以记录一下针对 Android 应用的内存泄漏的检测,处理和优化的相关内容,上篇主要会分析 Java/Android 的内存分配以及 GC 的详细分析,中篇会阐述 Android 内存泄漏的检测和内存泄漏的常见产生情景,下篇会

Android性能优化之内存泄漏

综述 内存泄漏(memory leak)是指由于疏忽或错误造成程序未能释放已经不再使用的内存.那么在Android中,当一个对象持有Activity的引用,如果该对象不能被系统回收,那么当这个Activity不再使用时,这个Activity也不会被系统回收,那这么以来便出现了内存泄漏的情况.在应用中内出现一次两次的内存泄漏获取不会出现什么影响,但是在应用长时间使用以后,若是存在大量的Activity无法被GC回收的话,最终会导致OOM的出现.那么我们在这就来分析一下导致内存泄漏的常见因素并且如何

老李分享:Android性能优化之内存泄漏3

线程造成的内存泄漏 对于线程造成的内存泄漏,也是平时比较常见的,如下这两个示例可能每个人都这样写过: //——————test1 new AsyncTask<Void, Void, Void>() { @Override protected Void doInBackground(Void... params) { SystemClock.sleep(10000); return null; } }.execute(); //——————test2 new Thread(new Runnabl

老李分享:Android性能优化之内存泄漏2

这种创建Handler的方式会造成内存泄漏,由于mHandler是Handler的非静态匿名内部类的实例,所以它持有外部类Activity的引用,我们知道消息队列是在一个Looper线程中不断轮询处理消息,那么当这个Activity退出时消息队列中还有未处理的消息或者正在处理消息,而消息队列中的Message持有mHandler实例的引用,mHandler又持有Activity的引用,所以导致该Activity的内存资源无法及时回收,引发内存泄漏,所以另外一种做法为: public class

安卓性能优化 视频

韩梦飞沙  韩亚飞  [email protected]  yue31313  han_meng_fei_sha 安卓-性能优化之内存泄漏-原创-高清视频-爱奇艺 ========= Android手机内存管理与性能优化视频教程下载 下载:http://pan.baidu.com/s/1skkSdI1密码:kdcs 01.Dalvik介绍及其优势和文件格式 02.基于栈与基于寄存器比较 03.DEX与ODEX文件格式和ODEX化详解 04.GC垃圾回收机制 05.内存监测分析工具DDMS介绍 0

android性能测试之内存泄漏

1.什么是内存泄漏? 内存泄漏就是向系统申请内存使用,却不归还(释放),导致该内存既不能被自己使用也不能被别人使用. 2.内存泄漏和内存溢出有什么区别? 内存泄漏是分配出去的内存无法回收. 内存溢出是程序使用的内存超出了系统能给予的. 3.如何从测试数据中得出内存泄漏? 首先,我们需要使用性能测试工具去监控性能数据,android可使用GT.apk(下载地址:http://gt.tencent.com/)监控内存数据.然后,对测试对象连续重复做完全相同的操作多次.然后,将测试结果(内存)制作成折

[Android Pro] Android应用性能测试之CPU和内存占用(转载)

首先稍做分析一下测试环境:我们知道CPU和内存占用是一个实时变化的状态,而市面上还没有具体的哪款android应用能做到实时监控CPU和内存占用并使用log日志保存.考虑到android的底层框架是基于Linux的平台,所有我们可以通过Linux的资源监控命令来实现对android平台的资源实时监控. 要做到上边的测试环境的实现,需要具备以下几点: 1.被测试的手机具备root权限:因为涉及到底层的linux命令,需要读取或执行相应的文件.至于如何root你的手机,不同型号的手机root的方法不

安卓中的内存泄漏

因为安卓是基于java语言的,所以我们先来看一看java中的内存泄漏,然后在此基础上来谈谈安卓中的内存泄漏. 一java中的内存泄漏: java中的内存泄漏主要是指在堆中分配的内存,明明已经不需要的时候,还仍然保留着访问它的引用,导致GC回收不能及时回收(关于GC回收不做过多赘述),导致这种情况出现的最主要原因是长生命周期的对象持有短生命周期对象的引用,导致短生命周期的对象明明已经不需要却无法被GC回收,从而导致内存泄漏.主要包括以下几种情况: 1在一个类中创建了一个非静态内部类的静态实例,如下

IOS性能调优系列:使用Instruments动态分析内存泄漏

硬广:<IOS性能调优系列>第二篇,持续更新,欢迎关注. 第一篇介绍了Analyze对App做静态分析,可以发现应用中的内存泄漏问题,对于有些内存泄漏情况通过静态分析无法解决的,可以通过动态分析来发现,分析起来更有针对性. 从本篇开始介绍XCode提供的强大的分析工具Instruments,内存分析只是Instruments中的一个功能,其他功能后续介绍. 使用Instruments动态分析内存泄漏 Instruments中的Leaks功能主要用于分析内存泄漏,还是以<IOS性能调优系列