概述
OOM(Out of Memory)即内存溢出,是因为应用所需要分配的内存超过系统对应用内存的阈值,而抛出java.lang.OutOfMemoryError错误。
其根本原因是对象的生命周期不一致,导致内存泄漏。
内存抖动
内存抖动是指在短时间内有大量的对象被创建或者被回收的现象,主要是循环中大量创建、回收对象。
内存泄漏和内存溢出的区别
栈内存溢出和堆内存溢出
堆內存溢出
栈內存溢出
- 抛出”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上的资源
- 尽量避免使用静态变量
- 异常状态,静态变量会被回收,系统启动恢复到当前页面时相应值可能没空
- 可能会造成某些对象没有释放
兜底策略
- 在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 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); }
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); } };
|
参考