IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Android中的内存泄漏和内存溢出 -> 正文阅读

[移动开发]Android中的内存泄漏和内存溢出

一、内存溢出和内存泄漏的概念理解

**内存溢出:**是指程序代码片段被执行申请内存时,没有足够的内存空间,导致OOM异常。

OOM:即内存溢出,out of momery。

Android系统为每一个应用程序申请的内存是有限的(为什么如此设计,在dalvik虚拟机章节已经说明),一般为64M或者128M等,国内手机产商修改rom后,也会有所不同。

我们可以在清单文件中配置android:largeheap=“true”,从而可给app申请更大的手机内存空间。

**内存泄漏:**内存泄漏是指程序在申请内存后,被某个对象一直持有而无法释放已申请的内存空间。内存泄漏不断堆积,应用程序可申请的内存空间就会越来越少,最终可能就出现,当程序片段被执行申请新的内存空间而不得,最终导致内存溢出。

内存泄漏是因,内存溢出是果。针对于内存溢出,除了手机内存小,应用程序本身申请的大对象内存多(比如没有合理的处理bitmap),内存泄漏是导致内存溢出的一个重要的原因。

故,在做我们的应用程序的内存优化的时候,内存溢出排查也是其中的一个重要方面。

二、内存溢出分类

1、栈内存溢出

栈内存溢出:StackOverflowError,方法被运行在虚拟栈中,在虚拟栈中的执行的命令递归执行,如果递归的深度过大,则可能会导致栈溢出。通过一下case,可以模拟栈内存溢出。

public class StackOverflowCase {

    //模拟一个递归方法调用
    public void stackOverflowMethod(){
        stackOverflowMethod();
    }

    public static void main(String[] args) {
        StackOverflowCase stackOverflowCase = new StackOverflowCase();
        stackOverflowCase.stackOverflowMethod();
    }
    
    //main函数运行后,会抛出Exception in thread "main" java.lang.StackOverflowError
}

2、堆内存溢出

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

堆内存溢出,是非常常见的,日常开发当中,有很多案例可能导致堆内存溢出,在第三节中会对堆内存泄漏的案例做详细分析。内存泄漏终将导致内存溢出。除了内存泄漏之外,常见的内存溢出场景有:

1)类结构没有声明好,导致的内存溢出,比如fastJson解析json串的时候,出现了对象之间的相互引用。

2)对象所申请的内存过大,比如在android应用开发中,经常会用到的大图加载,bitmap的对象占用大量内存,所以,我们有各种三方图片加载库,大多通过缓存算法,解决图像占用过大的内存。

3)内存泄漏,并不会理解导致我们的应用程序瘫痪,但是溢出的持续积累,终将导致内存溢出。

3、持久代内存溢出

持久代中包含方法区,方法区中包含常量池,持久代内存溢出包括:

1)运行时的常量池溢出 2)方法区中保存的class对象没有及时被回收掉或者class信息占用的内存过大导致溢出。用String.intern()可以触发常量池溢出。

三、内存泄漏的场景案例

1、单例模式下的内存溢出

代码案例:

class AppSetting {
    private static  AppSetting ourInstance = null;
    public static Context mContext;

    static AppSetting getInstance(Context context) {
        if(ourInstance!=null){
            ourInstance = new AppSetting(context);
        }
        return ourInstance;
    }

    private AppSetting(Context context) {
        this.mContext=context;
    }
}
public class PageActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_page);
        //此处代码的调用
        AppSetting.getInstance(this);
    }
}

AppSetting是一个单例,由于单例的静态特性,使得其生命周期和应用的生命周期一样长。PageActivity是一个普通页面Activity,当页面关闭的时候,PageActivity理当可以被Gc进行回收,但是由于上述的代码调用,使得AppSetting实例持有了PageActivity的引用,导致无法正常被Gc回收,形成此块已申请内存的泄漏。

泄漏,可以这么理解,就是这块内存不能jvm管理了,居然被普通对象长期占据。

上述问题的解决方法也比较简单,如果确实要使用上下文对象,换成ApplicationContext。

2、静态变量导致内存泄漏

public class StaticVarMLeakActivity extends AppCompatActivity {
    private static Info sInfo;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_static_var_mleak);
        if (sInfo == null) {
          //此处导致泄漏
            sInfo = new Info(this);
        }
    }
}

class Info {
    Activity mActivity;

    public Info(Activity activity) {
        mActivity = activity;
    }
}

静态变量存储在方法区,静态变量的生命周期,从类加载开始直到进程结束。一旦静态变量初始化后,它所持有的引用待进程结束才会被释放,上述代码中,页面关闭后,此Activity的对象实例,也没法被回收形成内存泄漏。

上述代码的解决方案是在onDestroy方法中,将静态变量置于null,当页面关闭后,Activity页面对象就可以被回收。

3、非静态内部类导致内存泄漏

Handler的case:

public class NoStaticInnerClassMLeakActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_no_static_inner_class_mleak);
        start();
    }

    private void start() {
        Message message = Message.obtain();
        message.what = 1;
        mHandler.sendMessageDelayed(message,100);
    }
    //非静态内部类
    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(@NonNull Message msg) {
            //do something
        }
    };
}

上述代码呢,也会造成内存泄漏,内存泄漏有主要是有两个原因:

1、非静态的内部类会有持有外部类的引用,这点可以通过查看编译后的class文件得到印证。Handler的引用链大概是这样的:

主线程 —> threadlocal —> Looper —> MessageQueue —> Message —> Handler —> Activity。

2、上述handler的引用链的生命周期要比Activity生命周期长,运行中的主线程是不会被回收的。

所以Handler泄漏的本质是长生命周期的主线程持有了短生命周期对象Activity的引用。

上述案例的解决方案同样也是在onDestroy中移除message,并将mHandler置为null。

其它case:

同样,比如我们在使用网络框架的时候,网络框架内部通过线程池的方式创建了区别于主线程的子线程,当我们发起网络请求的同时,关闭了页面,其实也就造成了当前页面Activity的泄漏,这种泄漏的本质和上面的case是一样的,运行中的子线程的生命周期比Activity的生命周期要长,造成内存泄漏。

这种情况,我们一般可以通过将强引用关系,变成弱引用关系来解决类似的问题。

4、未取消的广播接收器导致的内存泄漏

public class NoUnRegisterMLeakActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_no_un_register_mleak);
      	//会被注册到系统服务对象中AMS、AMS持有mBroadcastReceiver引用
        this.registerReceiver(mBroadcastReceiver,new IntentFilter());
    }

    //持有Activity的引用
    private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            //do something
        }
    };

    @Override
    protected void onDestroy() {
        super.onDestroy();
        //需要取消广播接收器
        this.unregisterReceiver(mBroadcastReceiver);
    }
}

上述代码中已经添加了很详细的注释,未取消的广播接收器导致的内存泄漏的本质原因是系统服务对象的生命周期比Activity的生命周期长,系统服务对象通过持有广播接收器的引用,间接持有了Activity的引用,有兴趣的同学可以查看ContextImpl.registerReceiverAsUser(…)源码进行详细查看。

5、WebView导致的内存泄漏

下面这段代码摘自网络:https://www.jianshu.com/p/3e8f7dbb0dc7

@Override
protected void onDestroy() {
    if( mWebView!=null) {

        // 如果先调用destroy()方法,则会命中if (isDestroyed()) return;这一行代码,需要先onDetachedFromWindow(),再
        // destory()
        ViewParent parent = mWebView.getParent();
        if (parent != null) {
            ((ViewGroup) parent).removeView(mWebView);
        }

        mWebView.stopLoading();
        // 退出时调用此方法,移除绑定的服务,否则某些特定系统会报错
        mWebView.getSettings().setJavaScriptEnabled(false);
        mWebView.clearHistory();
        mWebView.clearView();
        mWebView.removeAllViews();
        //webView的destroy在后
        mWebView.destroy();

    }
    super.on Destroy();
}

如果没有调用此句((ViewGroup) parent).removeView(mWebView);这里webView的引用关系:

mComponentCallbacks->AwContents->BaseWebView->Activity;这里面如果直接调用destroy之前,未调用父容器中移除webView,会导致UnRegister的相关逻辑,不被调用,资源得不到释放,导致内存溢出。详情可以参见上述网络资源链接。

总之,我们分析了多种案例过后,其实内存泄漏的本质原因也非常清晰了,这些案例其实也无需记住。

内存泄漏的本质问题在于长生命周期的对象持有了短生命周期的引用

当短生命周期对象本该到达生命周期终点之时,比如Activity页面关闭,本该交有Gc回收机制,允许其回收当前Activity实例,却由于被长生命周期对象持有,而无法被Gc系统进行回收,从而导致了内存泄漏。

及时获得更多更新,关注gongzhonghao:Hym4Android

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-12-10 11:10:25  更:2021-12-10 11:12:08 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 7:55:53-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码