深入内存泄露

Android应用的内存泄露,其实就是java虚拟机的堆内存泄漏.
当然,当应用有ndk,jni时,没有及时free,本地堆也会出现内存泄漏.
本文只是针对JVM内存泄漏应用,进行阐述分析.

1.知识储备

1.Java内存模型

相关内存对象模型,参照博客精讲Java内存模型

1) 寄存器(register)。这是最快的保存区域,这是主要由于它位于处理器内部。然而,寄存器的数量十分有限,所以寄存器是需要由编译器分配的。我们对此没有直接的控制权,也不可能在自己的程序里找到寄存器存在的任何踪迹。

(2) 堆栈(stack)。在执行函数(方法)时,函数一些内部变量的存储都可以放在栈上面创建,函数执行结束的时候这些存储单元就会自动被释放掉。位于通用RAM(随机访问存储器)中。可通过它的“堆栈指针” 获得处理的直接支持。堆栈指针若向下移,会创建新的内存;若向上移,则会释放那些内存。这是一种特别快、特别有效的数据保存方式,仅次于寄存器。

(3) 堆(heap)。一种通用性的内存池(也在RAM区域),堆是不连续的内存区域,堆空间比较灵活也特别大。其中保存了Java对象(对象里面的成员变量也在其中)。在堆里分配存储空间时会花掉更长的时间!也叫做动态内存分配。

(4) 静态存储(static storage)。这儿的“静态”(Static)是指“位于固定位置”(尽管也在RAM 里)。程序运行期间,静态存储的数据将随时等候调用。可用static关键字指出一个对象的特定元素是静态的。但Java 对象本身永远都不会置入静态存储空间,随着JVM的生命周期结束而结束,即当app完全退出,他才会释放。

(5) 常数存储(constant storage)。常数值通常直接置于程序代码内部。这样做是安全的,因为它们永远都不会改变。

(6) 非RAM 存储(non-storage-RAM)。若数据完全独立于一个程序之外,则程序不运行时仍可存在,并在程序的控制范围之外。其中两个最主要的例子便是“ 流式对象”和“固定对象” 。对于流式对象,对象会变成字节流,通常会发给另一台机器。而对于固定对象,对象保存在磁盘中。

2.GC回收机制

引用自http://blog.csdn.net/jiafu1115/article/details/7024323

首先JVM是对堆进行回收操作.

1.JVM堆中分类

(1) 新域young generation:存储所有新成生的对象

(2) 旧域old generation:新域中的对象,经过了一定次数的GC循环后,被移入旧域

(3) 永久域PermanentGeneration:存储类和方法对象,从配置的角度看,这个域是独立的,不包括在JVM堆内。默认为4M。

2.Gc回收流程

(1) 当eden满了,触发young GC;

(2) young GC做2件事:一,去掉一部分没用的object;二,把老的还被引用的object发到survior里面,等下几次GC以后,survivor再放到old里面。

(3) 当old满了,触发full GC。full GC很消耗内存,把old,young里面大部分垃圾回收掉。这个时候用户线程都会被block。

3.Gc回收总结

(1) JVM堆的大小决定了GC的运行时间。如果JVM堆的大小超过一定的限度,那么GC的运行时间会很长。

(2) 对象生存的时间越长,GC需要的回收时间也越长,影响了回收速度。

(3) 大多数对象都是短命的,所以,如果能让这些对象的生存期在GC的一次运行周期内,wonderful!

4.应用程序中,建立与释放对象的速度决定了垃圾收集的频率。

5.如果GC一次运行周期超过3-5秒,这会很影响应用程序的运行,如果可以,应该减少JVM堆的大小了。

6.前辈经验之谈:通常情况下,JVM堆的大小应为物理内存的80%。

3.内存抖动

内存抖动这个术语可用于描述在极短时间内分配给对象的过程.

例如,当你在循环语句中配置一系列临时对象,或者在绘图功能中配置大量对象时,这相当于内循环,当屏幕需要重新绘制或出现动画时,你需要一帧帧使用这些功能,不过它会迅速增加你的堆的压力。

Memory Monitor 内存抖动图例:

2.内存泄漏对程序造成的影响

(1) 直接:消耗内存,造成系应用本身的内存不足OutOfMemory.一个android应用程序,其实就是一个jvm虚拟机实例,而一个jvm的实例,在初始的时候,大小不等 16M,32M,64M(根据手机厂商和版本不同而不同),当然大小也可以修改,参考修改博客。

(2) 间接:gc回收频繁, 造成应用卡顿ANR.

首先,当内存不足的时候,gc会主动回收没用的内存.但是,内存回收也是需要时间的.

上图中,android在画图(播放视频等)的时候,draw到界面的对象,和gc回收垃圾资源之间高频率交替的执行.就会产生内存抖动.

很多数据就会污染内存堆,马上就会有许多GCs启动,由于这一额外的内存压力,也会产生突然增加的运算造成卡顿现象

任何线程的任何操作都会需要暂停,等待GC操作完成之后,其他操作才能够继续运行,所以垃圾回收运行的次数越少,对性能的影响就越少。

3. 内存泄露的原因

内存泄漏的本质:不再用到的对象,被错误引用,而无法被回收。未引用对象可以被垃圾回收机制回收,而被引用对象不能被垃圾回收机制回收。

当内存不足,gc会回收垃圾内存, 垃圾内存是没有别人使用的内存,好的内存而内存泄漏是正在被别人使用的的内存,不属于垃圾内存堆引用内存泄漏(Heap leak)

(1) 静态变量持有 已经没有用的对象,导致对象无法被回收.例如静态集合类引起内存泄露

(2) 单例中持有的引用,当activity重新构建后,单例持有的是上一个activity实例.导致上一个无法被回收.

(3) 事件监听器和回调.如果一个类注册了监听器,但当该类不再被使用后没有注销监听器,可能会发生内存泄漏。

(4) 静态内部类,持有对象.

(5) Handler 内存泄漏

系统资源泄露(Resource Leak)

主要指程序使用系统分配的资源比如 Bitmap,handle ,SOCKET等没有使用相应的函数释放掉,导致系统资源的浪费,严重可导致系统效能降低,系统运行不稳定。在try代码块里创建连接,在finally里释放连接,就能够避免此类内存泄漏。

(1) bitmap资源未释放

(2) IO流未关闭

(3) Cursor使用完后未释放

(4) 各种连接(网络,数据库,socket等)

4.内存泄露的分析工具

在android studio 中有以下几种工具,来进行内存泄漏优化分析(eclipse也有类似工具).

1.Memory Monitor 内存监视器.

2.Dump java heap

3.Android Device Monitor(eclipse系列工具类)

4.第三方库LeakCanary(极其简单)

leakcanary的github地址: https://github.com/square/leakcanary

5.内存泄露的实例解决方案

与其说解决内存泄漏,更应该说是 避免内存泄露 .因为内存泄漏一旦产生,即使需要重启JVM,也就是重启应用,内存重新开始计算。即使这样,也没法解决。

1.单例造成的内存泄露

/**
* Created by ccj on 2016/11/3.
*/

public class SingleExample {

private static SingleExample mExample;
private Context context;

private SingleExample(Context context) {
this.context = context;
}

/**
* 当MainActivity销毁再重建后,此时的context,不会走 if (mExample == null) ,而是直接返回.
* 此时的Context 还是上一个activity实例的Context,所以,上一个activity实例并未被释放,造成内存泄漏
*
* 此时,只需要将application的上下文,作为context即可解决问题
* @param context
* @return
*/
public static SingleExample getExampleInstance(Context context) {
if (mExample == null) {
mExample = new SingleExample(context);
}
return mExample;

}

}

2.非静态内部类(匿名内部类) 的内存泄漏

非静态内部类实例:

public class MainActivity extends Activity {

//会持有MainActivity实例。MainActivity.this.a
public void load(){
new Thread(new Runnable() {
@Override
public void run() {
while(true){
try {
int b=a;
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
}
解决方案:

将非静态内部类修改为静态内部类。因为静态内部类不会隐士持有外部类

3.Handler 造成的内存泄漏

Java对引用的分类有 Strong reference, SoftReference, WeakReference, PhatomReference 四种。

在Android应用的开发中,为了防止内存溢出,在处理一些占用内存大而且声明周期较长的对象时候,可以尽量应用软引用和弱引用技术。

软/弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。利用这个队列可以得知被回收的软/弱引用的对象列表,从而为缓冲器清除已失效的软/弱引用。

Handler 实例:

/* 在 Activity 中避免使用非静态内部类,比如上面我们将 Handler 声明为静态的,
则其存活期跟 Activity 的生命周期就无关了。同时通过弱引用的方式引入 Activity,
避免直接将 Activity 作为 context 传进去,
推荐使用静态内部类 + WeakReference 这种方式。每次使用前注意判空
*/
public class SampleActivity extends Activity {
private final Handler mLeakyHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
// ...
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Post a message and delay its execution for 10 minutes.
mLeakyHandler.postDelayed(new Runnable() {
@Override
public void run() { /* ... */ }
}, 5000);
// Go back to the previous Activity.
finish();
}
}

解决方案

//改进机制

/*当然在Activity销毁时候也应该取消相应的任务AsyncTask.cancel(),避免任务在后台执行浪费资源*/。
public class MainActivity extends AppCompatActivity {
private MyHandler mHandler = new MyHandler(this);
private TextView mTextView ;
private static class MyHandler extends Handler {
private WeakReference<Context> reference;
public MyHandler(Context context) {
reference = new WeakReference<>(context);
}
@Override
public void handleMessage(Message msg) {
MainActivity activity = (MainActivity) reference.get();
if(activity != null){
activity.mTextView.setText("");
}
}
}

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextView = (TextView)findViewById(R.id.textview);
loadData();
}

private void loadData() {
//...request
Message message = Message.obtain();
mHandler.sendMessage(message);
}
//注意释放
@Override
protected void onDestroy() {
super.onDestroy();
mHandler.removeCallbacksAndMessages(null);
}
}

4.监听器注册造成的内存泄漏

在观察者模式中, 有一个统一的观察者collector集合,
事件监听器和回调.如果一个类注册了监听器,但当该类不再被使用后没有注销监听器,可能会发生内存泄漏。例如,系统的传感器sensor监听器,
窗口改变监听WindowFocusChangeListener等等.

监听器实例:

系统级别的监听,例如重力感应监听sensorManager.registerListener(),如果不及时取消注册,就会造成内存泄漏.

首先看Sensor中的官方注释

/* Always make sure to disable sensors you don't need, especially when your activity is paused. Failing to do so can drain the battery in just a few hours. Note that the system will <i>not</i> disable sensors automatically when the screen turns off.*/
SensorManager sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ALL);
//监听
sensorManager.registerListener(this,sensor,SensorManager.SENSOR_DELAY_FASTEST);

实例1解决方案:

protected void onPause() {
* super.onPause();
* mSensorManager.unregisterListener(this);
* }

观察者模式实例2:

//自己的观察者模式.
public class ListenerCollector {
static private WeakHashMap<View,MyView.MyListener> sListener = new WeakHashMap<>();
public void setsListener(View view, MyView.MyListener listener){ sListener.put(view,listener);}
//解决方案
public static void clearListeners(){
//hashmap移除监听。
sListener.clear();
};
}

public class MyView extends View{
public MyView(Context context){
super(context);
init();
}

public interface MyListener{
public void myListenerCallback();
}

private void init(){
ListenerCollector collector = new ListenerCollector();
collector.setsListener(this,myListener);
}

private MyListener myListener = new MyListener() {
@Override
public void myListenerCallback() {
System.out.print("有被调用");
}
};

}

//activity调用处
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
MyView myView = new MyView(this);
setContentView(myView);

}

实例2解决方案

@Override
protected void onStop() {
super.onStop();
ListenerCollector.clearListeners();
}

5.资源未关闭造成的内存泄漏

对于使用了BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,造成内存泄漏。

6.内存泄漏总结

1、对于生命周期比Activity长的对象如果需要应该使用ApplicationContext

2、在涉及到Context时先考虑ApplicationContext,当然它并不是万能的,对于有些地方则必须使用Activity的Context,对于Application,Service,Activity三者的Context的应用场景如下:
这里写图片描述

其中:NO1表示Application和Service可以启动一个Activity,不过需要创建一个新的task任务队列。而对于Dialog而言,只有在Activity中才能创建

3、对于需要在静态内部类中使用非静态外部成员变量(如:Context、View ),可以在静态内部类中使用弱引用来引用外部类的变量来避免内存泄漏

4、对于生命周期比Activity长的内部类对象,并且内部类中使用了外部类的成员变量.将内部类改为静态内部类,静态内部类中使用弱引用来引用外部类的成员变量

5、对于不再需要使用的对象,显示的将其赋值为null,比如使用完Bitmap后先调用recycle(),再赋为null

6、保持对对象生命周期的敏感,特别注意单例、静态对象、全局性集合等的生命周期。

原文链接:

https://blog.csdn.net/ccj659/article/details/53032683

[转]深入Android内存泄露的更多相关文章

  1. (转)专项:Android 内存泄露实践分析

    今天看到一篇关于Android 内存泄露实践分析的文章,感觉不错,讲的还算详细,mark到这里. 原文发表于:Testerhome: 作者:ycwdaaaa ;  原文链接:https://teste ...

  2. Android内存泄露

    Android 内存泄漏是一个十分头疼的事情.LeakCanary是一款开源软件,主要作用是检测 Android APP 内存泄露.比起以前的 MAT 工具,LeakCanary 有着十分强大的功能, ...

  3. android内存泄露调试,Heap,MAT

    三.内存监测工具 DDMS --> Heap 无论怎么小心,想完全避免bad code是不可能的,此时就需要一些工具来帮助我们检查代码中是否存在会造成内存泄漏的地方.Android tools中 ...

  4. Android内存泄露测试

    Android性能测试过程中的一些常用命令: CPU: adb shell top -n | grep "+PackageName 内存: adb shell dumpsys meminfo ...

  5. android内存泄露小谈

    在做android的时候,用的语言大部分情况下都是java.以前最开始做的是编译器开发, 大部分情况都是用c语言和x86与arm架构的汇编,后来接触到ios用的是OC.对比之下, 感觉还是java用起 ...

  6. android 内存泄露之jni local reference table overflow (max=512)

    在android项目中要实现一个需求 为了性能的要求只能用c代码来实现功能. 这样就牺牲了java跨平台性. 通过加载.so的方式,把用c实现的模块集成到app中. android提供jni层,作为一 ...

  7. Android内存泄露---检测工具篇

    内存使用是程序开发无法回避的一个问题.如果我们毫不在意肆意使用,总有一天会为此还账,且痛不欲生...所以应当防患于未然,把内存使用细化到平时的每一行代码中. 内存使用概念较大,本篇先讲对已有app如何 ...

  8. Android内存泄露的原因

    (一)释放对象的引用,误将一个本来生命周期短的对象存放到一个生命周期相对较长的对象中,也称“对象游离“.隐蔽的内部类(Anonymous Inner Class): mHandler = new Ha ...

  9. JVM内存管理概述与android内存泄露分析

    一.内存划分 将内存划分为六大部分,分别是PC寄存器.JAVA虚拟机栈.JAVA堆.方法区.运行时常量池以及本地方法栈. 1.PC寄存器(线程独有):全称是程序计数寄存器,它记载着每一个线程当前运行的 ...

  10. Android 内存泄露测试数据处理--procrank,setprop,getprop(转)

    1.Android内存测试常用的几个概念. VSS--virtual set size 虚拟耗用内存(包含共享库占用的内存)RSS--Resident set size实际使用的物理内存(包含共享库占 ...

随机推荐

  1. 对多线程java内存模型JMM

    多线程概念的引入体现了人类重新有效压力寨计算机.这是非常有必要的,由于所涉及的读数据的过程中的一般操作,如从磁盘.其他系统.数据库等,CPU计算速度和数据读取速度已经严重失衡.假设印刷过程中一个线程将 ...

  2. HDU 1224 Free DIY Tour - 最短路

    传送门 题目大意: 一个有向图(n + 1相当于1),每个点有一个权值(可以认为1和n+1权值为0),求从1走到n+1(相当于走回1)的最大路径权值和是多少,输出方案. 题目分析: 最短路问题,输出方 ...

  3. sql server中查询结果集顺序问题

    因为优化器可能会选择并行处理,或者在多文件情况下不按“期待”顺序扫描数据,所以无法保证数据的顺序.唯一能确保顺序的只有order by. 并行处理的过程导致顺序不一致,单核上不存在并行,而双核,可能使 ...

  4. zoj 1008 Gnome Tetravex

    开放式存储阵列为每平方米有几个,否则,超时-- #include <stdio.h> #include <string.h> #include <iostream> ...

  5. TensorFlow 学习(十一)—— 正则(regularizer)

    正则作用的对象是目标函数,如图对均方误差使用 ℓ2 正则: loss = tf.reduce_mean(tf.square(y-y_) + tf.contrib.layers.l2_regulariz ...

  6. hadoop 3.x 回收站

    使用回收站最主要是为了给误删文件的你留条后路 打开core-site.xml添加以下配置 <!--回收站保存文件时间--> <property> <name>fs. ...

  7. PAT 1061 - 1064 题解

    这四道题来自 13 年 08 月 30 的 PAT 测试. 代码量不大,思路也比较直接.不过第一题的处理逻辑不太清晰,需要好好把握.稍有不慎就掉进坑里了(很多人被这道 20'的题坑了一个多小时心慌意乱 ...

  8. Windows Container 和 Docker

    Windows Container 和 Docker 微软在2016年的Ignite技术大会上正式发布了Windows Server 2016,其中的容器服务已经可以作为生产环境使用.这意味着Wind ...

  9. 开源|LightGBM:三天内收获GitHub 1000+ 星

    原创 2017-01-05 LightGBM 微软研究院AI头条 [导读]不久前微软DMTK(分布式机器学习工具包)团队在GitHub上开源了性能超越其他boosting工具的LightGBM,在三天 ...

  10. linux awk(good)

    一个用awk处理字符串的例子: #!/bin/bash source="nokia201703148855" preffixStr=$(echo $source |awk '{pr ...