Android开发笔记——常见BUG类型之内存泄露与线程安全

简介:

一、内存泄露

1、很抱歉,”XXX”已停止运行。OOM?

20160509-1

怎样才能让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

20160509-2

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

20160507-2

 

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内存优化(使用SparseArray和ArrayMap代替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泄露的截图:
 20160512-1

  • 内部类引用导致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、下面的方法是线程安全的吗?

怎样使上述方法线程安全?
 

2、Java中的线程安全

怎样保持在多线程环境下的数据一致性,Java提供了多种方法实现:

  1. synchronized

  2. java.util.concurrent.atomic

  3. java.util.concurrent.locks

  4. thread safe collection(ConcurrentHashMap)

  5. 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 synchronizationprivate Object mutex=new Object();
...//using synchronized block to read, increment and update count value synchronouslysynchronized (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();
        }
    }
}














本文转自xsster51CTO博客,原文链接:http://blog.51cto.com/12945177/1929765 ,如需转载请自行联系原作者




相关文章
|
29天前
|
编解码 算法 Java
构建高效的Android应用:内存优化策略详解
随着智能手机在日常生活和工作中的普及,用户对移动应用的性能要求越来越高。特别是对于Android开发者来说,理解并实践内存优化是提升应用程序性能的关键步骤。本文将深入探讨针对Android平台的内存管理机制,并提供一系列实用的内存优化技巧,以帮助开发者减少内存消耗,避免常见的内存泄漏问题,并确保应用的流畅运行。
|
1月前
|
Java 调度 Android开发
构建高效Android应用:探究Kotlin多线程编程
【2月更文挑战第17天】 在现代移动开发领域,性能优化一直是开发者关注的焦点。特别是在Android平台上,合理利用多线程技术可以显著提升应用程序的响应性和用户体验。本文将深入探讨使用Kotlin进行Android多线程编程的策略与实践,旨在为开发者提供系统化的解决方案和性能提升技巧。我们将从基础概念入手,逐步介绍高级特性,并通过实际案例分析如何有效利用Kotlin协程、线程池以及异步任务处理机制来构建一个更加高效的Android应用。
40 4
|
1月前
|
API 数据库 Android开发
构建高效Android应用:探究Kotlin多线程优化策略
【2月更文挑战第14天】随着移动设备性能的日益强大,用户对应用程序的响应速度和流畅性要求越来越高。在Android开发中,合理利用多线程技术是提升应用性能的关键手段之一。Kotlin作为一种现代的编程语言,其协程特性为开发者提供了更为简洁高效的多线程处理方式。本文将深入探讨使用Kotlin进行Android多线程编程的最佳实践,包括协程的基本概念、优势以及在实际项目中的应用场景和性能优化技巧,旨在帮助开发者构建更加高效稳定的Android应用。
|
3月前
|
Java 调度 数据库
Android 性能优化: 如何进行多线程编程以提高应用性能?
Android 性能优化: 如何进行多线程编程以提高应用性能?
47 0
|
2月前
|
Android开发
【Bug】Android resource linking failed和error: failed linking references.
【Bug】Android resource linking failed和error: failed linking references.
|
5天前
|
移动开发 Android开发 开发者
构建高效Android应用:采用Kotlin进行内存优化的策略
【4月更文挑战第18天】 在移动开发领域,性能优化一直是开发者关注的焦点。特别是对于Android应用而言,由于设备和版本的多样性,确保应用流畅运行且占用资源少是一大挑战。本文将探讨使用Kotlin语言开发Android应用时,如何通过内存优化来提升应用性能。我们将从减少不必要的对象创建、合理使用数据结构、避免内存泄漏等方面入手,提供实用的代码示例和最佳实践,帮助开发者构建更加高效的Android应用。
5 0
|
7天前
|
缓存 移动开发 Java
构建高效的Android应用:内存优化策略
【4月更文挑战第16天】 在移动开发领域,尤其是针对资源有限的Android设备,内存优化是提升应用性能和用户体验的关键因素。本文将深入探讨Android应用的内存管理机制,分析常见的内存泄漏问题,并提出一系列实用的内存优化技巧。通过这些策略的实施,开发者可以显著减少应用的内存占用,避免不必要的后台服务,以及提高垃圾回收效率,从而延长设备的电池寿命并确保应用的流畅运行。
|
9天前
|
Java API 调度
安卓多线程和并发处理:提高应用效率
【4月更文挑战第13天】本文探讨了安卓应用中多线程和并发处理的优化方法,包括使用Thread、AsyncTask、Loader、IntentService、JobScheduler、WorkManager以及线程池。此外,还介绍了RxJava和Kotlin协程作为异步编程工具。理解并恰当运用这些技术能提升应用效率,避免UI卡顿,确保良好用户体验。随着安卓技术发展,更高级的异步处理工具将助力开发者构建高性能应用。
|
21天前
|
安全 Linux API
Android进程与线程
Android进程与线程
18 0
|
26天前
|
缓存 Java Android开发
安卓应用开发中的内存优化策略
在移动应用开发领域,性能一直是衡量应用质量的重要指标之一。特别是对于安卓平台,由于设备的硬件配置多样化,内存管理成为开发者面临的重大挑战。本文将深入探讨针对安卓平台的内存优化技巧,包括内存泄漏的预防、合理使用数据结构和算法、以及高效的资源释放机制。通过这些方法,开发者可以显著提升应用的性能和用户体验。

热门文章

最新文章