Android开发笔记——常见BUG类型之内存泄露与线程安全
本文内容来源于最近一次内部分享的总结,没来得及详细整理,见谅。
本次分享主要对内存泄露和线程安全这两个问题进行一些说明,内部代码扫描发现的BUG大致分为四类:1)空指针;2)除0;3)内存、资源泄露;4)线程安全。第一、二个问题属于编码考虑不周,第三、四个问题则需要更深入的分析。
- 内存泄露
- 线程安全
一、内存泄露
1、很抱歉,”XXX”已停止运行。OOM?
怎样才能让app报OOM呢?最简单的办法如下:
Bitmap bt1 = BitmapFactory.decodeResource(this.getResources(), R.drawable.image);
Bitmap bt2 = BitmapFactory.decodeResource(this.getResources(), R.drawable.image);
Bitmap btn = ...
2、查看内存占用
- 命令行:adb shell dumpsys meminfo packageName

- 通过Android Studio的Memory Monitor查看内存中Dalvik Heap的实时变化

3、发生内存泄露的条件
首先,每个app有最大内存限制。
ActivityManager activityManager = (ActivityManager) context.getSystemServiceContext.ACTIVITY_SERVICE);
activityManager.getMemoryClass();
getMemoryClass()取到的是最大内存资源。Android中的堆内存分为Native Heap和Dalvik Heap。C/C++申请的内存空间在Native Heap中,Java申请的内存空间则在Dalvik Heap中。对于head堆的大小限制,可以查看/system/build.prop文件:
dalvik.vm.heapstartsize=8m
dalvik.vm.heapgrowthlimit=96m
dalvik.vm.heapsize=256m
注意:
heapsize参数表示单个进程heap可用的最大内存,但如果存在以下参数”dalvik.vm.headgrowthlimit =96m”表示单个进程heap内存被限定在96m,即程序运行过程实际只能使用96m内存。
如果申请的内存资源超过上述限制,系统就会抛出OOM错误。
4、常见避免OOM的措施
以下主要从四个方面总结常见的措施:1)减小对象的内存占用;2)内存对象的重复利用;3)避免对象的内存泄露;4)内存使用策略优化。
4.1 减小对象的内存占用
- 使用ArrayMap/SparseArray而不是HashMap等传统数据结构。
- 在Android中避免使用枚举。
- 减小Bitmap对象的内存占用。inSampleSize和decode format。
4.2 内存对象的重复利用
- ListView/GridView等出现大量重复子组件的视图里面对ConvertView的复用
- 使用LRU机制缓存Bitmap
- 避免在onDraw方法里面执行对象的创建
- 使用StringBuilder来替代频繁的”+”
4.3 避免对象的内存泄露
4.1和4.2都是比较常规的措施,4.3需要重点关注。
1)Activity泄露
导致Activity泄露的原因较多,下面列举一些比较常见的。从原理上主要分为两类:i)静态对象;ii)this$0。
- Activity被static变量引用。这段代码来自于我们的Crash上传
private static Map<ComponentName, ExceptionHandler> configMap =
new HashMap<ComponentName, ExceptionHandler>();
public static void setActivity(final Activity activity, boolean send2Server) {
Log.d(TAG, "bind exception handler : " + activity.getComponentName().getClassName());
//上下文初始化
SDKContext.init(activity.getApplication());
init(activity.getApplication());
ExceptionHandler exceptionHandler = new ExceptionHandler(
activity, send2Server, Thread.getDefaultUncaughtExceptionHandler());
configMap.put(activity.getComponentName(), exceptionHandler);
Thread.setDefaultUncaughtExceptionHandler(exceptionHandler);
}
下面是通过MAT分析一个Activity泄露的截图:

- 内部类引用导致Activity的泄漏
最典型的场景是Handler导致的Activity泄漏,如果Handler中有延迟的任务或者是等待执行的任务队列过长,都有可能因为Handler继续执行而导致Activity发生泄漏。此时的引用关系链是Looper -> MessageQueue -> Message -> Handler -> Activity。为了解决这个问题,可以在UI退出之前,执行remove Handler消息队列中的消息与runnable对象。或者是使用Static + WeakReference的方式来达到断开Handler与Activity之间存在引用关系的目的。
可参考链接:线程通信
2)考虑使用Application Context而不是Activity Context
对于大部分非必须使用Activity Context的情况(Dialog的Context就必须是Activity Context),我们都可以考虑使用Application Context而不是Activity的Context,这样可以避免不经意的Activity泄露。
3)注意临时Bitmap对象的及时回收
虽然在大多数情况下,我们会对Bitmap增加缓存机制,但是在某些时候,部分Bitmap是需要及时回收的。例如临时创建的某个相对比较大的bitmap对象,在经过变换得到新的bitmap对象之后,应该尽快回收原始的bitmap,这样能够更快释放原始bitmap所占用的空间。
4)内存占用监控
通过Runtime获取maxMemory,而maxMemory-totalMemory即为剩余可使用的dalvik内存。定期检查这个值,达到80%就去释放各种cache资源(bitmap的cache)。
/**
* Returns the maximum number of bytes the heap can expand to. See {@link #totalMemory} for the
* current number of bytes taken by the heap, and {@link #freeMemory} for the current number of
* those bytes actually used by live objects.
*/
int maxMemory = Runtime.getRuntime().maxMemory()); // 应用程序最大可用内存
/**
* Returns the number of bytes taken by the heap at its current size. The heap may expand or
* contract over time, as the number of live objects increases or decreases. See
* {@link #maxMemory} for the maximum heap size, and {@link #freeMemory} for an idea of how much
* the heap could currently contract.
*/
long totalMemory = Runtime.getRuntime().totalMemory()); // 应用程序已获得内存
/**
* Returns the number of bytes currently available on the heap without expanding the heap. See
* {@link #totalMemory} for the heap's current size. When these bytes are exhausted, the heap
* may expand. See {@link #maxMemory} for that limit.
*/
long freeMemory = Runtime.getRuntime().freeMemory()); // 应用程序已获得内存中未使用内存
5)注意Cursor对象是否及时关闭
在程序中我们经常会进行查询数据库的操作,但时常会存在不小心使用Cursor之后没有及时关闭的情况。这些Cursor的泄露,反复多次出现的话会对内存管理产生很大的负面影响,我们需要谨记对Cursor对象的及时关闭。
4.4 内存使用策略优化
- 谨慎使用large heap
- 综合考虑设备内存阈值与其他因素设计合适的缓存大小
- onLowMemory()/onTrimMemory(int)
- 资源文件需要选择合适的文件夹进行存放
- Try catch某些大内存分配的操作
- 谨慎使用static对象
- 优化布局层次,减少内存消耗
- 谨慎使用多进程
- 谨慎使用依赖注入框架
- 使用ProGuard来剔除不需要的代码
- 谨慎使用第三方libraries
- 考虑不同的实现方式来优化内存占用
二、线程安全
1、下面的方法是线程安全的吗?
class MyCounter {
private static int counter = 0;
public static int getCount() {
return counter++;
}
}怎样使上述方法线程安全?
2、Java中的线程安全
怎样保持在多线程环境下的数据一致性,Java提供了多种方法实现:
- synchronized
- java.util.concurrent.atomic
- java.util.concurrent.locks
- thread safe collection(ConcurrentHashMap)
- volatile
2.1 synchronized
JVM保证被synchronized关键字修饰的代码段在同一时间只能被一个线程访问,内部通过对对象或类加锁来实现的。当方法被synchronized修饰时,锁加在对象上;当方法同时为static时,锁加在类上。从性能的角度来讲,一般不建议直接将锁加在类上,这样会使得类的所有对象的该方法均为synchronized的。
从之前扫描的问题来看,在编写synchronized程序时主要有两点需要注意:
- synchronized需要创建基于对象或者类的锁,所以不能在构造器或者变量上加锁。
- synchronized造成死锁。
1) 锁加在哪里?
List<ResultPoint> currentPossible = possibleResultPoints;
List<ResultPoint> currentLast = lastPossibleResultPoints;
int frameLeft = frame.left;
int frameTop = frame.top;
if (currentPossible.isEmpty()) {
lastPossibleResultPoints = null;
} else {
possibleResultPoints = new ArrayList<>(5);
lastPossibleResultPoints = currentPossible;
paint.setAlpha(CURRENT_POINT_OPACITY);
paint.setColor(resultPointColor);
synchronized (currentPossible) {
for (ResultPoint point : currentPossible) {
canvas.drawCircle(frameLeft
+ (int) (point.getX() * scaleX), frameTop
+ (int) (point.getY() * scaleY), POINT_SIZE,
paint);
}
}
}
上述方法中,possibleResultPoints的创建没有采用同步措施,需要使用Collections.synchronizedXxx。
List<MyType> list = Collections.synchronizedList(new ArrayList(<MyType>));
...
synchronized(list){
for(MyType m : list){
foo(m);
m.doSomething();
}
}
一般比较推荐创建一个虚拟的对象专门用于获取锁。
//dummy object variable for synchronization
private Object mutex=new Object();
...
//using synchronized block to read, increment and update count value synchronously
synchronized (mutex) {
count++;
}
PS:直接在方法上加synchronized可能DoS攻击喔,举个栗子:
public class MyObject {
// Locks on the object's monitor
public synchronized void doSomething() {
// ...
}
}
// 黑客的代码
MyObject myObject = new MyObject();
synchronized (myObject) {
while (true) {
// Indefinitely delay myObject
Thread.sleep(Integer.MAX_VALUE);
}
}
黑客的代码获取了MyObject对象的锁,导致doSomething死锁,从而引发Denial of Service。
public class MyObject {
//locks on the class object's monitor
public static synchronized void doSomething() {
// ...
}
}
// 黑客的代码
synchronized (MyObject.class) {
while (true) {
Thread.sleep(Integer.MAX_VALUE); // Indefinitely delay MyObject
}
}
2) 死锁。
public class ThreadDeadlock {
public static void main(String[] args) throws InterruptedException {
Object obj1 = new Object();
Object obj2 = new Object();
Object obj3 = new Object();
Thread t1 = new Thread(new SyncThread(obj1, obj2), "t1");
Thread t2 = new Thread(new SyncThread(obj2, obj3), "t2");
Thread t3 = new Thread(new SyncThread(obj3, obj1), "t3");
t1.start();
Thread.sleep(5000);
t2.start();
Thread.sleep(5000);
t3.start();
}
}
class SyncThread implements Runnable{
private Object obj1;
private Object obj2;
public SyncThread(Object o1, Object o2){
this.obj1=o1;
this.obj2=o2;
}
@Override
public void run() {
String name = Thread.currentThread().getName();
System.out.println(name + " acquiring lock on "+obj1);
synchronized (obj1) {
System.out.println(name + " acquired lock on "+obj1);
work();
System.out.println(name + " acquiring lock on "+obj2);
synchronized (obj2) {
System.out.println(name + " acquired lock on "+obj2);
work();
}
System.out.println(name + " released lock on "+obj2);
}
System.out.println(name + " released lock on "+obj1);
System.out.println(name + " finished execution.");
}
private void work() {
try {
Thread.sleep(30000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
上述代码会输出什么呢?
Android开发笔记——常见BUG类型之内存泄露与线程安全的更多相关文章
- 转:Android开发:使用DDMS Heap进行内存泄露调试
无论怎么小心,想完全避免bad code是不可能的,此时就需要一些工具来帮助我们检查代码中是否存在会造成内存泄漏的地方.Android tools中的DDMS就带有一个很不错的内存监测工具Heap,本 ...
- 【转】Android开发笔记(序)写在前面的目录
原文:http://blog.csdn.net/aqi00/article/details/50012511 知识点分类 一方面写写自己走过的弯路掉进去的坑,避免以后再犯:另一方面希望通过分享自己的经 ...
- Android 性能优化之使用MAT分析内存泄露问题
我们平常在开发Android应用程序的时候,稍有不慎就有可能产生OOM,虽然JAVA有垃圾回收机,但也不能杜绝内存泄露,内存溢出等问题,随着科技的进步,移动设备的内存也越来越大了,但由于Android ...
- Android 性能优化之使用MAT分析内存泄露
转载请注明本文出自xiaanming的博客(http://blog.csdn.net/xiaanming/article/details/42396507),请尊重他人的辛勤劳动成果,谢谢! 我们平常 ...
- Android开发中常见的设计模式 MD
Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina ...
- Android开发笔记——以Volley图片加载、缓存、请求及展示为例理解Volley架构设计
Volley是由Google开源的.用于Android平台上的网络通信库.Volley通过优化Android的网络请求流程,形成了以Request-RequestQueue-Response为主线的网 ...
- [置顶] Android开发笔记(成长轨迹)
分类: 开发学习笔记2013-06-21 09:44 26043人阅读 评论(5) 收藏 Android开发笔记 1.控制台输出:called unimplemented OpenGL ES API ...
- 【转】Android开发笔记——圆角和边框们
原文地址:http://blog.xianqu.org/2012/04/android-borders-and-radius-corners/ Android开发笔记——圆角和边框们 在做Androi ...
- 《ArcGIS Runtime SDK for Android开发笔记》
开发笔记之基础教程 ArcGIS Runtime SDK for Android 各版本下载地址 <ArcGIS Runtime SDK for Android开发笔记>——(1).And ...
随机推荐
- Eclipse为Unity3d编写jar组件
Unity3d和Android的交互有两种方式: (1)使用Eclipse为Unity3d编写库,也就是jar包,然后导入到U3D中使用: (2)将Unity3d项目导出为Android项目,然后直接 ...
- codeforces D. Design Tutorial: Inverse the Problem
题意:给定一个矩阵,表示每两个节点之间的权值距离,问是否可以对应生成一棵树, 使得这棵树中的任意两点之间的距离和矩阵中的对应两点的距离相等! 思路:我们将给定的矩阵看成是一个图,a 到 b会有多条路径 ...
- solrcloud使用中遇到的问题及解决方式
首先声明,我们团队在使用solrcloud过程中踩了一些坑,同事(晓磊和首富)进行了总结,我列到我的博客上做记录用: Q:为什么Solr里面的时间比数据库里面早8小时? Solr默认采用的时区是UTC ...
- github神器--Atom编辑器初体验
Atom 1.0正式式版已经出来好几天,自从听说github出了这神器之后,一直想体验一吧,这两天终于体验上. 下载: https://atom.io/ 其实,我的网速还不错,但总是下载到一半就没网速 ...
- ASP.NET在不同情况下实现单点登陆(SSO)的方法
第一种:同主域但不同子域之间实现单点登陆 Form验证其实是基于身份cookie的验证.客户登陆后,生成一个包含用户身份信息(包含一个ticket)的cookie,这个cookie的名字就是在web. ...
- CentOS6.5菜鸟之旅:安装rpmforge软件库
一.rpmforge软件库 rpmforge是包含4000多种CentOS软件的软件库,被CentOS社区认为是安全和稳定的软件库. 二.安装rpmforege 1. 在http:/ ...
- Linux永久修改系统时间和时区方法
修改时区: 1> 找到相应的时区文件 /usr/share/zoneinfo/Asia/Shanghai 用这个文件替换当前的/etc/localtime文件. 或者找你认为是标准时间的服务器, ...
- sprint3冲刺团队贡献分-软件工程
蔡舜 : 20 卢晓洵 : 19 林宇粲 :22 王昕明 :21
- 组合数学 - 波利亚定理 --- poj : 2154 Color
Color Time Limit: 2000MS Memory Limit: 65536K Total Submissions: 7873 Accepted: 2565 Description ...
- .net C# 对虚拟目录IIS的操作
一.查看虚拟目录是否存在 private bool IsExitesVirtualDir(string virtualdirname) { bool exited =false; Dire ...