内存溢出分析

Catalogue   

概述

OOM(Out of Memory)即内存溢出,是因为应用所需要分配的内存超过系统对应用内存的阈值,而抛出java.lang.OutOfMemoryError错误。

其根本原因是对象的生命周期不一致,导致内存泄漏。

内存抖动

内存抖动是指在短时间内有大量的对象被创建或者被回收的现象,主要是循环中大量创建、回收对象。

内存泄漏和内存溢出的区别

  • 内存溢出

    是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory。

  • 内存泄漏

    是指程序在申请内存后,无法释放已申请的内存空间。内存泄漏最终会导致内存溢出。

栈内存溢出和堆内存溢出

堆內存溢出

  • new出来的对象所需内存不够了

栈內存溢出

  • 抛出”StackOverflowError”的原因:线程请求的栈深度大于JVM所允许的最大深度。所以根本原因是,某个线程所需的栈内存超过了JVM的限制,
    而此时物理内存仍有足够的可用空间。出现的情况:方法中无限递归调用。
  • 抛出”OutOfMemoryError”的原因:无法(向操作系统)申请到足够的内存空间用来拓展栈。根本原因是,(操作系统管理的)物理内存已没有足够的
    可用内存分配给JVM的栈使用。出现的情况:方法中不停的创建线程。

可能出现OOM的场景

静态变量导致的内存泄漏

描述

比如某个静态变量持有Activity,则当Activity生命周期结束时不会被释放。

1
2
3
4
5
6
7
Static Vector v = new Vector(10);//静态变量的生命周期跟应用一致
for (int i = 1; i<100; i++)
{
Object o = new Object();
v.add(o);
o = null;
}

解决办法:

及时释放静态变量

单例模式导致的内存泄漏

描述

单例持有Activity

解决办法

如果需要持有Context,则使用ApplicationContext

属性动画导致的内存泄漏

解决办法

视图销毁时停止动画

for循环中不停的创建局部变量

非静态内部类(包括匿名内部类)默认会持有外部类的引用

当非静态内部类会持有外部类引用,如果在内部类中,将外部类的引用传入到另外的线程中,则可能造成内存泄漏。

未取消注册或回调导致的内存泄漏

比如在Activity中注册广播,如果Activity销毁后不取消注册,那么这个广播就会一直存在系统中

一些经验

  • 在onDestroy中手动释放View上的资源
  • 尽量避免使用静态变量
    • 异常状态,静态变量会被回收,系统启动恢复到当前页面时相应值可能没空
    • 可能会造成某些对象没有释放

兜底策略

  1. 在Activity、Fragment的onDestroy时,手动释放一下资源,降低内存泄漏时内存的占用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private void traverse(ViewGroup root) {
final int childCount = root.getChildCount();
for (int i = 0; i < childCount; ++i) { final View child = root.getChildAt(i);
if (child instanceof ViewGroup) { child.setBackground(null); traverse((ViewGroup) child);
} else {
if (child != null) {
child.setBackground(null); }
if (child instanceof ImageView) {
((ImageView) child).setImageDrawable(null);
} else if (child instanceof EditText) {
((EditText) child).cleanWatchers();
}
}
}
}
  1. 监控内存使用情况,到达到某个阈值时,手动释放资源。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

private static Handler lowMemoryMonitorHandler;
private static final int MEMORY_MONITOR_INTERVAL = 1000 * 60;

/**
* 开启低内存监测,如果低内存了,作出相应的反应
*/
public static void startMonitorLowMemory() {
HandlerThread thread = new
HandlerThread("thread_monitor_low_memory");
thread.start();
lowMemoryMonitorHandler = new Handler(thread.getLooper());
lowMemoryMonitorHandler.postDelayed(releaseMemoryCacheRunner, MEMORY_MONITOR_INTERVAL);
}

/**
* 低内存时释放内存资源
* 如果已用内存达到了总的 80%时,就清空缓存
*/
private static Runnable releaseMemoryCacheRunner = new Runnable() {
@Override
public void run() {
long alreadyUsedSize = Runtime.getRuntime().totalMemory() -
Runtime.getRuntime().freeMemory();
long maxSize = Runtime.getRuntime().maxMemory();
if (Double.compare(alreadyUsedSize, maxSize * 0.8) == 1) {
BitmapUtil.clearMemoryCaches();
}
lowMemoryMonitorHandler.postDelayed(releaseMemoryCacheRunner, MEMORY_MONITOR_INTERVAL);
}
};

参考