性能优化:内存溢出&内存泄漏

查看App内存限制:需要root

adb shell cat /system/build.prop

dalvik.vm.heapstartsize=6m  app启动的初始分配内存
dalvik.vm.heapgrowthlinit=128m app最大内存限制
dalvik.vm.heapsize=192m 开启largeHeap="true"

内存溢出:out of momery

是指程序在申请内存时,没有足够的内存空间给其使用,导致OOM异常

产生OOM原因:

  • Java堆内存溢出:OutOfMemoeryError

Java中被创建的对象实例,所占用的内存空间过大,超出了当前应用进程能获取的最大内存空间。

  • 无足够的连续内存空间

  • FD数量超出限制 --1024

  • 线程数量超出限制 --1024

  • 虚拟内存不足

内存泄漏:momery leak

是指程序在申请内存后,被某个对象一直持有而无法释放已申请的内存空间。

一次的内存泄漏的危害可以忽略,但是内存泄漏堆积会很严重,无论多少内存,迟早会被耗光,最终导致内存溢出

产生内存泄漏的原因:
  1. 单例造成的内存泄漏
产生原因:

由于单例模式的静态特性,使得它的生命周期和我们的应用一样长,如果让单例无限制的持有Activity的强引用就会导致内存泄漏

解决方案:

使用Activity的弱引用,或者没特殊需求时使用Application Context

//单例造成的内存泄漏
public class SingletonContext {
    private static SingletonContext instance;
    private Context mContext;
    
    public SingletonContext(Context context) {
        this.mContext = context;
    }

    public static SingletonContext getInstance(Context context) {
        if (instance == null){
            instance = new SingletonContext(context);
        }
        return instance;
    }
}
  1. 非静态内部类创建静态实例造成的内存泄漏
产生原因:

非静态内部类持有外部类实例的引用,若非静态内部类的实例是静态的,便拥有app存活期整个生命周期,长期持有外部类的引用,阻止外部类实例被回收。

解决方案:
  • 改为静态内部类,不再持有外部类实例的引用

  • 避免申明非静态内部类的静态实例

  • 将内部类抽取出来封装成一个单例,如需使用Context,没有特殊要求就使用Application Context;如果需要Activity Context,则使用完毕后置空,或者使用弱引用

//非静态内部类创建静态实例造成的内存泄漏
public class StaticLeakActivity extends AppCompatActivity {

    //创建静态实例
    private static NoneStaticClass mResource = null;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        if (mResource == null) {
            mResource = new NoneStaticClass();
        }
    }

    //非静态内部类
    private class NoneStaticClass {

    }
}
  1. handler造成的内存泄漏
产生原因:

非静态Handler持有Activity或Service的引用,Message中的target指向Handler实例,所以当Message在MessageQueue中排队,长时间未得到处理时,Activity边不会被回收,导致临时性内存泄露。

解决方案:
  • 静态内部类+弱引用:

将handler声明成静态内部类,通过弱引用(weakReference)的方式持有Activity

  • 在Activity销毁时,清空Handler中未执行或正在执行的Callback以及Message:

mHandler.removeCallbacksAndMessages(null);

//handler造成的内存泄露
public class HandlerLeakActivity extends AppCompatActivity {
    //1.匿名内部类
    private final Handler mLeakHandler = new Handler() {
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
        }
    };

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //延迟操作
        mLeakHandler.postDelayed(new Runnable() {
            @Override
            public void run() {

            }
        }, 50);
    }

    //2,非静态内部类
    private class MyHandler extends Handler{
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
        }
    }

    private MyHandler mHandler = new MyHandler();

   //解决方案:静态内部类+弱引用
    private static class FinalHandler extends Handler {
        //弱引用,在垃圾回收时,activity可被回收
        private WeakReference<HandlerLeakActivity> mWeakReference;

        public FinalHandler(HandlerLeakActivity activity) {
            this.mWeakReference = new WeakReference<>(activity);
        }

        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
        }
    }

    //在Activity销毁时,可清空Handler中未执行或正在执行的Callback以及Message:
    @Override
    protected void onDestroy() {
        super.onDestroy();
        //清空handler管道和队列
        mHandler.removeCallbacksAndMessages(null);
    }
}
  1. 线程造成的内存泄漏
产生原因:

由于线程是Activity的内部类,所以在Thread中保存了Activity的一个引用,当Thread的run函数没有结束时,Thread是不会被销毁的,因此引用的Activity也不会被销毁,造成内存泄漏,AsyncTask内部也是Handler机制,也存在内存泄漏的风险

解决方案:
  • 将线程的内部类,改为静态内部类

  • 在线程内部采用弱引用保存Context引用

//线程造成的内存泄漏
public class ThreadLeakActivity extends AppCompatActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        testThreadLeak();
    }

    private void testThreadLeak() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                SystemClock.sleep(2000);
            }
        }).start();

        new AsyncTask(){
            @Override
            protected Object doInBackground(Object[] objects) {
                return null;
            }
        }.execute();
    }

    //解决方案:改成静态内部类
    static class MyAsyncTask extends AsyncTask {
        private WeakReference<Context> weakReference;

        public MyAsyncTask(Context context) {
            weakReference = new WeakReference<>(context);
        }

        @Override
        protected Object doInBackground(Object[] objects) {
            SystemClock.sleep(2000);
            return null;
        }
    }
}
  1. WebView造成的内存泄漏
解决方案:
  • 避免在xml直接写webview控件,可以使用一个占位布局如:LinearLayout,然后在代码中动态创建一个webView,

linearLayout.addView(new MyWebview(getApplicationContext()));

在当前Activity销毁的时候 在onDestroy方法中销毁这个WebView,释放webview内存

  • 将包含WebView的Activity放在一个单独的进程中,不需要时将进程销毁,从而释放所有所占内存

public class WebLeakActivity extends AppCompatActivity {
    private WebView mWebView;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mWebView = findViewById(R.id.webview);
        mWebView.loadUrl("https://www.baidu.com");
    }

    @Override
    protected void onDestroy() {
        destoryWebView();
        //webview在一个独立进程中创建,不使用的时候直接kill掉进程
        Process.killProcess(Process.myPid());
        super.onDestroy();
    }

    private void destoryWebView() {
        if (mWebView != null) {
            mWebView.pauseTimers();
            mWebView.removeAllViews();
            mWebView.destroy();
            mWebView = null;
        }
    }
}
  1. 资源对象未关闭造成的内存泄漏
产生原因:

资源性对象使用了缓冲区,缓冲区不仅在JVM内,JVM之外也有。如果仅仅把变量设置为null,而不关闭它们,缓冲区得不到释放,往往造成内存泄露。

常见场景:如Cursor、File等一些Closeable对象

解决方案:

一般在finally中关闭资源型对象,而后设置对象为null

  1. 注册对象未注销造成的内存泄漏
产生原因:

观察者模式中,如果注册对象不再使用时,未及时注销,会导致观察者列表中维持这对象的引用,阻止垃圾回收,导致内存泄露。

常见场景:动态注册BroadcastReceiver,注册PhoneStateListener,注册EventBus,还有自定义使用观察者模式 等等

解决方案:

在onDestroy()中进行解注册

  1. 容器中对象未及时清理导致内存泄露
产生原因:

容器类一般拥有较长的生命周期,若内部不再使用的对象不及时清理,内部对象边一直被容器类引用

常见的容器类:线程池、对象池、图片缓存池等

解决方案:

注意对对象置空释放

  1. 静态View导致内存泄露
产生原因:

当一个Activity经常启动,但是对应的View读取非常耗时,我们可以通过静态View变量来保持对该Activity的rootView引用。这样就可以不用每次启动Activity都去读取并渲染View了。这确实是一个提高Activity启动速度的好方法!但是要注意,一旦View attach到我们的Window上,就会持有一个Context(即Activity)的引用。而我们的View有事一个静态变量,所以导致Activity不被回收

解决方案:

在使用静态View时,需要确保在资源回收时,将静态View detach掉。

  1. 属性动画未及时关闭导致内存泄露
产生原因:

在使用ValueAnimator或者ObjectAnimator时,如果没有及时做cancel取消动画,就可能造成内存泄露。 因为在cancel方法里,最后调用了endAnimation(); ,在endAnimation里,有个AnimationHandler的单例,会持有属性动画对象的引用

解决方案:

在onDestory时,调用动画的cancel方法

  1. 其他常见的引起内存泄漏原因
  • 构造Adapter时,没有使用缓存的 contentView

  • Bitmap在不使用的时候没有使用recycle()释放内存

  • 避免代码设计模式的错误造成内存泄露;譬如循环引用,A持有B,B持有C,C持有A,这样的设计谁都得不到释放

  • 警惕线程未终止造成的内存泄露,如在Activity中关联了一个生命周期超过Activity的Thread,在退出Activity时切记结束线程。HandlerThread的run方法是一个死循环,它不会自己结束,线程的生命周期超过了Activity生命周期,我们必须手动在Activity的销毁方法中调用thread.getLooper().quit();才不会泄露

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值