android中,jvm具有自动回收的机制,会不定时不定期的去清理无用的被占用的内存,
而在理论上不需要再被使用的内存,在实际中却还持有对这一块内存的引用,导致GC时,不会被回收释放掉,这部分内存就会随着程序的运行不断堆积,从而导致应用分配的内存不够使用导致卡顿、ANR异常等情况。
一般内存泄漏(traditional memory leak):由忘记释放分配的内存导致的(Cursor忘记关闭等)
逻辑内存泄漏(logical memory leak):当应用不再需要这个对象,但仍未释放该对象的所有引用
一、 命令
1. meminfo dump
adb shell dumpsys meminfo com.xxx.xxx.xxx
2. android hprof dump
adb shell am dumpheap com.xxx.xxx.xxx /data/local/tmp/meminfo.hprof
adb pull data/local/tmp/meminfo.hprof /d/meninfo/
3. 使用 mat 工具查看
hprof-conv xxxx.hprof xxxxx_mat.hprof
二、
强引用(StrongReference):JVM宁可抛出OOM,也不会让GC回收具有强引用的对象 生命周期:JVM停止的时候才会终止
软引用(SoftReference):只有在内存空间不足时,才会被回收的对象 生命周期:内存不足时终止
弱引用(WeakReference):在GC时,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存
虚引用(PhantomReference):任何时候都可以被GC回收,当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否存在该对象的引用,来了解这个对象是否将要被回收。可以用来作为GC回收Object的标志。 生命周期:GC后终止
成员变量全部存储在堆中(包括基本数据类型,引用及引用的对象实体) ---- 属于类,类对象 AA aa = new AA();
局部变量的基本数据类型和引用存储于栈当中。--因为他们属于方法当中的变量,生命周期会随着方法一起结束
三、
3.1 Activity 泄漏
(1) Activity 被静态方法引用 无法释放 : Settings.getXX(mActivity);
重点:如果持有Activity对象的强引用,垃圾回收器是无法在内存中回收这个对象
(2) Activity 静态变量
Singleton的getInstance()方法时传入了Activity , static Activity activity;
如果这个静态变量在activity生命结束后没有清空,就会导致内存泄漏。因为static贯穿了这个应用的生命周期
所以被泄露的Activity就会一直存在于应用的进程中,不会被垃圾回收器回收。
最容易引发的内存泄漏为 Context,如Activity的Context,就包含了大量的内存引用(View Hierarchies和其他资源)
重要:一旦泄漏Context,就意味着泄漏它指向的所有对象。
如果Activity.onDestroy()执行完,堆栈中仍存在持有该activity的引用,垃圾回收器就无法把他标记成已回收的内存(结果就是Activity存活在它的生命周期之外)
(3) Static Views 单例中保存activity
在单例中,如果Activity经常被用到,那么内存中保存一个实例是很实用的。但由于单例的生命周期是应用程序的生命周期,
这样做会延长Activity的生命周期(强制延长Activity的生命周期是相当危险且不必要的,无论如何都不能在单例中保存类似的Activity对象)
PS: 如果一个View初始化耗费大量资源,而且在一个activity生命周期内保持不变,那可以把它变成static,加载到视图树上(View Hierachy)
像这样,当activity被销毁时,应当释放资源。(在销毁视图时应该把这个static view的view置为null)
PS:导致潜在的内存泄漏的两种:
全局进程(process-global)的static变量。这个无视应用状态,持有activity的强引用的东西
活在Activity生命周期之外的线程。没有清空对Activity的强引用。
静态的类对象会一直存在,并且他持有Activity 的引用(内部类),导致Activity无法释放.
内部类的优势之一就是可以访问外部类,不好的是,导致内存泄漏的原因,就是因为内部类持有外部类实例的引用。
因为内部类的一个特性是它们可以访问外部类的变量,所以它们必然持有了对外部类实例的引用以至于 Activity 会发生泄漏。
解决方案:销毁的时候 InnerClass = null
Activity{
private static InnerClass in;
onCreate(){
in = new InnerClass();
}
class InnerClass {
}
}
解决方案:销毁的时候 view = null
Activity{
static view;
void initView() {
view = this.findViewById(R.id.sv_button);
}
}
3.2 内部类 Inner Classes, 匿名类 Anonymous Classes
优点: 提高可读性,但是导致内存泄漏的原因,就是内部持有外部类实例的强引用,如内部类中持有Activity对象
静态内部类解决内存泄漏
非静态内部类导致内存泄漏主要原因:App大量的内存泄漏导致内存耗尽,会由于内存空间不足,出现频繁的GC,每一次GC都是一个耗时阻塞操作,会造成设备卡顿。
非静态内部类中创建了一个静态实例,导致该实例的生命周期和应用ClassLoader级别,又因为该静态实例会隐式持有其外部类的引用,所以导致其外部类无法正常释放,出现泄漏问题。
Android开发中,最容易引发的内存泄漏问题的是Context。比如Activity的Context,就包含大量的内存引用,例如View Hierarchies和其他资源。
一旦泄漏了Context,也意味泄漏它指向的所有对象。Android机器内存有限,太多的内存泄漏容易导致OOM。
1.非静态内部类会对外部类存在一个隐式引用 非静态(匿名)内部类会持有外部类的引用,静态内部类中未持有外部类的引用。
2.非静态内部类中存在异步任务,可能导致其对应的外部类内存资源无法正常释放
3.非静态内部类中创建了一个静态实例,会导致内存泄漏
解决办法 1.将内部类变为静态内部类,把匿名内部类变为静态匿名内部类 (static 修饰的不受类的限制)
2.如果有强引用Activity中的属性,则将该属性的引用方式改为弱引用
3.在业务运行的情况下,当Activity执行onDestroy时,结束这些耗时任务(取消强引用)
1. 匿名的Runnable,用匿名Handler执行。Runnable内部类会持有外部类的隐式引用,被传递到Handler的消息队列MessageQueue中,
在Message消息没有被处理之前,Activity实例不会被销毁,于是导致了内存泄漏。
Activity{
mHanlder.postDelayed(new Runnable() {
@Override public void run() {
}
});
}
2. Thread 匿名类的实例,不管是不是在工作线程,都会持有Activity的引用,导致内存泄漏
Activity{
new Thread() {
@Override public void run() {
}
}.start();
}
3. AsyncTask匿名类的实例,会持有Activity的引用,导致内存泄漏
Activity{
void startAsyncTask() {
new AsyncTask() {
}
}.execute();
}
弱引用WeakReference(弱引用),它能保证在系统调用GC时就会被系统释放资源
总结: 不是所有内部类只能使用静态内部类,只有在该内部类中的生命周期不可控的情况下,采用静态内部类。
解决思路:去掉隐式引用(静态(匿名)内部类),手动管理对象引用(修改静态内部类的构造方法,手动引入其外部类引用)
当内存不可用时,不执行不可控代码(Android 可以结合智能指针 ,WeakReference包裹外部类实例)
private static class MainHandler extends Handler {
private final WeakReference mActivity;
public MainHandler(Activity activity) {
mActivity = new WeakReference(activity);
}
public void clear(){
if(null != mActivity) {
mActivity.clear();
}
}
@Override
public void handleMessage(Message msg) {
// TODO Auto-generated method stub
Activity activity = (MainActivity) reference.get();
}
};
MainHandler mHandler = new MainHandler(this);
@Override
protected void onDestroy() {
super.onDestroy();
FixedThreadPoolManager.release();
mHandler.clear();
mHandler.removeCallbacksAndMessages(null);
}
3.3 服务注册类泄漏
服务注册类泄漏
通过Context.getSystemService(int name) 可获取系统服务。这些服务工作在各自的进程中,这会导致服务持有了Context的引用,
如果在Activity销毁的时候没有注销这些监听,会导致内存泄漏