Android开发笔记——常见BUG类型之内存泄露与线程安全
怎样才能让app报OOM呢?最简单的办法如下:
Bitmapbt1=BitmapFactory.decodeResource(this.getResources(),R.drawable.p_w_picpath);Bitmapbt2=BitmapFactory.decodeResource(this.getResources(),R.drawable.p_w_picpath);Bitmapbtn=...2、查看内存占用
命令行:adb shell dumpsys meminfopackageName
通过Android Studio的Memory Monitor查看内存中Dalvik Heap的实时变化
3、发生内存泄露的条件首先,每个app有最大内存限制。
ActivityManageractivityManager=(ActivityManager)context.getSystemServiceContext.ACTIVITY_SERVICE);activityManager.getMemoryClass();
getMemoryClass()取到的是最大内存资源。Android中的堆内存分为NativeHeap和DalvikHeap。C/C++申请的内存空间在NativeHeap中,Java申请的内存空间则在DalvikHeap中。对于head堆的大小限制,可以查看/system/build.prop文件:
dalvik.vm.heapstartsize=8mdalvik.vm.heapgrowthlimit=96mdalvik.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上传
privatestaticMap<ComponentName,ExceptionHandler>configMap=newHashMap<ComponentName,ExceptionHandler>();publicstaticvoidsetActivity(finalActivityactivity,booleansend2Server){Log.d(TAG,"bindexceptionhandler:"+activity.getComponentName().getClassName());//上下文初始化SDKContext.init(activity.getApplication());init(activity.getApplication());ExceptionHandlerexceptionHandler=newExceptionHandler(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)。
/***Returnsthemaximumnumberofbytestheheapcanexpandto.See{@link#totalMemory}forthe*currentnumberofbytestakenbytheheap,and{@link#freeMemory}forthecurrentnumberof*thosebytesactuallyusedbyliveobjects.*/intmaxMemory=Runtime.getRuntime().maxMemory());//应用程序最大可用内存/***Returnsthenumberofbytestakenbytheheapatitscurrentsize.Theheapmayexpandor*contractovertime,asthenumberofliveobjectsincreasesordecreases.See*{@link#maxMemory}forthemaximumheapsize,and{@link#freeMemory}foranideaofhowmuch*theheapcouldcurrentlycontract.*/longtotalMemory=Runtime.getRuntime().totalMemory());//应用程序已获得内存/***Returnsthenumberofbytescurrentlyavailableontheheapwithoutexpandingtheheap.See*{@link#totalMemory}fortheheap'scurrentsize.Whenthesebytesareexhausted,theheap*mayexpand.See{@link#maxMemory}forthatlimit.*/longfreeMemory=Runtime.getRuntime().freeMemory());//应用程序已获得内存中未使用内存
5)注意Cursor对象是否及时关闭
在程序中我们经常会进行查询数据库的操作,但时常会存在不小心使用Cursor之后没有及时关闭的情况。这些Cursor的泄露,反复多次出现的话会对内存管理产生很大的负面影响,我们需要谨记对Cursor对象的及时关闭。
4.4 内存使用策略优化
谨慎使用large heap
综合考虑设备内存阈值与其他因素设计合适的缓存大小
onLowMemory()/onTrimMemory(int)
资源文件需要选择合适的文件夹进行存放
Try catch某些大内存分配的操作
谨慎使用static对象
优化布局层次,减少内存消耗
谨慎使用多进程
谨慎使用依赖注入框架
使用ProGuard来剔除不需要的代码
谨慎使用第三方libraries
考虑不同的实现方式来优化内存占用
二、线程安全1、下面的方法是线程安全的吗?怎样使上述方法线程安全?
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;intframeLeft=frame.left;intframeTop=frame.top;if(currentPossible.isEmpty()){lastPossibleResultPoints=null;}else{possibleResultPoints=newArrayList<>(5);lastPossibleResultPoints=currentPossible;paint.setAlpha(CURRENT_POINT_OPACITY);paint.setColor(resultPointColor);synchronized(currentPossible){for(ResultPointpoint:currentPossible){canvas.drawCircle(frameLeft+(int)(point.getX()*scaleX),frameTop+(int)(point.getY()*scaleY),POINT_SIZE,paint);}}}
上述方法中,possibleResultPoints的创建没有采用同步措施,需要使用Collections.synchronizedXxx。
List<MyType>list=Collections.synchronizedList(newArrayList(<MyType>));...synchronized(list){for(MyTypem:list){foo(m);m.doSomething();}}
一般比较推荐创建一个虚拟的对象专门用于获取锁。
//dummyobjectvariableforsynchronizationprivateObjectmutex=newObject();...//usingsynchronizedblocktoread,incrementandupdatecountvaluesynchronouslysynchronized(mutex){count++;}
PS:直接在方法上加synchronized可能DoS***喔,举个栗子:
publicclassMyObject{//Locksontheobject'smonitorpublicsynchronizedvoiddoSomething(){//...}}//***的代码MyObjectmyObject=newMyObject();synchronized(myObject){while(true){//IndefinitelydelaymyObjectThread.sleep(Integer.MAX_VALUE);}}
***的代码获取了MyObject对象的锁,导致doSomething死锁,从而引发Denial of Service。
publicclassMyObject{//locksontheclassobject'smonitorpublicstaticsynchronizedvoiddoSomething(){//...}}//***的代码synchronized(MyObject.class){while(true){Thread.sleep(Integer.MAX_VALUE);//IndefinitelydelayMyObject}}
2) 死锁。
publicclassThreadDeadlock{publicstaticvoidmain(String[]args)throwsInterruptedException{Objectobj1=newObject();Objectobj2=newObject();Objectobj3=newObject();Threadt1=newThread(newSyncThread(obj1,obj2),"t1");Threadt2=newThread(newSyncThread(obj2,obj3),"t2");Threadt3=newThread(newSyncThread(obj3,obj1),"t3");t1.start();Thread.sleep(5000);t2.start();Thread.sleep(5000);t3.start();}}classSyncThreadimplementsRunnable{privateObjectobj1;privateObjectobj2;publicSyncThread(Objecto1,Objecto2){this.obj1=o1;this.obj2=o2;}@Overridepublicvoidrun(){Stringname=Thread.currentThread().getName();System.out.println(name+"acquiringlockon"+obj1);synchronized(obj1){System.out.println(name+"acquiredlockon"+obj1);work();System.out.println(name+"acquiringlockon"+obj2);synchronized(obj2){System.out.println(name+"acquiredlockon"+obj2);work();}System.out.println(name+"releasedlockon"+obj2);}System.out.println(name+"releasedlockon"+obj1);System.out.println(name+"finishedexecution.");}privatevoidwork(){try{Thread.sleep(30000);}catch(InterruptedExceptione){e.printStackTrace();}}}
声明:本站所有文章资源内容,如无特殊说明或标注,均为采集网络资源。如若本站内容侵犯了原著者的合法权益,可联系本站删除。