Android 内存泄漏、内存抖动和内存溢出

1. 内存

Java是在JVM所虚拟出的内存环境中运行的,Android应用层是由Java开发的,Android的davlik虚拟机与jvm也类似,只不过它是基于寄存器的。内存分为三个区:堆、栈和方法区。

  • 栈(stack):是简单的数据结构,程序运行时系统自动分配,使用完毕后自动释放。优点:速度快。
  • 堆(heap):用于存放由new创建的对象和数组。在堆中分配的内存,一方面由Java虚拟机自动垃圾回收器来管理,另一方面还需要程序员提供修养,防止内存泄露问题。
  • 方法区(method):又叫静态区,跟堆一样,被所有的线程共享。方法区包含所有的class和static变量。

2. Java GC(垃圾回收机制)

GC可以自动清理堆中不在使用(不在有对象持有该对象的引用)的对象。

在Java中对象如果在没有引用指向该对象,那么该对象就无从处理或调用该对象,这样的对象称为不可到达(unreachable)。垃圾回收用于释放不可到达的对象所占据的内存。

对Android来说,内存使用尤为吃紧,最开始的app进程最大分配才8M的内存,渐渐增加到16M、32M、64M,但是和服务端相比还是很渺小的,如果对象回收不及时,很容易出现OOM内存溢出错误。

3. 定义

内存泄漏(Memory leak): 当一个对象不再使用了,本应该被垃圾回收器回收,但是这个对象由于被其他正在使用的对象所持有,造成无法被回收的结果。这种对象存在堆内存中,就产生了内存泄漏。内存泄漏最终会导致内存溢出OOM
内存溢出(Out Of Memory):当APP占用的内存加上我们申请的内存资源超过了Dalvik虚拟机的最大内存时就会抛出的OOM异常。也就是说程序申请的内存超出了系统能给的内存大小。
内存抖动:在短时间内有大量的对象被创建或者被回收的现象,一般是在循环中大量创建或回收对象导致的。我们在开发中应当尽量避免这种情况。

三者关系:内存抖动 < 内存泄漏< 内存溢出
内存泄漏是造成内存溢出的主要原因之一,多次内存泄漏就会导致内存溢出。
其中内存溢出最为致命,可能会导致应用程序的退出,所以解决内存溢出很重要。

4. 内存泄漏原因及解决办法

内存泄露主要表现为当Activity在finish的时候,由于对象持有对Activity的引用,造成Activity没有被及时回收。大致有以下5种情况造成内存泄露:

  1. static变量、匿名类的使用
  2. 线程执行处理
  3. 各种监听回调处置
  4. Bitmap等回收处置
  5. 集合类只有增操作却没有减操作

4.1 外部类持有Activity的静态引用

原因 :单例模式造成的内存泄漏,如context的使用,单例传入的是activity的context,在activity关闭后,因为单例持有activity的引用,activity的内存无法被回收。
解决:在context的使用上,应该传入application的context到单例模式中,这样就保证了单例的生命周期跟application的生命周期一样。单例模式应该尽量少持有生命周期不同的外部对象,一旦持有该对象的时候,必须在该对象的生命周期结束前null

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        TestUtil testUtil = TestUtil.getInstance(this);
    }
}
public class TestUtil {
    private static TestUtil instance;
    private Context context;
 
    private TestUtil(Context context) {
        this.context = context;
    }
 
    public static TestUtil getInstance(Context context) {
        if (instance == null) {
            instance = new TestUtil(context);
        }
        return instance;
    }
}

单例模式修改为这样,通过获取context.getApplicationContext拿到Application的context对象,保证单例中不管传入的是什么,其生命周期都是和应用一样长的,减少内存泄漏的可能性。

public class TestUtil {
	private static TestUtil instance;
	private Context context;
    
    // context.getApplicationContext
	private TestUtil(Context context) {
		this.context = context.getApplicationContext();
    }
    
    public static TestUtil getInstance(Context context) {
        if (instance != null) {
            instance = new TestUtil(context);
        }
        return instance;
    }
}

4.2 非静态内部类和匿名内部类生命周期导致的内存泄漏

原因:当activity中创建的非静态内部类或者匿名内部类会持有Activity的隐式引用,Handler、Thread、AsyncTask、Runnable等,若内部类生命周期长于Activity(也就是当activity退出finish,但是内部类的异步任务尚未执行完成),会导致Activity实例无法被回收,造成内存泄漏。(屏幕旋转后会重新创建Activity实例,如果内部类持有引用,将会导致旋转前的实例无法被回收)

 public class MainActivity extends AppCompatActivity {
     @Override
     protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        new AsyncTask<String, Void, String>() {
            @Override
            protected String doInBackground(String... params) {
                for (int i = 0; i < 15; i++) {
                    try {
                        Log.e("MainActivity2", "dddd" + i + MainActivity2.this.getLocalClassName());
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                return null;
            }
 
            @Override
            protected void onPostExecute(String s) {
                super.onPostExecute(s);
            }
        }.execute();
    }
 }

在上面的代码中,存在一个非静态的匿名类对象AsyncTask,会隐式持有一个外部类的引用MainActivity 。同理,若是这个AsyncTask作为MainActivity的内部类而不是匿名内部类,他同样会持有外部类的引用。当MainActivity在这15秒内退出或者重建,AsyncTask异步任务尚未处理完成,会持有activity引用,导致其无法回收造成内存泄漏。

解决:如果一定要使用内部类,就改用static静态内部类,在内部类中通过WeakReference的方式引用外界资源。对Handler、Thread、Runnable等使用弱引用;或者在activity生命周期结束前调用removeCallbacksAndMessages等移除异步任务,这样没有引用了自然可以回收。

举例:使用静态的Handler内部类,并通过WeakReference弱引用

public class MainActivity extends AppCompatActivity {
	static MyHandler myHandler;
	
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        myHandler = new MyHandler(this);
    }
    static class MyHandler extends Handler {
        WeakReference<Activity> mActivityReference;
 
        MyHandler(Activity activity) {
            mActivityReference = new WeakReference<Activity>(activity);
        }
 
        @Override
        public void handleMessage(Message msg) {
            final Activity activity = mActivityReference.get();
            if (activity != null) {
                //....
            }
        }
    }
}

4.3 监听回调处理

在onPause()/onDestroy()方法中解除监听器,包括在Android自己的Listener,Location Service或Display Manager Service以及自己写的Listener。

4.4 资源未及时关闭

原因:对于BraodcastReceiver,ContentObserver,Cursor,File,Stream,ContentProvider,Bitmap,动画,I/O,数据库,网络的连接等资源的使用,应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,造成内存泄漏。

解决

  • 广播BraodcastReceiver:记得注销注册unregisterReceiver();
  • 文件流File:记得关闭流InputStream / OutputStream.close();
  • 数据库游标Cursor:使用后关闭游标cursor.close();
  • 对于图片资源Bitmap:当它不再被使用时,应调用recycle()回收此对象的像素所占用的内存,再赋为null
  • 动画:属性动画或循环动画,在Activity退出时需要停止动画。在属性动画中有一类无限循环动画,如果在Activity中播放这类动画并且在onDestroy中没有去停止动画,那么这个动画将会一直播放下去,这时候Activity会被View所持有,从而导致Activity无法被释放。在Activity中onDestroy去调用objectAnimator.cancel()来停止动画。
public class TestActivity extends AppCompatActivity {
    private TextView textView;
    ObjectAnimator objectAnimator;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test);
        textView = (TextView) findViewById(R.id.textview);
        objectAnimator = ObjectAnimator.ofFloat(textView, "rotation", 0, 360);
        objectAnimator.setRepeatCount(ValueAnimator.INFINITE);
        objectAnimator.start();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        // 停止动画,防止内存泄漏
        objectAnimator.cancel();
    }
}

4.5 集合

集合对象及时清理,使得JVM回收。我们通常会把对象存入集合中,当不使用时,清空集合,让相关对象不再被引用。

 objectList.clear();
 objectList = null;

4.6 static关键字修饰的变量由于生命周期过长

原因:静态变量Activity和View会导致内存泄漏。例如下面代码中的静态变量context,textView的生命周期与应用的生命周期一样,而他们都持有当前Activity的(TestActivity )引用,一旦TestActivity 销毁,而它的引用一直被持有,就不会被回收,内存泄漏就产生了。
解决:static对象的生命周期过长,应该谨慎使用,一定要使用要及时进行null处理。

 public class TestActivity extends AppCompatActivity{   
 	private static Context context;    
 	private static TextView textView;  

 	@Override    
 	protected void onCreate(Bundle savedInstanceState){   
     	super.onCreate(savedInstanceState);    
     	setContentView(R.layout.activity_main);    
     	context = this;    
     	textView = new TextView(this);
     }    
 }

4.6 ThraedLocal用完及时remove

原因:我们都知道,threadlocal中维护了一个ThreadLocalMap,而threadlocal作为map的值弱引用存在,当threadlocal为null的时候,垃圾回收机制回收,但是此时我们的threadlocalmap与thread生命周期一致,不会被回收,ThreadLocalMap的key没了,但是value还在,这样就会导致内存泄漏。
解决:使用完ThreadLocal后,执行remove操作,避免出现内存溢出情况。

ThreadLoacal

5. 内存溢出的原因

产生内存溢出的原因主要有:

  1. 内存泄漏,上面讲了内存泄漏产生的原因及解决,在程序开发过程中应尽量避免
  2. 保存了多个耗用内存过大的对象(如Bitmap)或加载单个超大的图片,造成内存超出限制

5.1 大量的图片、音频、视频处理,当在内存比较低的系统上也容易造成内存溢出

建议使用第三方,或者JNI来进行处理;

5.2 Bitmap对象的处理

  • 不要在主线程中处理图片
  • 使用Bitmap对象要用recycle释放
  • 控制图片的大小,压缩大图,高效处理,加载合适属性的图片。
    当我们有些场景是可以显示缩略图的时候,就不要调用网络请求加载大图,例如在RecyclerView中,我们在上下滑动的时候,就不要去调用网络请求,当监听到滑动结束的时候,才去加载大图,以免上下滑动的时候产生卡顿现象。
 // Bitmap对象没有被回收
 if (!bitmapObject.isRecyled()) {
     // 释放  
     bitmapObject.recycle(); 
     // 提醒系统及时回收 
     System.gc(); 
 }  

5.3 间接原因——内存抖动

当频繁大量的创建/回收对象的时候,会造成内存抖动,会导致GC处理机制频繁的运行。
因为new对象会在内存中引用大量内存地址,如果对象大量并且没有被回收的时候,当需要大片的连续地址时,内存中空间足够,但是提供不了连续的内存,也会导致内存溢出。

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
Android开发中,内存溢出是指应用程序在申请的内存超过了系统可用内存的情况。这可能导致应用程序崩溃、运行缓慢或者被系统强制关闭。以下是一些常见导致内存溢出的情况和解决方法: 1. Bitmap内存溢出:在使用Bitmap处理图片时,如果没有正确地释放Bitmap对象,或者加载了过大的图片导致占用大量内存,就会发生内存溢出。解决方法包括使用合适的图片加载库(如Glide、Picasso),及时释放不再使用的Bitmap对象,或者对图片进行压缩处理。 2. 长时间占用内存的后台任务:如果应用程序中存在长时间运行的后台任务,而且这些任务没有及时释放占用的内存,就可能导致内存溢出。解决方法是在合适的时机停止或取消后台任务,并释放相关资源。 3. 内存泄漏内存泄漏问题在上一个问题中已经提到过。如果应用程序中存在内存泄漏,持续占用内存而不释放,就会导致内存溢出。解决方法是检查代码,及时释放不再使用的对象引用,避免长时间持有上下文或其他对象的引用。 4. 大量对象的创建和销毁:如果应用程序频繁地创建和销毁大量对象,而没有及时释放,就会导致内存溢出。解决方法包括使用对象池或缓存来重复利用对象,减少对象的创建和销毁次数。 5. 不适当的资源使用:如果应用程序使用了大量的资源,如文件、数据库连接等,而没有正确地关闭或释放这些资源,就会导致内存溢出。解决方法是在不再需要使用资源的地方及时关闭或释放资源。 6. 大数据集的处理:如果应用程序需要处理大量的数据集,而没有进行分页或分批加载,就可能导致内存溢出。解决方法是采用分页加载或分批处理的方式,减少一次性加载大量数据的压力。 总之,要避免Android应用程序中的内存溢出问题,开发者应该注意及时释放占用的内存,避免内存泄漏和不适当的资源使用。合理管理Bitmap对象、后台任务、对象的创建和销毁,以及大数据集的处理都是预防内存溢出的重要方法。此外,使用工具进行内存分析和优化也是提高应用程序性能的有效方式。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值