前言JAVA程序,因为有垃圾回收机制,应该不会有内存泄漏。我们已经知道,如果一个对象从根节点可达,即从根节点到该对象存在引用链,那么该对象就不会被GC回收。如果这个对象不再被使用,它就没有用了。如果我们仍然持有它的引用,就会导致内存泄漏。例如,一个长时间在后台运行的线程持有对Activity的引用。这时Activity执行了onDestroy方法,那么这个Activity就是一个从根节点可达的无用对象,这个Activity对象就是一个泄漏对象,分配给这个对象的内存不会被回收。如果我们的java运行时间长了,这种内存泄漏不断的发生,到最后就没有可用的内存了。当然,在java中,内存泄漏和C/C++是不一样的。如果java程序完全结束,它的所有对象都是不可达的,系统可以对它们进行垃圾回收。它的内存泄漏仅限于自身,不会影响整个系统。C/C++的内存泄漏更甚。它的内存泄漏是在系统级别。即使C/C++程序退出,其泄漏的内存也无法被系统回收,除非重启机器,否则永远无法使用。我们的文章开始总结Android内存泄漏;一、Android内存泄漏介绍1、什么是内存泄漏?无法释放,导致系统内存浪费,导致程序运行速度变慢甚至系统崩溃等严重后果。内存泄漏缺陷具有隐蔽性和累积性,比其他内存非法访问错误更难检测。因为内存泄漏的原因是内存块没有释放,所以属于遗漏缺陷而不是故障缺陷。此外,内存泄漏通常不会直接产生可观察到的错误症状,而是逐渐累积,降低系统的整体性能,极端情况下可能导致系统崩溃;Android应用程序中的内存泄漏对其他应用程序几乎没有影响。为了让Android应用程序安全、快速地运行,每个Android应用程序都会使用一个专用的Dalvik虚拟机实例来运行,该实例由Zygote服务进程孵化,也就是说每个应用程序都运行在自己的进程中。Android为不同类型的进程分配不同的内存使用限制。如果程序在运行过程中发生内存泄漏,应用进程使用的内存超过了这个限制,就会被系统认为是内存泄漏而被杀死,这使得只有自己的进程被杀死,而不会影响其他进程(如果system_process等系统进程出现问题,会导致系统重启)。2、内存泄漏的危害用户对单次内存泄漏没有感知,但当泄漏累积到内存被消耗时,就会造成卡顿甚至死机;频繁的gc回收导致应用卡死ANR:当内存不足时GC会主动回收无用的内存。但是,内存回收也需要时间。内存回收和gc回收垃圾资源的高频交替执行,会造成内存抖动。大量的数据会污染内存堆。马上就会有很多GC。由于这种额外的内存压力,计算量也会突然增加,从而导致卡顿。任何线程的任何操作都需要暂停。等待GC操作完成,然后其他操作才能继续运行。因此,垃圾回收运行的次数越少,对性能的影响就越小;3、内存泄漏的原因①内存空间在使用后没有回收,会导致内存泄漏。虽然Java有垃圾回收机制,但是Java中还是有很多代码逻辑会导致内存泄漏。垃圾回收器会回收大部分的内存空间,但是有些内存空间仍然保留着引用,但是逻辑上不再被再次使用的对象,此时垃圾回收器是无能为力的,无法回收它们,例如:忘记释放分配的内存;应用程序不需要该对象,但不释放该对象的引用;垃圾收集器不能回收这个对象;持有对象生命周期过长,无法回收;②Android(Java)平台的内存泄漏是指在无用的对象资源和GCRoots之间维护一条可访问的路径,导致系统无法回收;③那么从栈中弹出的对象就不会被当作垃圾回收处理,即使程序不再使用栈中的这些对象,它们也不会被回收,因为栈中仍然保存着这个对象的引用,俗称作为过期引用,这个内存泄漏非常隐蔽;2.Detect内存泄漏检测工具①MemoryMonitor位于AndroidMonitor中,该工具可以:方便的显示内存使用情况和GC状态快速定位freeze是否与GC有关快速定位Crash是否与内存占用高有关关于快速定位潜在内存leaks(内存占用一直在增长)但没有准确定位问题②AllocationTracker这个工具的用途:可以定位代码中分配的对象类型、大小、时间、线程、栈等信息可以定位内存抖动问题配合HeapViewer定位内存泄漏问题(可以找出泄漏对象创建的位置等)使用方法:MemoryMonitor中有一个StartAllocationTracking按钮可以开始跟踪。点击停止追踪后,会显示统计结果。③HeapViewer该工具用于:显示内存快照信息,收集每次GC后的信息,发现内存泄漏。使用方法:内存监视器中有一个DumpJavaHeap按钮,点击它,在统计报告的左上角选择包分类。通过MemoryMonitor的initiateGC(executeGC)按钮,可以检测内存泄漏等情况。④LeakCanarydependencies{debugImplementation'com.squareup.leakcanary:leakcanary-android:1.6.3'releaseImplementation'com.squareup.leakcanary:leakcanary-android-no-op:1.6.3'//可选,ifyouusesupportlibraryfragments:debugImplementation'com.squareup.leakcanary:leakcanary-support-fragment:1.6.3'}直接在Application中使用,然后运行APP会自动检测,检测到会在另一个APP上通知,显示详情publicclassExampleApplicationextendsApplication{@OverridepublicvoidonCreate(){super.onCreate();if(LeakCanary.isInAnalyzerProcess(this)){//ThisprocessisdedicatedtoLeakCanaryforheapanalysis.//Youshouldnotinityyourappinthisprocess.return;}LeakCanary.install(this);//Normalappinitcode...}}3.常见内存泄漏场景详解1.单例Android开发中经常使用单例模式,但如果使用不当,会导致内存泄漏。由于单例的静态特性,它的生命周期与应用程序的生命周期一样长。如果一个对象不再有用,但单例仍然持有它的引用,那么在应用程序的整个生命周期中都不能正常使用它。回收,导致内存泄漏。publicclassAppSettings{privatestaticvolatileAppSettingssingleton;privateContextmContext;privateAppSettings(Contextcontext){this.mContext=context;}publicstaticAppSettingsgetInstance(Contextcontext){if(singleton==null){synchronized(AppSettings.class){if(singleton==null){singleton=newAppSettings(context);}}}returnssingleton;}}对于像上面代码这样的单例,如果我们调用getInstance(Contextcontext)方法时传入的context参数是Activity、Service等的context,就会造成内存泄露。以Activity为例,当我们启动一个Activity,调用getInstance(Contextcontext)方法获取AppSettings的单实例时,传入Activity.this作为context,这样AppSettings类的单实例sInstance持有Activity的引用,当我们退出Activity时,Activity是无用的,但是由于sIntance作为静态单例(存在于应用程序的整个生命周期中)会继续持有Activity的引用,导致Activity对象无法回收释放,造成内存泄漏。为了避免此类单例导致内存泄漏,我们可以将context参数改为全局上下文:privateAppSettings(Contextcontext){this.mContext=context.getApplicationContext();}2.静态变量导致内存泄漏静态变量存放在方法区,它的生命周期从类加载开始,到整个进程结束。一旦一个静态变量被初始化,它持有的引用直到进程结束才会被释放。比如下面这种情况,为了避免在Activity中重复创建info,将sInfo作为静态变量使用:}classInfotext{priv;publicInfo(Contextcontext){this.mContext=context;}}}Info是Activity的静态成员,持有Activity的引用,但是作为静态变量,sInfo的生命周期肯定比Activity长。所以当Activity退出时,sInfo仍然引用Activity,Activity无法被回收,从而导致内存泄漏。在Android开发中,静态持有可能经常会因为生命周期不一致而导致内存泄露,所以我们在创建静态持有变量时需要考虑各个成员之间的引用关系,尽量少用静态持有变量,避免内存泄露。当然,我们也可以在适当的时候将静态值重置为null,使其不再持有引用,这样也可以避免内存泄漏。3.非静态内部类导致内存泄漏。默认情况下,非静态内部类(包括匿名内部类)将保存对外部类的引用。当非静态内部类对象的生命周期比外部类对象的生命周期长时,就会导致内存泄漏。非静态内部类导致的内存泄漏是Android开发中使用Handler的典型场景。很多开发者使用Handler这样写:publicclassMainActivity2extendsAppCompatActivity{@OverrideprotectedvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);start();}privatevoidstart(){Messagemessage=Message.obtain();message.what=1;mHandler.sendMessage(message);}privateHandlermHandler=newHandler(){@OverridepublicvoidhandleMessage(Messagemsg){super.handleMessage(msg);if(msg.what==1){//doNothing}}};}可能有人会说mHandler不将Activity的引用作为静态变量持有,其生命周期不得长于Activity。它不一定会导致内存泄漏。显然不是这样!熟悉Handler消息机制的人都知道,mHandler会作为成员变量存储在发送消息msg中,即msg持有mHandler的引用,而mHandler是Activity的非静态内部类实例,即,mHandler持有Activity的Reference,那么我们可以理解为msg间接持有Activity的引用。msg发送后,先放入消息队列MessageQueue,然后等待Looper的轮询处理(MessageQueue和Looper都是关联线程的,MessageQueue是Looper引用的成员变量,Looper存放在ThreadLocal中)。那么当Activity退出时,msg可能还存在于MessageQueue中未处理或正在处理,这将导致Activity不被回收,导致Activity内存泄漏。通常在Android开发中,如果既要使用内部类又要避免内存泄漏,一般会使用静态内部类+弱引用。MyHandlermHandler;publicstaticclassMyHandlerextendsHandler{privateWeakReference
