百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术教程 > 正文

Android内存泄漏定位、分析、解决全方案

suiw9 2024-11-05 12:35 30 浏览 0 评论

***

## [原文链接](http://tryenough.com/android-momeryleak)

## [更多教程](http://tryenough.com/type-android)

***

## 为什么会发生内存泄漏

***

>内存空间使用完毕之后未回收, 会导致内存泄漏。有人会问:Java不是有垃圾自动回收机制么?不幸的是,在Java中仍存在很多容易导致内存泄漏的逻辑(logical leak)。虽然垃圾回收器会帮我们干掉大部分无用的内存空间,但是对于还保持着引用,但逻辑上已经不会再用到的对象,垃圾回收器不会回收它们。

例如

- 忘记释放分配的内存的。(Cursor忘记关闭等)。

- 应用不再需要这个对象,未释放该对象的所有引用。

- 强引用持有的对象,垃圾回收器是无法在内存中回收这个对象。

- 持有对象生命周期过长,导致无法回收。

### Java判断无效对象的原理

Android内存回收管理策略图:

![](http://upload-images.jianshu.io/upload_images/679754-279fef56f0558377.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/640)

图中的每个圆节点代表对象的内存资源,箭头代表可达路径。当圆节点与 GC Roots 存在可达路径时,表示当前资源正被引用,虚拟机是无法对其进行回收的(如图中的黄色节点)。反过来,如果圆节点与 GC Roots 不存在可达路径,则意味着这块对象的内存资源不再被程序引用,系统虚拟机可以在 GC 过程中将其回收掉。

从定义上讲,Android(Java)平台的内存泄漏是指**没有用的对象资源任与GC-Root保持可达路径,导致系统无法进行回收。**

### 内存泄漏带来的危害

- 用户对单次的内存泄漏并没有什么感知,但当泄漏积累到内存都被消耗完,就会导致卡顿,崩溃。

- 内存泄露是内存溢出OOM的重要原因之一,会导致Crash

## Android中常见的可能发生内存泄漏的地方

***

### 1.在Android开发中,最容易引发的内存泄漏问题的是[Context](https://developer.android.com/reference/android/content/Context)。

比如[Activity](https://developer.android.com/reference/android/app/Activity)的`Context`,就包含大量的内存引用,一旦泄漏了`Context`,也意味泄漏它指向的所有对象。

造成Activity泄漏的常见原因:

- Static Activities

在类中定义了静态Activity变量,把当前运行的Activity实例赋值于这个静态变量。

如果这个静态变量在Activity生命周期结束后没有清空,就导致内存泄漏。

因为static变量是贯穿这个应用的生命周期的,所以被泄漏的Activity就会一直存在于应用的进程中,不会被垃圾回收器回收。

```java

static Activity activity; //这种代码要避免

```

- 单例中保存Activity

在单例模式中,如果Activity经常被用到,那么在内存中保存一个Activity实例是很实用的。但是由于单例的生命周期是应用程序的生命周期,这样会强制延长Activity的生命周期,这是相当危险而且不必要的,无论如何都不能在单例子中保存类似Activity的对象。

举例:

```java

public class Singleton {

private static Singleton instance;

private Context mContext;

private Singleton(Context context){

this.mContext = context;

}

public static Singleton getInstance(Context context){

if (instance == null){

synchronized (Singleton.class){

if (instance == null){

instance = new Singleton(context);

}

}

}

return instance;

}

}

```

在调用Singleton的getInstance()方法时传入了Activity。那么当instance没有释放时,这个Activity会一直存在。因此造成内存泄露。

解决方法:

>可以将new Singleton(context)改为new Singleton(context.getApplicationContext())即可,这样便和传入的Activity没关系了。

- Static Views

同理,静态的View也是不建议的

- Inner Classes

内部类的优势可以提高可读性和封装性,而且可以访问外部类,不幸的是,导致内存泄漏的原因,就是内部类持有外部类实例的强引用。 例如在内部类中持有Activity对象

解决方法:

>1.将内部类变成静态内部类;

2.如果有强引用Activity中的属性,则将该属性的引用方式改为弱引用;

3.在业务允许的情况下,当Activity执行onDestory时,结束这些耗时任务;

例如:

发生内存泄漏的代码:

```java

public class LeakAct extends Activity {

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.aty_leak);

test();

}

//这儿发生泄漏

public void test() {

new Thread(new Runnable() {

@Override

public void run() {

while (true) {

try {

Thread.sleep(1000);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

}).start();

}

}

```

解决方法:

```java

public class LeakAct extends Activity {

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.aty_leak);

test();

}

//加上static,变成静态匿名内部类

public static void test() {

new Thread(new Runnable() {

@Override

public void run() {

while (true) {

try {

Thread.sleep(1000);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

}).start();

}

}

```

- Anonymous Classes

匿名类也维护了外部类的引用。当你在匿名类中执行耗时任务,如果用户退出,会导致匿名类持有的Activity实例就不会被垃圾回收器回收,直到异步任务结束。

***

## [原文链接](http://tryenough.com/android-momeryleak)

## [更多教程](http://tryenough.com/type-android)

***

- Handler

handler中,Runnable内部类会持有外部类的隐式引用,被传递到Handler的消息队列MessageQueue中,在Message消息没有被处理之前,Activity实例不会被销毁了,于是导致内存泄漏。

解决方法:

>1.可以把Handler类放在单独的类文件中,或者使用静态内部类便可以避免泄露;

>2.如果想在Handler内部去调用所在的Activity,那么可以在handler内部使用弱引用的方式去指向所在Activity.使用Static + WeakReference的方式来达到断开Handler与Activity之间存在引用关系的目的.

>3.在界面销毁是,释放handler资源

```java

@Override

protected void doOnDestroy() {

super.doOnDestroy();

if (mHandler != null) {

mHandler.removeCallbacksAndMessages(null);

}

mHandler = null;

mRenderCallback = null;

}

```

- 同样还有其他匿名类实例,如TimerTask、Threads等,执行耗时任务持有Activity的引用,都可能导致内存泄漏。

线程产生内存泄露的主要原因在于线程生命周期的不可控。如果我们的线程是Activity的内部类,所以MyThread中保存了Activity的一个引用,当MyThread的run函数没有结束时,MyThread是不会被销毁的,因此它所引用的老的Activity也不会被销毁,因此就出现了内存泄露的问题。

### 要解决Activity的长期持有造成的内存泄漏,可以通过以下方法:

- 传入Application 的 Context,因为 Application 的生命周期就是整个应用的生命周期,所以这将没有任何问题。

- 如果此时传入的是 Activity 的 Context,当这个 Context 所对应的 Activity 退出时,主动结束执行的任务,并释放Activity资源。

- 将线程的内部类,改为静态内部类。

因为非静态内部类会自动持有一个所属类的实例,如果所属类的实例已经结束生命周期,但内部类的方法仍在执行,就会hold其主体(引用)。也就使主体不能被释放,亦即内存泄露。静态类编译后和非内部类是一样的,有自己独立的类名。不会悄悄引用所属类的实例,所以就不容易泄露。

- 如果需要引用Acitivity,使用弱引用。

- 谨慎对context使用static关键字。

### 2.Bitmap没调用recycle()

Bitmap对象在不使用时,我们应该先调用recycle()释放内存,然后才设置为null.

### 3.集合中对象没清理造成的内存泄露

我们通常把一些对象的引用加入到了集合中,当我们不需要该对象时,并没有把它的引用从集合中清理掉,这样这个集合就会越来越大。如果这个集合是static的话,那情况就更严重了。

解决方案:

>在Activity退出之前,将集合里的东西clear,然后置为null,再退出程序。

### 4.注册没取消造成的内存泄露

这种Android的内存泄露比纯Java的内存泄漏还要严重,因为其他一些Android程序可能引用系统的Android程序的对象(比如注册机制)。即使Android程序已经结束了,但是别的应用程序仍然还有对Android程序的某个对象的引用,泄漏的内存依然不能被垃圾回收。

解决方案:

>1.使用ApplicationContext代替ActivityContext;

>2.在Activity执行onDestory时,调用反注册;

### 5.资源对象没关闭造成的内存泄露

资源性对象比如(Cursor,File文件等)往往都用了一些缓冲,我们在不使用的时候,应该及时关闭它们,以便它们的缓冲及时回收内存。而不是等待GC来处理。

### 6.占用内存较多的对象(图片过大)造成内存溢出

因为Bitmap占用的内存实在是太多了,特别是分辨率大的图片,如果要显示多张那问题就更显著了。Android分配给Bitmap的大小只有8M.

解决方法:

- 1.等比例缩小图片

```java

BitmapFactory.Options options = new BitmapFactory.Options();

options.inSampleSize = 2;//图片宽高都为原来的二分之一,即图片为原来的四分之一

```

- 2.对图片采用软引用,及时地进行recycle()操作

```java

//软引用

SoftReference<Bitmap> bitmap = new SoftReference<Bitmap>(pBitmap);

//回收操作

if(bitmap != null) {

if(bitmap.get() != null && !bitmap.get().isRecycled()){

bitmap.get().recycle();

bitmap = null;

}

}

```

### 7.WebView内存泄露(影响较大)

解决方案:

>用新的进程起含有WebView的Activity,并且在该Activity 的onDestory() 最后加上 System.exit(0); 杀死当前进程。

## 检测内存泄漏的方法

***

- 1.使用 **静态代码分析工具-Lint** 检查内存泄漏

Lint 是 Android Studio 自带的工具,使用姿势很简单 Analyze -> Inspect Code 然后选择想要扫面的区域即可

![](http://upload-images.jianshu.io/upload_images/679754-45660ec202d56c2a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/640)

![](http://upload-images.jianshu.io/upload_images/679754-983dd636a06d18f1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/640)

对可能引起泄漏的编码,Lint 都会进行温馨提示:

![](http://upload-images.jianshu.io/upload_images/679754-6c1ffe1a6d4c0c0f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/640)

- 2.LeakCanary 工具

![](http://upload-images.jianshu.io/upload_images/679754-ca1f77c33d38cee8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/240)

Square 公司出品的内存分析工具,官方地址如下:https://github.com/square/leakcanary/

LeakCanary 需要在项目代码中集成,不过代码也非常简单,如下的官方示例:

在你的 build.gradle:

```

dependencies {

debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.3'

releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.3'

// Optional, if you use support library fragments:

debugImplementation 'com.squareup.leakcanary:leakcanary-support-fragment:1.6.3'

}

```

在 Application 类:

```java

public class ExampleApplication extends Application {

@Override public void onCreate() {

super.onCreate();

if (LeakCanary.isInAnalyzerProcess(this)) {

// This process is dedicated to LeakCanary for heap analysis.

// You should not init your app in this process.

return;

}

LeakCanary.install(this);

// Normal app init code...

}

}

```

当内存泄漏发生时,LeakCanary 会弹窗提示并生成对应的堆存储信息记录

-3.Android Monitor

开Android Studio,编译代码,在模拟器或者真机上运行App,然后点击![](http://upload-images.jianshu.io/upload_images/679754-e6486dbd162610e5?imageMogr2/auto-orient/strip%7CimageView2/2/w/440)

,在Android Monitor下点击Monitor对应的Tab,进入如下界面

![](http://upload-images.jianshu.io/upload_images/679754-52b89e8e66946369?imageMogr2/auto-orient/strip%7CimageView2/2/w/640)

在Memory一栏中,可以观察不同时间App内存的动态使用情况,点击![](http://upload-images.jianshu.io/upload_images/679754-760b7d3931c95ac7?imageMogr2/auto-orient/strip%7CimageView2/2/w/440)

可以手动触发GC,点击![](http://upload-images.jianshu.io/upload_images/679754-c7ed121d9b089cb2?imageMogr2/auto-orient/strip%7CimageView2/2/w/440)

可以进入HPROF Viewer界面,查看Java的Heap,如下图

![](http://upload-images.jianshu.io/upload_images/679754-cf86cf4711762b8c?imageMogr2/auto-orient/strip%7CimageView2/2/w/640)

Reference Tree代表指向该实例的引用,可以从这里面查看内存泄漏的原因,Shallow Size指的是该对象本身占用内存的大小,Retained Size代表该对象被释放后,垃圾回收器能回收的内存总和。

## 扩展知识

***

### 四种引用类型的介绍

- 强引用(StrongReference):JVM 宁可抛出 OOM ,也不会让 GC 回收具有强引用的对象;

- 软引用(SoftReference):只有在内存空间不足时,才会被回的对象;

- 弱引用(WeakReference):在 GC 时,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存;

- 虚引用(PhantomReference):任何时候都可以被GC回收,当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否存在该对象的虚引用,来了解这个对象是否将要被回收。可以用来作为GC回收Object的标志。

***

## [原文链接](http://tryenough.com/android-momeryleak)

## [更多教程](http://tryenough.com/type-android)

***

相关推荐

俄罗斯的 HTTPS 也要被废了?(俄罗斯网站关闭)

发布该推文的ScottHelme是一名黑客,SecurityHeaders和ReportUri的创始人、Pluralsight作者、BBC常驻黑客。他表示,CAs现在似乎正在停止为俄罗斯域名颁发...

如何强制所有流量使用 HTTPS一网上用户

如何强制所有流量使用HTTPS一网上用户使用.htaccess强制流量到https的最常见方法可能是使用.htaccess重定向请求。.htaccess是一个简单的文本文件,简称为“.h...

https和http的区别(https和http有何区别)

“HTTPS和HTTP都是数据传输的应用层协议,区别在于HTTPS比HTTP安全”。区别在哪里,我们接着往下看:...

快码住!带你十分钟搞懂HTTP与HTTPS协议及请求的区别

什么是协议?网络协议是计算机之间为了实现网络通信从而达成的一种“约定”或“规则”,正是因为这个“规则”的存在,不同厂商的生产设备、及不同操作系统组成的计算机之间,才可以实现通信。简单来说,计算机与网络...

简述HTTPS工作原理(简述https原理,以及与http的区别)

https是在http协议的基础上加了一层SSL(由网景公司开发),加密由ssl实现,它的目的是为用户提供对网站服务器的身份认证(需要CA),以至于保护交换数据的隐私和完整性,原理如图示。1、客户端发...

21、HTTPS 有几次握手和挥手?HTTPS 的原理什么是(高薪 常问)

HTTPS是3次握手和4次挥手,和HTTP是一样的。HTTPS的原理...

一次安全可靠的通信——HTTPS原理

为什么HTTPS协议就比HTTP安全呢?一次安全可靠的通信应该包含什么东西呢,这篇文章我会尝试讲清楚这些细节。Alice与Bob的通信...

为什么有的网站没有使用https(为什么有的网站点不开)

有的网站没有使用HTTPS的原因可能涉及多个方面,以下是.com、.top域名的一些见解:服务器性能限制:HTTPS使用公钥加密和私钥解密技术,这要求服务器具备足够的计算能力来处理加解密操作。如果服务...

HTTPS是什么?加密原理和证书。SSL/TLS握手过程

秘钥的产生过程非对称加密...

图解HTTPS「转」(图解http 完整版 彩色版 pdf)

我们都知道HTTPS能够加密信息,以免敏感信息被第三方获取。所以很多银行网站或电子邮箱等等安全级别较高的服务都会采用HTTPS协议。...

HTTP 和 HTTPS 有何不同?一文带你全面了解

随着互联网时代的高速发展,Web服务器和客户端之间的安全通信需求也越来越高。HTTP和HTTPS是两种广泛使用的Web通信协议。本文将介绍HTTP和HTTPS的区别,并探讨为什么HTTPS已成为We...

HTTP与HTTPS的区别,详细介绍(http与https有什么区别)

HTTP与HTTPS介绍超文本传输协议HTTP协议被用于在Web浏览器和网站服务器之间传递信息,HTTP协议以明文方式发送内容,不提供任何方式的数据加密,如果攻击者截取了Web浏览器和网站服务器之间的...

一文让你轻松掌握 HTTPS(https详解)

一文让你轻松掌握HTTPS原文作者:UC国际研发泽原写在最前:欢迎你来到“UC国际技术”公众号,我们将为大家提供与客户端、服务端、算法、测试、数据、前端等相关的高质量技术文章,不限于原创与翻译。...

如何在Spring Boot应用程序上启用HTTPS?

HTTPS是HTTP的安全版本,旨在提供传输层安全性(TLS)[安全套接字层(SSL)的后继产品],这是地址栏中的挂锁图标,用于在Web服务器和浏览器之间建立加密连接。HTTPS加密每个数据包以安全方...

一文彻底搞明白Http以及Https(http0)

早期以信息发布为主的Web1.0时代,HTTP已可以满足绝大部分需要。证书费用、服务器的计算资源都比较昂贵,作为HTTP安全扩展的HTTPS,通常只应用在登录、交易等少数环境中。但随着越来越多的重要...

取消回复欢迎 发表评论: