内存优化_unity内存优化 - CSDN
内存优化 订阅
程序在运行时,Windows会将其直接调入到物理内存中,但物理内存毕竟有限,因此,微软又设计了虚拟内存,它其实就是硬盘中的一块空间,Windows会将一些暂时不用,但可能以后会用到的数据从物理内存移动到虚拟内存中,从而保证有足够的物理内存给当前运行的程序使用。所以,电脑的内存=实际物理内存容量+“分页文件”(就是交换文件)。如果需要,“分页文件”会动用硬盘上所有可用空间。内存优化的好处是:在将占用物理内存的程序移动到虚拟内存后,再启动新程序,程序、系统运行的速度会变得更快,提升系统工作效率。 展开全文
程序在运行时,Windows会将其直接调入到物理内存中,但物理内存毕竟有限,因此,微软又设计了虚拟内存,它其实就是硬盘中的一块空间,Windows会将一些暂时不用,但可能以后会用到的数据从物理内存移动到虚拟内存中,从而保证有足够的物理内存给当前运行的程序使用。所以,电脑的内存=实际物理内存容量+“分页文件”(就是交换文件)。如果需要,“分页文件”会动用硬盘上所有可用空间。内存优化的好处是:在将占用物理内存的程序移动到虚拟内存后,再启动新程序,程序、系统运行的速度会变得更快,提升系统工作效率。
信息
提    高
内存的使用效率
优    化
内存的管理
中文名
内存优化
目    的
主要是为了保持虚拟内存的连续性
内存优化技巧方法
如何优化内存的管理,提高内存的使用效率,尽可能地提高运行速度,是我们所关心的问题。下面介绍在Windows操作系统中,提高内存的使用效率和优化内存管理的几种方法。改变页面文件的位置其目的主要是为了保持虚拟内存的连续性。因为硬盘读取数据是靠磁头在磁性物质上读取,页面文件放在磁盘上的不同区域,磁头就要跳来跳去,自然不利于提高效率。而且系统盘文件众多,虚拟内存肯定不连续,因此要将其放到其他盘上。改变(调整大小)页面文件位置的方法是:用鼠标右键点击“我的电脑”,选择“属性→ 高级→性能设置→高级→更改虚拟内存”,在驱动器栏里选择想要改变到的位置即可。值得注意的是,当移动好页面文件后,要将原来的文件删除(系统不会自动删除)。因为C盘扇区最近,所以尽量把虚拟内存设置在C盘,且不要设置其他盘的内存,使其具有连续性。改变页面文件的大小改变了页面文件的位置后,我们还可以对它的大小进行一些调整。调整时我们需要注意,不要将最大、最小页面文件设为等值。因为通常内存不会真正“塞满”,它会在内存储量达到一定程度时,自动将一部分暂时不用的数据放到硬盘中。最小页面文件越大,所占比例就低,执行的速度也就越慢。最大页面文件是极限值,有时打开很多程序,内存和最小页面文件都已“塞满”,就会自动溢出到最大页面文件。所以将两者设为等值是不合理的。一般情况下,最小页面文件设得小些,这样能在内存中尽可能存储更多数据,效率就越高。最大页面文件设得大些,以免出现“满员”的情况。禁用页面文件当拥有了8GB以上的内存时,页面文件的作用将不再明显,因此我们可以将其禁用。方法是:依次进入注册表编辑器“HKEY_LOCAL_MACHINESystemCurrentControlSetControlSession Ma-nagerMemoryManagement”下,在“DisablePa-ging Executive”(禁用页面文件)选项中将其值设为“1”即可。清空页面文件在同一位置上有一个“ClearPageFileAtShutdown(关机时清除页面文件)”,将该值设为“1”。这里所说的“清除”页面文件并非是指从硬盘上完全删除pagefile.sys文件,而是对其进行“清洗”和整理,从而为下次启动Windows XP时更好地利用虚拟内存做好准备。调整高速缓存区域的大小可以在“计算机的主要用途”选项卡中设置系统利用高速缓存的比例(针对Windows 98)。如果系统的内存较多,可选择“网络服务器”,这样系统将用较多的内存作为高速缓存。在CD-ROM标签中,可以直接调节系统用多少内存作为CD-ROM光盘读写的高速缓存。监视内存系统的内存不管有多大,总是会用完的。虽然有虚拟内存,但由于硬盘的读写速度无法与内存的速度相比,所以在使用内存时,就要时刻监视内存的使用情况。Windows操作系统中提供了一个系统监视器,可以监视内存的使用情况。一般情况下如果只有60%的内存资源可用,这时你就要注意调整内存了,不然就会严重影响电脑的运行速度和系统性能,否则会很卡的。及时释放内存空间如果你发现系统的内存不多了,就要注意释放内存。所谓释放内存,就是将驻留在内存中的数据从内存中释放出来。释放内存最简单有效的方法,就是重新启动计算机。另外,就是关闭暂时不用的程序。还有要注意剪贴板中如果存储了图像资料,是要占用大量内存空间的。这时只要剪贴几个字,就可以把内存中剪贴 板上原有的图片冲掉,从而将它所占用的大量的内存释放出来。优化内存中的数据在Windows中,驻留内存中的数据越多,就越要占用内存资源。所以,桌面上和任务栏中的快捷图标不要设置得太多。如果内存资源较为紧张,可以考虑尽量少用各种后台驻留的程序。平时在操作电脑时,不要打开太多的文件或窗口。长时间地使用计算机后,如果没有重新启动计算机,内存中的数据排列就有可能因为比较混乱,从而导致系统性能的下降。这时你就要考虑重新启动计算机。提高系统其他部件的性能计算机其他部件的性能对内存的使用也有较大的影响,如总线类型、CPU、硬盘和显存等。如果显存太小,而显示的数据量很大,再多的内存也是不可能提高其运行速度和系统效率的。如果硬盘的速度太慢,则会严重影响整个系统的工作。提高计算机运算速度优质的内存能提高计算机的内存读取力度,加强计算机各部件功能的协调性,使计算机的运行速度更流畅。
收起全文
精华内容
参与话题
  • 内存优化(一)浅谈内存优化

    千次阅读 2019-01-02 15:49:23
    本系列文章,主要是总结我对Android开发过程中内存优化的理解,很多东西都是平常的习惯和一些细节问题,重在剖析优化的原理,养成一种良好的代码习惯。 概述 既然谈优化,就绕不开Android三个内存相关的经典问题...

    本系列文章,主要是总结我对Android开发过程中内存优化的理解,很多东西都是平常的习惯和一些细节问题,重在剖析优化的原理,养成一种良好的代码习惯。

    概述

    既然谈优化,就绕不开Android三个内存相关的经典问题:

    • OOM
    • 内存泄漏
    • 频繁GC卡顿

    导致这三个问题的原因:

    OOM

    App在启动时会从系统分配一个默认的堆内存,同时拥有一个堆内存最大值(可以动态申请这个大小),这个Max Heap Size的大小,决定了软件运行时可以申请的最大运行内存。App软件内存分配是个不断创建和GC回收的过程,就像一个水池拥有注入和排出水的通道,当注入过快,排出不足时,水池满了溢出,Out of Memory,即我们常说的OOM。

    内存泄漏

    当我们在代码中创建对象,会申请内存空间,同时包含一个对象的引用,当我们长时间不使用该引用时,JVM GC操作时会根据这个引用去释放内存。但是,对象的回收可能有点差错,如果这个对象A被另一个线程B所引用,当我们不再使用A,可A却处于B的hold状态,那么我们每次创建的A都得不到回收,这个时候就会发生内存泄漏了。

    频繁GC卡顿

    上面说了,App的堆内存有最大值,是有限的,那么如果我们频繁的创建,当运行内存不断上升,为了维持App的运行,GC回收也会频繁操作,软件运行资源有些,必然导致卡顿问题。

    JAVA的GC机制,非常的复杂和精辟,不可一言概论之,在看过许多blog之后,给出一点自己的总结。

    简述JVM GC

    我们都知道Java语言非常的方便,不像C语言,申请和释放内存都是自己操作,java有虚拟机帮忙。Android 的每个应用程序都会使用一个专有的Dalvik虚拟机实例来运行,即使内存泄漏也只是kill当前App.

    Java虚拟机有一套完整的GC方案,只是简单理解的话就是,它维持着一个对象关系树,当开始GC操作时,它会从GC Roots开始扫描整个Object Tree,当发现某个无法从Tree中引用到的对象时,便将其回收

    GC Roots分类举例:

    • Class类
    • Alive Thread
    • 线程stack上的对象,如方法或者局部变量
    • JNI活动对象
    • System Class Loader

    Java中的引用关系

    java中有四种对象引用关系,分别是:强引用StrongRefernce、软引用SoftReference、弱引用WeakReference、虚引用PhantomReference,这四种引用关系分别对应的效果:

    StrongRefernce

    通过new创建的对象,如Object obj = new Object();,强引用不会被垃圾回收器回收和销毁,即是OOM,所以这也容易造成我们接下来会分析的**《非静态内部类持有对象导致的内存泄漏问题》**

    SoftReference

    软引用可以被垃圾回收器回收,但它的生命周期要强于弱引用,但GC回收发生时,只有在内存空间不足时才会回收它

    WeakReference

    弱引用的生命周期短,可以被GC回收,但GC回收发生时,扫描到弱引用便会被垃圾回收和销毁掉

    PhantomReference

    虚引用任何时候都可以被GC回收,它不会影响对象的垃圾回收机制,它只有一个构造函数,因此只能配合ReferenceQueue一起使用,用于记录对象回收的过程

    	PhantomReference(T referent, ReferenceQueue<? super T> q)
    

    关于ReferenceQueue

    他的作用主要用于记录引用是否被回收,除了强引用其他的引用方式得构造函数中都包含了ReferenceQueue参数。当调用引用的get()方法返回null时,我们的对象不一定已经回收掉了,可能正在进入回收流程中,而当对象被确认回收后,它的引用会被添加到ReferenceQueue中。

    Felix obj = new Felix();
    ReferenceQueue<Felix> rQueue = new ReferenceQueue<Felix>();
    WeakReference<Felix> weakR = new WeakReference<Felix>(obj,rQueue);
    

    总结

    看完Android引用和回收机制,我们对于代码中内存问题的原因也有一定认识,当时现实中内存泄漏或者溢出的问题,总是不经意间,在我之后一些列的文章中,会对不同场景的代码问题进行分析和解决,一起来关注吧!

    展开全文
  • 内存优化

    2019-01-08 14:59:18
    内存泄漏 内存管理 内存模型  Android原生开发以java为主。 在java中,Java内存模型,往往是指Java程序在运行时内存的模型,而Java代码是运行在Java虚拟机之上的,所以Java内存模型,也就是指Java虚拟机的运行...

    内存泄漏

    内存管理

    内存模型

        Android原生开以java主。

    在java中,Java内存模型,往往是指Java程序在运行时内存的模型,而Java代码是运行在Java虚拟机之上的,所以Java内存模型,也就是指Java虚拟机的运行时内存模型。

     

             java中内存全权交给虚拟机去管理,那虚拟机的运行时内存是如何构成的?

            

    很多时候,我们提到内存,会说到堆和栈,这是对内存粗略的一种划分,这种划分的对应内存模型的Java堆,是指虚拟机栈,但是实际上Java内存模型比这复杂多了。

            

             在曾经的日公司(sun 已被甲骨文2009收购) 制定的java虚拟机规范中,运行时内存模型,分为线程私有和共享数据区两大类,其中线程私有的数据区包含程序计数器、虚拟机栈、本地方法区,所有线程共享的数据区包含Java堆、方法区,在方法区内有一个常量池。

     

     

    2.1 程序数器PC

    程序计数器PC是一块较小的内存空间,可以看作所执行字节码的行号指示器。字节码解释器就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,比如循环、跳转、异常处理等等这些基础功能都需要依赖这个计数器来完成。

    在开发多线程应用时,由于Java中的多线程是抢占式的调用,也就是任何一个确定的时刻,cpu都只会执行一条线程,执行哪条线程也是不确定的。所以为了线程切换后能够回到正确的执行位置,每个线程都需要一个独立的程序计数器,各条线程之间计数器互不影响,独立存储,所以这块区域是线程私有的内存。

    当线程正在执行一个Java方法时,PC计数器记录的是正在执行的虚拟机字节码的地址;当线程正在执行的一个Native方法时,PC计数器则为空Undefined)。这一块的内存区域是唯一一个在java虚拟机规范中没有规定任何OutOfMemoryError的区域

    2.2

    和程序计数器一样,虚拟机栈也是线程私有的,它的生命周期与线程相同。虚拟机栈描述的是java方法执行的内存模型。

    每个方法(不包含native方法)执行的同时都会创建一个栈帧 用于存储局部变量表、操作数栈、动态链接、方法出口等信息。

             我们平时所说的栈内存就是指的这一块区域。

    Java虚拟机规范规定该区域有两种异常:

    • StackOverFlowError:当线程请求栈深度超出虚拟机栈所允许的深度时抛出 (递归函数)
    • OutOfMemoryError:当Java虚拟机动态扩展到无法申请足够内存时抛出 (OOM)

    2.3 本地方法

    本地方法栈和虚拟机栈差不多,前者是为虚拟机使用到的Native方法提供内存空间。有些虚拟机的实现直接把本地方法栈和虚拟机栈合二为一,比如主流的HotSpot虚拟机。

    异常(Exception)Java虚拟机规范规定该区域可抛出StackOverFlowErrorOutOfMemoryError

    2.4 Java

    Java堆,是Java虚拟机管理的最大的一块内存,也是GC的主战场,所以可以叫它gc(垃圾堆),里面存放的是几乎所有的对象实例和数组数据。

    异常(Exception)Java虚拟机规范规定该区域可抛出OutOfMemoryError

    2.5 方法区

    方法区主要存放的是已被虚拟机加载的类信息、常量、静态变量、编译器编译后的代码等数据。Java虚拟机规范对这一块区域的限制非常宽松,不同的虚拟机实现也不同,相对而言垃圾回收在这个区域比较少的出现。根据java虚拟机规范,当方法区无法满足内存分配需求时,会抛出oom异常。

    2.6 运行常量池

    运行时常量池是方法区的一部分,用于存放编译器生成的各种字面量和符号引用。运行时常量池除了编译期产生的Class文件的常量池,还可以在运行期间,将新的常量加入常量池,比较String类的intern()方法。

    • 字面量:与Java语言层面的常量概念相近,包含文本字符串、声明为final的常量值等。
    • 符号引用:编译语言层面的概念,包括以下3类:
      • 类和接口的全限定名
      • 字段的名称和描述符
      • 方法的名称和描述符

    属于方法区一部分,所以和方法区一样,会oom

     

    局部变量的基本数据类型和引用存储于栈中,引用的对象实体存储于堆中。

    ——因为它们属于方法中的变量,生命周期随方法而结束。

    成员变量全部存储与堆中(包括基本数据类型,引用和引用的对象实体)

    ——因为它们属于类,类对象终究是要被new出来使用的。

     

    们说的内存泄露,是针对,也只针对堆内存,他存放的就是引用指向的体。

     

    内存的分配是由程序完成的,而内存的释放是由垃圾收集器(Garbage CollectionGC)完成的,java程序员不需要通过调用函数来释放内存,但gc只能回收无用并且不再被其它对象引用的那些对象所占用的空间。

     

    堆中几乎存放着Java世界中所有的例,垃圾收集器在堆回收之前,第一件事情就是要确定象哪些存活着,哪些象已死去(即不可能再被任何途径使用的)

     

    确定象是否活着的方法有:

    1、引用数算法

           1.1算法分析 

    引用计数是垃圾收集器中的早期策略。在这种方法中,堆中每个对象实例都有一个引用计数。当一个对象被创建时,且将该对象实例分配给一个变量,该变量计数设置为1。当任何其它变量被赋值为这个对象的引用时,计数加1a = b,b引用的对象实例的计数器+1),当一个对象实例的某个引用超过了生命周期或者被设置为一个新值时,对象实例的引用计数器减1。任何引用计数器为0的对象实例可以被当作垃圾收集。当一个对象实例被垃圾收集时,它引用的任何对象实例的引用计数器减1

    1.2优缺点

    优点:

      引用计数收集器可以很快的执行,交织在程序运行中。对程序需要不被长时间打断的实时环境比较有利。

    缺点:

      无法检测出循环引用。如父对象有一个对子对象的引用,子对象反过来引用父对象。这样,他们的引用计数永远不可能为0.

    1引用计数算法无法解决循环引用问题,例如:

    public class Main {
        public static void main(String[] args) {
            MyObject object1 = new MyObject();
            MyObject object2 = new MyObject();
            
            object1.object = object2;
            object2.object = object1;
            
            object1 = null;
            object2 = null;
        }
    }

    最后面两句将object1object2赋值为null,也就是说object1object2指向的对象已经不可能再被访问,但是由于它们互相引用对方,导致它们的引用计数器都不为0,那么垃圾收集器就永远不会回收它们。

     

     

    2、可达性分析算法(主流方法)

    https://img-blog.csdn.net/20160728194719701

      可达性分析算法中,通过一系列的gc root为起始点,从一个GC ROOT开始,寻找对应的引用节点,找到这个节点以后,继续寻找这个节点的引用节点,当所有的引用节点寻找完毕之后,剩余的节点则被认为是没有被引用到的节点,即无用的节点。

    java中可作为GC Root的对象有

      1.虚拟机栈(本地变量表)中正在运行使用的引用

      2.方法区中静态属性引用的对象

      3. 方法区中常量引用的对象

    4.本地方法栈JNI中引用的对象(Native对象)

    上图中objDobjEGC ROOT不可达,所以可以被回收。而其他的对gc root可达。

    在代码看来,类似于:(GC/Main.java

     

    但是即使在可达性分析算法中不可达的对象,也并非一定要死。当gc第一次扫过这些对象的时候,他们处于“死缓”的阶段。要真正执行死刑,至少需要经过两次标记过程。如果对象经过可达性分析之后发现没有与GC Roots相关联的引用链,那他会被第一次标记,并经历一次筛选。这个对象的finalize方法会被执行。如果对象没有覆盖finalize或者已经被执行过了。虚拟机也不会去执行finalize方法。Finalize是对象逃狱的最后一次机会。

    Reference项目的FinalizeEscapeGC

    输出结果:

    在例子中,对象第一次被执行了finalize方法,但是把自己上交给国家逃了一死,但是在给国家执行任务的时候,不幸牺牲了。所以没办法再自救了。

    这个对象的finalize方法执行了一次(自救而不是被救,this赋值,所以给的还是自己)

     

     

    在说到内存的问题,我们都会提到一个关键词:引用。

        通俗的,通A用并访问到B,那就明A持有B的引用,或A就是B的引用。

        比如 Person p1 = new Person();P1能操作Person对象,因此P1Person的引用;p1是O中的一个成员变量,因此我可以使用o.p1的方式来访问Person类对象的成员,因此o持有一个Person对象的引用

       

        GC过程与对象的引用类型是密切相关的,

    Java对引用的分类Strong reference(强), SoftReference(软), WeakReference(), PhatomReference()

    强引用就是在程序代码中普遍存在的,比如”Object obj = new Object()”这种引用,只要强引用还在,垃圾收集器就不会回收被引用的对象。

    软引用用来定义一些还有用但并非必须的对象。对于软引用关联着的对象,在系统将要内存溢出之前,会将这些对象列入回收范围进行第二次回收,如果回收后还是内存不足,才会抛出内存溢出。

    弱引用也是用来描述非必须对象。但他的强度比软引用更弱一些。被弱引用关联的对象只能生存到下一次垃圾收集发生之前。当垃圾收集器回收时,无论内存是否足够,都会回收掉被弱引用关联的对象。

    虚引用也称为幽灵引用或者幻影引用,是最弱的引用关系。一个对象的虚引用根本不影响其生存时间,也不能通过虚引用获得一个对象实例。虚引用的唯一作用就是这个对象被GC时可以收到一条系统通知。

     

    Android应用的开发中,为了防止内存溢出,在处理一些占用内存大而且生命周期较长候,可以尽量引用和弱引用技

     

    对于软引用和弱引用的选择,

    如果只是想避免OutOfMemory异常的生,可以使用引用。如果用的性能更在意,想尽快回收一些占用内存比大的象,可以使用弱引用。另外可以根据象是否常使用来判断选择软引用是弱引用。如果该对象可能会常使用的,就尽量用引用。如果该对象不被使用的可能性更大些,就可以用弱引用。

     

    内存泄漏就是

    堆内存中的生命周期的象持有短生命周期象的引用,尽管短生命周期象已不再需要,但是因为长生命周期象持有它的引用而致不能被回收,就是Java中内存泄露的根本原因。

     总结一句就是不需要了该回收因引用问题导致不能回收。

     

    内存泄漏会致可用内存慢慢少,程序慢慢卡。最终还会导致臭名昭著的oom 内存溢出。

     

     

    我们的android程序应该怎么排查内存泄漏问题呢?

    在android中我们执行一段代,比如入了一个新的面(Activity),这时候我的内存使用肯定比在前一个面大,而在界面finish返回后,如果内存没有回落,那么很有可能就是出了内存泄漏。

     

    从内存监控工具中观察内存曲线,是否存在不断上升的趋势且不会在程序返回时明显回落。这种方式可以发现最基本,也是最明显的内存泄露问题,对用户价值最大,操作难度小,性价比极高。

    因为他能发现很明显很严重的内存泄漏问题

     

    可以通AS的Memory Profile或者DDMS中的heap察内存使用情况。

       

     

     

    Android Profile

       https://developer.android.com/studio/preview/features/android-profiler.html

     

         The new Android Profiler window in Android Studio 3.0 replaces the Android Monitor tools.

     

        在Android Studio我们可以 运行app。

    然后我们能看到

     

    我们点击memory后

    • ① 强制执行垃圾收集事件的按钮。
    • ② 捕获堆转储的按钮。
    • ③ 记录内存分配的按钮。
    • ④ 放大时间线的按钮。
    • ⑤ 跳转到实时内存数据的按钮。
    • ⑥ 事件时间线显示活动状态、用户输入事件和屏幕旋转事件。
    • ⑦ 内存使用时间表,其中包括以下内容: 
      • 每个内存类别使用多少内存的堆栈图,如左边的y轴和顶部的颜色键所示。
      • 虚线表示已分配对象的数量,如右侧y轴所示。
      • 每个垃圾收集事件的图标。

     

    与之前的Android监控工具相比,新的内存分析器记录了更多内存使用情况,所以看起来你的内存使用量会更高。内存分析器监视一些额外的类别,这些类别增加了总数。

     

     

    在比较茫然的情况下,不知道哪儿出现了内存泄漏,我们可以进行一刀切,来个大致的排查。

     

    我们进入了一大堆页面并最终返回到主页。

     

     

    然后gc ,再dump下内存查看。AS可以点击

    然后等待一段时间会出现:

     

    但是说实话这个页面想要分析出什么很难。一般不会使用这个页面来进行分析,最多打开看一眼刚刚我们进入的Activity是否因为我们退出而回收。

     

    先按照包名来分组,

    Alloc Cout : 对象数

    Shallow Size : 对象占用内存大小

    Retained Set : 对象引用组占用内存大小(包含了这个对象引用的其他对象)

     

    当然一次dump可能并不能发现内存泄漏,可能每次我们dump的结果都不同,那么就需要多试几次,然后结合代码来排查。

     

     

    这里还不能确定发生了内存泄漏。

    我们这时候可以借助一些更专业的工具来进行内存的分析。

    我们先把 这个内存快照保存为hprof文件。

     

    AS自动分析

    其实现在的AS,可以说是非常强大了。我们把刚刚保存的hprof文件拖入到AS中。

     

    这个自动分析任务包含了两个内容,一个是检测Activity的泄漏,一个是检测重复字符串。

    点击运行分析:

    这里出现了MainActivity的泄漏。并且观察到这个MainActivity可能不止一个对象存在,可能是我们上次退出程序的时候发生了泄漏,导致它不能回收。而在此打开app,系统会创建新的MainActivity。

    但是在AS上发现为何没被回收需要运气,更方便的工具是Mat。

     

     

    Memory Analyzer Tool基于eclipse

    可以直接下载:

    http://www.eclipse.org/mat/downloads.php

    也可以在eclispe上安装mat插件:

    点击eclipse marketplace...搜索memory。

     

     

    在使用mat之前我们需要把快照文件转换一下,

    转换工具在sdk/platform-tools/hprof-conv

    -z:排除不是app的内存,比如Zygote

    hprof-conv -z src dst

     

     

    然后在Mat中打开它:

    打开我们的快照文件,

     

    之后我们能看到

     

    我们点击

    以直方图的方式来显示当前内存使用情况可能更加适合较为复杂的内存泄漏分析,它默认直接显示当前内存中各种类型对象的数量及这些对象的shallow heap和retained heap。结合MAT提供的不同显示方式,往往能够直接定位问题

    • shallow heap:指的是某一个对象所占内存大小。
    • retained heap:指的是一个对象与所包含对象所占内存的总大小。

     

    out查看这个对象持有的外部对象引用

    incoming查看这个对象被哪些外部对象引用

     

    我们现在希望查看为什么这个对象还存在,那么

    排除软弱虚引用。

    关于这个问题是android系统的一个bug。

    原因是Activity的DecorView请求了InputMethodManager,而InputMethodManager一直持有DecorView的引用,导致无法释放Activity。

    解决办法是:

     

     

     

     

    这个问题是一个第三方库中持有引用,导致的无法释放。

    这个问题可能是这个第三方库本身的问题,也可能是我们使用不当引起的。为了确定这个问题。我们进入被混淆了的g.e这个类,

    注意这里是g.e中的a成员是一个HashMap,

    结合代码

     

    这里面保存的都是一些Observer对象。这里就需要结合对代码的熟悉度,加上一些猜测来寻找问题。

    在Activity中搜索Observer,可以找到很多个new Observer。

    但是我们注意,调用的地方第二个参数是true,表示注册;如果传false表示注销

     

    而这里只有注册,没有注销。

     

    我们在onDestory中加入注销。

     

    修改完成再次运行,然后退出app一次再打开:

    结果:

    还有两个Activity。

    第一个问题仍然是observer,但是是在MessageFragment。

    第二个问题也还是InputMethodmanager,但是泄漏点变了。

    修改:

     

     

    内存泄漏解决完成!

        这个app还有很多内存泄漏的地方。大家可以自己尝试去寻找并解决。比如MyInfoActivity。。。。大家自己去解决啊,有什么不懂得再问我。如果问的人多,下节课先解决掉这个泄漏。

     

    除了检查单个hprof文件之外,还能够使用多个hprof进行对比。

    比如我们在进入一个新页面之前先dump下来内存。然后再进入这个页面之后退出,再dump一份内存。通过对比就能够知道,进入这个页面之后增加了多少内存/对象等信息:

     

    之后在mat中打开这个两个文件并都切换到直方图查看。

    再然后把两个直方均加入对比,点击 ,或者

     

    执行对比:

     

    再把视图切换到difference from base table(与第一个的不同)

     

    然后能看到

     

    第二行就是指第二个文件相比第一个文件多出来了几个对象。

    如果存在增加了不合理的对象,同样可以查看其GC root。

     

     

    =====================================================================

     

    Android内存泄露 们还可以使用著名的LeakCanary (Square出品,SquareAndroid开源界中的界良心,开源的目包括okhttp, retrofitotto, picasso, Android大神Jake Wharton曾今就是Square)检测

    https://github.com/square/leakcanary

    这个库也有一些bug,但总体来说还是能起到一定的辅助作用。

     

    总结:

    内存泄漏常见原因:

    1.集合类

    集合类如果仅仅有添加元素的方法,而没有相应的删除机制,导致内存被占用。如果这个集合类是全局性的变量 (比如类中的静态属性,全局性的 map 等即有静态引用或 final 一直指向它),那么没有相应的删除机制,很可能导致集合所占用的内存只增不减。

             一开始的微信工程中使用的ButterKnife中的linkeahashmap就存在这个问题。

    2、静态成员

             Static成员作为gc root,如果一个对象被static声明,这个对象会一直存活直到程序进程停止。

    2.单例模式

    不正确使用单例模式是引起内存泄露的一个常见问题,单例对象在被初始化后将在 JVM 的整个生命周期中存在(以静态变量的方式),如果单例对象持有外部对象的引用,那么这个外部对象将不能被 JVM 正常回收,导致内存泄露。

            

    这里如果传递Activity作为Context来获得单例对象,那么单例持有Activity的引用,导致Activity不能被释放。

    不要直接对 Activity 进行直接引用作为成员变量,如果允许可以使用Application。如果不得不需要Activity作为Context,可以使用弱引用WeakReference,相同的,对于Service 等其他有自己声明周期的对象来说,直接引用都需要谨慎考虑是否会存在内存泄露的可能。

     

    3.未关闭/释放资源

    BraodcastReceiverContentObserverFileObserverCursorCallback等在 Activity onDestroy 或者某类生命周期结束之后一定要 unregister 或者 close 掉,否则这个 Activity 类会被 system 强引用,不会被内存回收。

             我们经常会写出下面的代码

    当然这样写代码没问题,但是如果我们在close之前还有一些可能抛出异常的代码

    那么现在这段代码存在隐患的。因为如果运行到fos2时候抛出了异常,那么fos也没办法close

    所以正确的方式应该是

    因为如果write发生异常那么这个fos会因为没有close造成内存泄漏。 

    4. Handler

    只要 Handler 发送的 Message 尚未被处理,则该 Message 及发送它的 Handler 对象将被线程 MessageQueue 一直持有。特别是handler执行延迟任务。所以,Handler 的使用要尤为小心,否则将很容易导致内存泄露的发生。

    这种创建Handler的方式会造成内存泄漏,由于mHandler是Handler的非静态匿名内部类的实例,所以它持有外部类Activity的引用,我们知道消息队列是在一个Looper线程中不断轮询处理消息,那么当这个Activity退出时消息队列中还有未处理的消息或者正在处理消息,而消息队列中的Message持有mHandler实例的引用,mHandler又持有Activity的引用,所以导致该Activity的内存资源无法及时回收,引发内存泄漏,所以另外一种做法为:

    创建一个静态Handler内部类,然后对Handler持有的对象使用弱引用,这样在回收时也可以回收Handler持有的对象,这样虽然避免了Activity泄漏,不过Looper线程的消息队列中还是可能会有待处理的消息,所以我们在Activity的Destroy时或者Stop时应该移除消息队列中的消息,

    使用mHandler.removeCallbacksAndMessages(null);是移除消息队列中所有消息和所有的Runnable。当然也可以使用mHandler.removeCallbacks();或mHandler.removeMessages();来移除指定的Runnable和Message。

     

    5. Thread 内存泄露

    handler一样,线程也是造成内存泄露的一个重要的源头。线程产生内存泄露的主要原因在于线程生命周期的不可控。比如线程是 Activity 的内部类,则线程对象中保存了 Activity 的一个引用,当线程的 run 函数耗时较长没有结束时,线程对象是不会被销毁的,因此它所引用的老的 Activity 也不会被销毁,因此就出现了内存泄露的问题。

    ThreadHandler都可以划分到为非静态包括匿名内部类的内存泄漏。

     

    6、系统bug,比如InputMethodManager

     

    展开全文
  • 说起内存我们要先了解JAVA虚拟机 JAVA虚拟机 java虚拟机在运行程序时会在内存空间分配一块区域用于程序的运行,虚拟机又会把这一块区域划分为若干个不同的数据块 线程私有:程序计数器、虚拟机栈、本地方法栈; ...

    说起内存我们要先了解JAVA虚拟机

    JAVA虚拟机

    java虚拟机在运行程序时会在内存空间分配一块区域用于程序的运行,虚拟机又会把这一块区域划分为若干个不同的数据块

    线程私有:程序计数器、虚拟机栈、本地方法栈;

    共享数据区:方法区、java堆。

     

     

    程序计数器相当于一个执行代码的指示器,用来确认下一行执行代码的地址,每个线程都有一个,没有OOM的区。

    虚拟机栈:我们平常所说的栈就是指这一块区域,虚拟机规范中定义了OOM和stackoverflow异常。

    本地方法栈:java虚拟机规范中定义了OOM和stackoverflow异常。

    在hotspotVM(Sun JDK虚拟机)把虚拟机栈和本地方法栈合为一个栈区。

    方法区:分配给ClassLoader加载类信息,常量,静态变量,编译后的代码。

    堆区:分配给对象实例和数组,是虚拟机管理的最大的一块内存,是GC的主战场。

    GC垃圾回收器

    GC如何确定内存回收:

    1、引用计数算法

    就是对象在引用一次计数+1,置空时-1,只有计数为0时进行回收,其缺点也很明显,就是互相引用时计数永不为0。

    2、可达性分析算法

    当一个对象到GC Root没有任何引用链相连时(即从GC Roots节点到该节点不可达),则证明该对象是不可用的。

    3、引用类型

    强引用:不会主动被回收。

    软引用(SoftReference):内存不足时回收,存放一些重要性不是很强又不能随便让清除的对象,比如图片切换到后台不需要马上显示了。

    弱引用(WeakReference):第一次扫到了,就标记下来,第二次扫到直接回收。

    虚引用(PhantomReference):幽灵幻影引用   不对生存造成任何影响,用于跟踪GC的回收通知

    内存泄漏

    产生原因:一个长生命周期的对象持有一个短生命周期对象的引用,通俗讲就是该回收的对象,因为引用问题没有被回收,最终会产生OOM。

     

     

    展开全文
  • 写在最前: ...本文的思路主要借鉴了2014年AnDevCon开发者...所以我将本文定义为一个工具类的文章,如果你在ANDROID开发中遇到关于内存问题,或者马上要参加面试,或者就是单纯的学习或复习一下内存相关知识,

    转载自:http://blog.csdn.net/a396901990

    写在最前:

    本文的思路主要借鉴了2014年AnDevCon开发者大会的一个演讲PPT,加上把网上搜集的各种内存零散知识点进行汇总、挑选、简化后整理而成。

    所以我将本文定义为一个工具类的文章,如果你在ANDROID开发中遇到关于内存问题,或者马上要参加面试,或者就是单纯的学习或复习一下内存相关知识,都欢迎阅读。(本文最后我会尽量列出所参考的文章)。


    OOM:


    内存泄露可以引发很多的问题:

    1.程序卡顿,响应速度慢(内存占用高时JVM虚拟机会频繁触发GC)

    2.莫名消失(当你的程序所占内存越大,它在后台的时候就越可能被干掉。反之内存占用越小,在后台存在的时间就越长)

    3.直接崩溃(OutOfMemoryError)


    ANDROID内存面临的问题:

    1.有限的堆内存,原始只有16M

    2.内存大小消耗等根据设备,操作系统等级,屏幕尺寸的不同而不同

    3.程序不能直接控制

    4.支持后台多任务处理(multitasking)

    5.运行在虚拟机之上


    5R:

    本文主要通过如下的5R方法来对ANDROID内存进行优化:


    1.Reckon(计算)

    首先需要知道你的app所消耗内存的情况,知己知彼才能百战不殆

    2.Reduce(减少)

    消耗更少的资源

    3.Reuse(重用)

    当第一次使用完以后,尽量给其他的使用

    5.Recycle(回收)

    回收资源

    4.Review(检查)

    回顾检查你的程序,看看设计或代码有什么不合理的地方。





    内存简介Reckon(计算):


    关于内存简介,和Reckon的内容请看:ANDROID内存优化(大汇总——上)



    Reduce(减少) ,Reuse(重用):


    关于Reduce,和Reuse的内容请看:ANDROID内存优化(大汇总——中)



    Recycle(回收):


    Recycle(回收),回收可以说是在内存使用中最重要的部分。因为内存空间有限,无论你如何优化,如何节省内存总有用完的时候。而回收的意义就在于去清理和释放那些已经闲置,废弃不再使用的内存资源和内存空间。

    因为在Java中有垃圾回收(GC)机制,所以我们平时都不会太关注它,下面就来简单的介绍一下回收机制:



    垃圾回收(GC):


    Java垃圾回收器:

    在C,C++或其他程序设计语言中,资源或内存都必须由程序员自行声明产生和回收,否则其中的资源将消耗,造成资源的浪费甚至崩溃。但手工回收内存往往是一项复杂而艰巨的工作。

    于是,Java技术提供了一个系统级的线程,即垃圾收集器线程(Garbage Collection Thread),来跟踪每一块分配出去的内存空间,当Java 虚拟机(Java Virtual Machine)处于空闲循环时,垃圾收集器线程会自动检查每一快分配出去的内存空间,然后自动回收每一快可以回收的无用的内存块。 


    作用:

    1.清除不用的对象来释放内存:

    采用一种动态存储管理技术,它自动地释放不再被程序引用的对象,按照特定的垃圾收集算法来实现资源自动回收的功能。当一个对象不再被引用的时候,内存回收它占领的空间,以便空间被后来的新对象使用。 

    2.消除堆内存空间的碎片:

    由于创建对象和垃圾收集器释放丢弃对象所占的内存空间,内存会出现碎片。碎片是分配给对象的内存块之间的空闲内存洞。碎片整理将所占用的堆内存移到堆的一端,JVM将整理出的内存分配给新的对象。 


    垃圾回收器优点:

    1.减轻编程的负担,提高效率

    使程序员从手工回收内存空间的繁重工作中解脱了出来,因为在没有垃圾收集机制的时候,可能要花许多时间来解决一个难懂的存储器问题。在用Java语言编程的时候,靠垃圾收集机制可大大缩短时间。

    2.它保护程序的完整性:

    因此垃圾收集是Java语言安全性策略的一个重要部份。 


    垃圾回收器缺点:

    1.占用资源时间:

    Java虚拟机必须追踪运行程序中有用的对象, 而且最终释放没用的对象。这一个过程需要花费处理器的时间。

    2.不可预知:

    垃圾收集器线程虽然是作为低优先级的线程运行,但在系统可用内存量过低的时候,它可能会突发地执行来挽救内存资源。当然其执行与否也是不可预知的。 

    3.不确定性:

    不能保证一个无用的对象一定会被垃圾收集器收集,也不能保证垃圾收集器在一段Java语言代码中一定会执行。

    同样也没有办法预知在一组均符合垃圾收集器收集标准的对象中,哪一个会被首先收集。 

    4.不可操作

    垃圾收集器不可以被强制执行,但程序员可以通过调用System. gc方法来建议执行垃圾收集器。

    垃圾回收算法:
    1.引用计数(Reference Counting) 
    比较古老的回收算法。原理是此对象有一个引用,即增加一个计数,删除一个引用则减少一个计数。垃圾回收时,只用收集计数为0的对象。此算法最致命的是无法处理循环引用的问题。 
    2.标记-清除(Mark-Sweep) 
    此算法执行分两阶段。第一阶段从引用根节点开始标记所有被引用的对象,第二阶段遍历整个堆,把未标记的对象清除。此算法需要暂停整个应用,同时,会产生内存碎片。
    3.复制(Copying) 
    此算法把内存空间划为两个相等的区域,每次只使用其中一个区域。垃圾回收时,遍历当前使用区域,把正在使用中的对象复制到另外一个区域中。次算法每次只处理正在使用中的对象,因此复制成本比较小,同时复制过去以后还能进行相应的内存整理,不过出现“碎片”问题。当然,此算法的缺点也是很明显的,就是需要两倍内存空间。
    4.标记-整理(Mark-Compact) 
    此算法结合了 “标记-清除”和“复制”两个算法的优点。也是分两阶段,第一阶段从根节点开始标记所有被引用对象,第二阶段遍历整个堆,把清除未标记对象并且把存活对象 “压缩”到堆的其中一块,按顺序排放。此算法避免了“标记-清除”的碎片问题,同时也避免了“复制”算法的空间问题。 
    5.增量收集(Incremental Collecting) 
    实施垃圾回收算法,即:在应用进行的同时进行垃圾回收。不知道什么原因JDK5.0中的收集器没有使用这种算法的。 
    6.分代(Generational Collecting) 
    基于对对象生命周期分析后得出的垃圾回收算法。把对象分为年青代、年老代、持久代,对不同生命周期的对象使用不同的算法(上述方式中的一个)进行回收。现在的垃圾回收器(从J2SE1.2开始)都是使用此算法的。 



    finalize():

    每一个对象都有一个finalize方法,这个方法是从Object类继承来的。 

    当垃圾回收确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法


    Java 技术允许使用finalize方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。一旦垃圾回收器准备好释放对象占用的空间,将首先调用其finalize()方法,并且在下一次垃圾回收动作发生时,才会真正回收对象占用的内存。
    简单的说finalize方法是在垃圾收集器删除对象之前对这个对象调用的


    System.gc():

    我们可以调用System.gc方法,建议虚拟机进行垃圾回收工作(注意,是建议,但虚拟机会不会这样干,我们也无法预知!)


    下面来看一个例子来了解finalize()System.gc()的使用:

    [java] view plain copy
     print?
    1. public class TestGC {  
    2.     public TestGC() {}  
    3.       
    4.     //当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。  
    5.     protected void finalize() {  
    6.         System.out.println("我已经被垃圾回收器回收了...");  
    7.     }  
    8.       
    9.     public static void main(String [] args) {  
    10.         TestGC gc = new TestGC();  
    11.         gc = null;    
    12.         // 建议虚拟机进行垃圾回收工作  
    13.         System.gc();  
    14.     }  
    15. }  
    如上面的例子所示,大家可以猜猜重写的finalize方法会不会执行?

    答案是:不一定


    因为无论是设置gc的引用为null还是调用System.gc()方法都只是"建议"垃圾回收器进行垃圾回收,但是最终所有权还在垃圾回收器手中,它会不会进行回收我们无法预知!

    垃圾回收面试题:
    最后通过网上找到的3道面试题来结束垃圾回收的内容。

     

    面试题一: 

    [java] view plain copy
     print?
    1. 1.fobj = new Object ( ) ;   
    2. 2.fobj. Method ( ) ;   
    3. 3.fobj = new Object ( ) ;   
    4. 4.fobj. Method ( ) ;   
    问:这段代码中,第几行的fobj 符合垃圾收集器的收集标准? 
    答:第3行。因为第3行的fobj被赋了新值,产生了一个新的对象,即换了一块新的内存空间,也相当于为第1行中的fobj赋了null值。这种类型的题是最简单的。 

    面试题二: 
    [java] view plain copy
     print?
    1. 1.Object sobj = new Object ( ) ;   
    2. 2.Object sobj = null ;   
    3. 3.Object sobj = new Object ( ) ;   
    4. 4.sobj = new Object ( ) ;   
    问:这段代码中,第几行的内存空间符合垃圾收集器的收集标准? 
    答:第2行和第4行。因为第2行为sobj赋值为null,所以在此第1行的sobj符合垃圾收集器的收集标准。而第4行相当于为sobj赋值为null,所以在此第3行的sobj也符合垃圾收集器的收集标准。 

    如果有一个对象的句柄a,且你把a作为某个构造器的参数,即 new Constructor ( a )的时候,即使你给a赋值为null,a也不符合垃圾收集器的收集标准。直到由上面构造器构造的新对象被赋空值时,a才可以被垃圾收集器收集。 


    面试题三: 
    [java] view plain copy
     print?
    1. 1.Object aobj = new Object ( ) ;   
    2. 2.Object bobj = new Object ( ) ;   
    3. 3.Object cobj = new Object ( ) ;   
    4. 4.aobj = bobj;   
    5. 5.aobj = cobj;   
    6. 6.cobj = null;   
    7. 7.aobj = null;   
    问:这段代码中,第几行的内存空间符合垃圾收集器的收集标准? 
    答:第4,7行。注意这类题型是认证考试中可能遇到的最难题型了。 
    行1-3:分别创建了Object类的三个对象:aobj,bobj,cobj
    行4:此时对象aobj的句柄指向bobj,原来aojb指向的对象已经没有任何引用或变量指向,这时,就符合回收标准。
    行5:此时对象aobj的句柄指向cobj,所以该行的执行不能使aobj符合垃圾收集器的收集标准。 
    行6:此时仍没有任何一个对象符合垃圾收集器的收集标准。 
    行7:对象cobj符合了垃圾收集器的收集标准,因为cobj的句柄指向单一的地址空间。在第6行的时候,cobj已经被赋值为null,但由cobj同时还指向了aobj(第5行),所以此时cobj并不符合垃圾收集器的收集标准。而在第7行,aobj所指向的地址空间也被赋予了空值null,这就说明了,由cobj所指向的地址空间已经被完全地赋予了空值。所以此时cobj最终符合了垃圾收集器的收集标准。 但对于aobj和bobj,仍然无法判断其是否符合收集标准。 

    总之,在Java语言中,判断一块内存空间是否符合垃圾收集器收集的标准只有两个: 
    1.给对象赋予了空值null,以下再没有调用过。 
    2.给对象赋予了新值,既重新分配了内存空间。 

    最后再次提醒一下,一块内存空间符合了垃圾收集器的收集标准,并不意味着这块内存空间就一定会被垃圾收集器收集。



    资源的回收:

    刚才讲了一堆理论的东西,下面来点实际能用上的,资源的回收:


    Thread(线程)回收:

    线程中涉及的任何东西GC都不能回收(Anything reachable by a thread cannot be GC'd ),所以线程很容易造成内存泄露。

    如下面代码所示:

    [java] view plain copy
     print?
    1. Thread t = new Thread() {  
    2.     public void run() {  
    3.         while (true) {  
    4.             try {  
    5.                 Thread.sleep(1000);  
    6.                 System.out.println("thread is running...");  
    7.             } catch (InterruptedException e) {  
    8.               
    9.             }  
    10.         }  
    11.     }  
    12. };  
    13. t.start();  
    14. t = null;  
    15. System.gc();  
    如上在线程t中每间隔一秒输出一段话,然后将线程设置为null并且调用System.gc方法。

    最后的结果是线程并不会被回收,它会一直的运行下去。


    因为运行中的线程是称之为垃圾回收根(GC Roots)对象的一种,不会被垃圾回收。当垃圾回收器判断一个对象是否可达,总是使用垃圾回收根对象作为参考点。


    Cursor(游标)回收:

    Cursor是Android查询数据后得到的一个管理数据集合的类,在使用结束以后。应该保证Cursor占用的内存被及时的释放掉,而不是等待GC来处理。并且Android明显是倾向于编程者手动的将Cursor close掉,因为在源代码中我们发现,如果等到垃圾回收器来回收时,会给用户以错误提示。

    所以我们使用Cursor的方式一般如下:

    [java] view plain copy
     print?
    1. Cursor cursor = null;  
    2. try {  
    3.     cursor = mContext.getContentResolver().query(uri,nullnull,null,null);  
    4.     if(cursor != null) {  
    5.         cursor.moveToFirst();  
    6.         //do something  
    7.     }  
    8. catch (Exception e) {  
    9.     e.printStackTrace();  
    10. finally {  
    11.     if (cursor != null) {  
    12.         cursor.close();  
    13.     }  
    14. }  
    有一种情况下,我们不能直接将Cursor关闭掉,这就是在CursorAdapter中应用的情况,但是注意,CursorAdapter在Acivity结束时并没有自动的将Cursor关闭掉,因此,你需要在onDestroy函数中,手动关闭。
    [java] view plain copy
     print?
    1. @Override    
    2. protected void onDestroy() {          
    3.     if (mAdapter != null && mAdapter.getCurosr() != null) {    
    4.         mAdapter.getCursor().close();    
    5.     }    
    6.     super.onDestroy();     
    7. }    

    Receiver(接收器)回收

    调用registerReceiver()后未调用unregisterReceiver(). 
    当我们Activity中使用了registerReceiver()方法注册了BroadcastReceiver,一定要在Activity的生命周期内调用unregisterReceiver()方法取消注册 
    也就是说registerReceiver()和unregisterReceiver()方法一定要成对出现,通常我们可以重写Activity的onDestory()方法: 
    [java] view plain copy
     print?
    1. @Override    
    2. protected void onDestroy() {    
    3.       this.unregisterReceiver(receiver);    
    4.       super.onDestroy();    
    5. }    

    Stream/File(流/文件)回收:

    主要针对各种流,文件资源等等如:

    InputStream/OutputStream,SQLiteOpenHelper,SQLiteDatabase,Cursor,文件,I/O,Bitmap图片等操作等都应该记得显示关闭。
    和之前介绍的Cursor道理类似,就不多说了。





    Review:


    Review(回顾,检查),大家都知道Code Review的重要性。而这里我说的Review和Code Review差不多,主要目的就是检查代码中存在的不合理和可以改进的地方,当然这个Review需要大家自己来做啦。



    Code Review(代码检查):

    Code Review主要检查代码中存在的一些不合理或可以改进优化的地方,大家可以参考之前写的Reduce,Reuse和Recycle都是侧重讲解这方面的。




    UI Review(视图检查):

    Android对于视图中控件的布局渲染等会消耗很多的资源和内存,所以这部分也是我们需要注意的。


    减少视图层级:
    减少视图层级可以有效的减少内存消耗,因为视图是一个树形结构,每次刷新和渲染都会遍历一次。

    hierarchyviewer:
    想要减少视图层级首先就需要知道视图层级,所以下面介绍一个SDK中自带的一个非常好用的工具hierarchyviewer。
    你可以在下面的地址找到它:your sdk path\sdk\tools


    如上图大家可以看到,hierarchyviewer可以非常清楚的看到当前视图的层级结构,并且可以查看视图的执行效率(视图上的小圆点,绿色表示流畅,黄色和红色次之),所以我们可以很方便的查看哪些view可能会影响我们的性能从而去进一步优化它。


    hierarchyviewer还提供另外一种列表式的查看方式,可以查看详细的屏幕画面,具体到像素级别的问题都可以通过它发现。


    ViewStub标签

    此标签可以使UI在特殊情况下,直观效果类似于设置View的不可见性,但是其更大的意义在于被这个标签所包裹的Views在默认状态下不会占用任何内存空间。


    include标签

    可以通过这个标签直接加载外部的xml到当前结构中,是复用UI资源的常用标签。


    merge标签

    它在优化UI结构时起到很重要的作用。目的是通过删减多余或者额外的层级,从而优化整个Android Layout的结构。


    注意:灵活运用以上3个标签可以有效减少视图层级,具体使用大家可以上网搜搜)


    布局用Java代码比写在XML中快

    一般情况下对于Android程序布局往往使用XML文件来编写,这样可以提高开发效率,但是考虑到代码的安全性以及执行效率,可以通过Java代码执行创建,虽然Android编译过的XML是二进制的,但是加载XML解析器的效率对于资源占用还是比较大的,Java处理效率比XML快得多,但是对于一个复杂界面的编写,可能需要一些套嵌考虑,如果你思维灵活的话,使用Java代码来布局你的Android应用程序是一个更好的方法。



    重用系统资源:

    1. 利用系统定义的id

    比如我们有一个定义ListView的xml文件,一般的,我们会写类似下面的代码片段。

    [html] view plain copy
     print?
    1. <ListView  
    2.     android:id="@+id/mylist"  
    3.     android:layout_width="fill_parent"  
    4.     android:layout_height="fill_parent"/>  

    这里我们定义了一个ListView,定义它的id是"@+id/mylist"。实际上,如果没有特别的需求,就可以利用系统定义的id,类似下面的样子。

    [html] view plain copy
     print?
    1. <ListView  
    2.     android:id="@android:id/list"  
    3.     android:layout_width="fill_parent"  
    4.     android:layout_height="fill_parent"/>  
    在xml文件中引用系统的id,只需要加上“@android:”前缀即可。如果是在Java代码中使用系统资源,和使用自己的资源基本上是一样的。不同的是,需要使用android.R类来使用系统的资源,而不是使用应用程序指定的R类。这里如果要获取ListView可以使用android.R.id.list来获取。

    2. 利用系统的图片资源

    这样做的好处,一个是美工不需要重复的做一份已有的图片了,可以节约不少工时;另一个是能保证我们的应用程序的风格与系统一致。

    3. 利用系统的字符串资源

    如果使用系统的字符串,默认就已经支持多语言环境了。如上述代码,直接使用了@android:string/yes和@android:string/no,在简体中文环境下会显示“确定”和“取消”,在英文环境下会显示“OK”和“Cancel”。

    4. 利用系统的Style

     假设布局文件中有一个TextView,用来显示窗口的标题,使用中等大小字体。可以使用下面的代码片段来定义TextView的Style。

    [html] view plain copy
     print?
    1. <TextView  
    2.         android:id="@+id/title"  
    3.         android:layout_width="wrap_content"  
    4.         android:layout_height="wrap_content"  
    5.         android:textAppearance="?android:attr/textAppearanceMedium" />  
    其中android:textAppearance="?android:attr/textAppearanceMedium"就是使用系统的style。需要注意的是,使用系统的style,需要在想要使用的资源前面加“?android:”作为前缀,而不是“@android:”。

    5. 利用系统的颜色定义

    除了上述的各种系统资源以外,还可以使用系统定义好的颜色。在项目中最常用的,就是透明色的使用。

    [html] view plain copy
     print?
    1. android:background ="@android:color/transparent"  


    除了上面介绍的以外还有很多其他Android系统本身自带的资源,它们在应用中都可以直接使用。具体的,可以进入android-sdk的相应文件夹中去查看。例如:可以进入$android-sdk$\platforms\android-8\data\res,里面的系统资源就一览无余了。

    开发者需要花一些时间去熟悉这些资源,特别是图片资源和各种Style资源,这样在开发过程中,能重用的尽量重用,而且有时候使用系统提供的效果可能会更好。



    其他小tips:

    1. 分辨率适配-ldpi,-mdpi, -hdpi配置不同精度资源,系统会根据设备自适应,包括drawable, layout,style等不同资源。

    2.尽量使用dp(density independent pixel)开发,不用px(pixel)。

    3.多用wrap_content, match_parent

    4.永远不要使用AbsoluteLayout

    5.使用9patch(通过~/tools/draw9patch.bat启动应用程序),png格式

    6.将Acitivity中的Window的背景图设置为空。getWindow().setBackgroundDrawable(null);android的默认背景是不是为空。

    7.View中设置缓存属性.setDrawingCache为true。



    Desgin Review(设计检查):

    Desgin Review主要侧重检查一下程序的设计是否合理,包括框架的设计,界面的设计,逻辑的设计(其实这些东西开发之前就应该想好了)。


    框架设计:

    是否定义了自己的Activity和fragment等常用控件的基类去避免进行重复的工作

    是否有完善的异常处理机制,即使真的出现OOM也不会直接崩溃导致直接退出程序


    界面设计:

    1.在视图中加载你所需要的,而不是你所拥有。因为用户不可能同时看到所有东西。最典型的例子就是ListView中的滑动加载。

    2.如果数据特别大,此时应该暗示用户去点击加载,而不是直接加载。

    3.合理运用分屏,转屏等,它是个双刃剑,因为它即可以使程序更加美观功能更加完善,但也相应增加了资源开销。


    逻辑设计:

    避免子类直接去控制父类中内容,可以使用监听等方式去解决


    关于这三点由于我工作经验比较少,加上一时半会也想不出来多少,如果大家有建议希望可以留言,之后我给加进去。



    写在最后:


    到此ANDROID内存优化上、中、下三篇全部写完了。

    内存简介,Recoken(计算)请看ANDROID内存优化(大汇总——上)

    Reduce(减少),Reuse(重用) 请看:ANDROID内存优化(大汇总——中)

    Recycle(回收), Review(检查) 请看:ANDROID内存优化(大汇总——全)


    最初写这篇文章的原因是因为我拿到一个国外大牛演讲的PPT,我看过之后感觉写的非常好,于是想按照ppt的思路将其总结一下。结果到写的时候发现困难重重,因为内存本来就是很理论的东西,很多都是靠经验的。而我的经验几乎可以忽略,写的东西完全是网上各路文章的大汇总(所以大家千万不要叫我大神,我只是大神的搬运工。。。)


    虽然如此我觉得我总结和搜集的还算比较全面的,当然也有很多遗落也可能有很多错误,这个就希望大家一起帮着完善一下。


    最后我把这个PPT的原件附上,里面很多高级的东西我没看懂(比如那个5R中其实是没有Review的,原文是Reorder,由于这部分我看不懂而且找不到很好的资料只能自己换了一个Review),各路大神有兴趣可以看看,如果可以的话写出来分享一下。


    Putting Your App on a Memory Diet, Parts I and II_Murphy


    最后小唠叨一下,我最近参加了devstore网站的一个小比赛,所以blog先停更一个月,十一之后接着写。

    在这段时间里我正好也可以休息一下想想以后写点什么东西。像内存这种偏理论的东西我还是不要碰了,以后可能会多翻译一些国外大神的文章和自己做的一些小Demo吧。


    不知不觉Blog也写了快半年了,越来越觉得Blog这种分享精神的重要性,因为只有分享才能收获的更多!

    最后要谢谢那些关注,点赞和评论的网友们,这些真的是我能坚持下来的一个巨大动力!



    参考文章:

    Android内存优化http://blog.csdn.net/imain/article/details/8560986
    Android 内存优化http://blog.csdn.net/awangyunke/article/details/20380719
    关于android性能,内存优化http://www.cnblogs.com/zyw-205520/archive/2013/02/17/2914190.htm
    Java垃圾回收原理http://www.360doc.com/content/11/0911/15/18042_147476260.shtml
    JVM垃圾回收(GC)原理http://www.360doc.com/content/11/0911/16/18042_147492404.shtml
    展开全文
  • 本文主要讲解性能优化中的内存优化,希望你们会喜欢 目录 1. 定义 优化处理 应用程序的内存使用、空间占用 2. 作用 避免因不正确使用内存 &amp; 缺乏管理,从而出现 内存泄露(ML)、内存...
  • 一、什么是内存泄露内存不在GC掌控之内了。就是当一个对象已经不需要再使用了,本该被回收时,而又另外一个正在使用的对象持有它的引用从而导致对象不能被回收。这种导致了本该被回收的对象不能被回收而停留在堆内存...
  • Android内存优化

    千次阅读 2018-06-08 17:24:12
    参考Android性能优化(四)之内存优化实战 内存优化的套路: 解决所有的内存泄漏 集成LeakCanary,可以方便的定位出90%的内存泄漏问题; 通过反复进出可疑界面,观察内存增减的情况,Dump Java Heap获取当前堆栈...
  • Redis 云平台 CacheCloud 提供一个Redis云管理平台:实现多种类型(Redis Standalone、Redis Sentinel、Redis Cluster)自动部署、解决Redis实例碎片化现象、提供完善统计、监控、运维功能、减少运维成本和误操作,...
  • 一、优化IDEA配置 IDEA默认启动配置主要考虑低配置用户,参数不高(默认最低128m,最高512m),导致启动慢,然后运行也不流畅,这里我们需要优化下启动和运行配置;但是在工作中的电脑一般都是8G或者16G的运行内存...
  • 你的内存经常不够用吗?吃了就给我吐出来,betaflare今天就介绍6款windows7系统下可用的免费内存释放软件,把应用软件没有释放的不需要内存释放出来,让你的PC跑的更欢。  经常会有一些软件在运行后并不会把内存...
  • 本文全面讲解性能优化中的所有知识,献上一份 Android性能优化的详细攻略, 含:优化方向、原因 &amp; 具体优化方案,希望你们会喜欢 文章较长,建议预留较长时间阅读 / 收藏 目录 1. 性能优化...
  • 一、内存优化原则 为了优化应用内存,你应该知道是什么消耗了你应用的大部分内存,答案就是Texture(纹理)!它几乎占据了90%的应用内存。那么我们应该尽力去减小我们应用的纹理内存使用,否则我们的应用进程...
  • 学习Python的内存释放知识点的动机之前我学过很多Python的程序,偶然的一次,我打开任务管理器,看到我写的程序,运行时占用了大量的内存,所以,我希望学会如何释放内存,来优化我的程序,也不给电脑照成太大的负担...
  • Spring boot内存压榨

    万次阅读 热门讨论 2018-05-31 19:48:16
    基于Java语言、web服务器和开发效率上的考虑,我们选择了Spring boot框架。但是,服务上线后问题来了,本来只有1G内存的容器,...接下来就得优化Spring boot的内存了。 第一个想到的是JVM Xmx Xms的设置问题。...
  • Android应用优化方案一

    万次阅读 2016-11-22 11:22:45
    1. 为什么需要性能优化? 上面说到,在时间窗口期内开发出产品是极端重要的,但是虽然基本功能我们实现了,但是开发出来的产品代码运行的效率怎么样呢?我们的App用户给用户的体验如何呢? 我们的App在低端机上...
  • Intellij IDEA 内存优化

    千次阅读 2017-01-17 15:32:23
    Intellij IDEA 内存优化idea 默认的内存配置会导致idea启动缓慢,运行时很卡,查了很多资料后做了一些优化。 自己的开发机内存8G,需要自定义的同学可以重点关注Xms 和 Xmx 两个参数
  • eclipse 性能调优之内存分配

    万次阅读 2011-03-30 16:34:00
    建议读者打开 eclipse 的 Window -> Preferences -> General,将 Show heap status 选中,可以随时在右下角监视内存的使用情况。
  • Lua性能优化—Lua内存优化

    千次阅读 2017-06-09 14:15:46
    笔者在这里和大家分享了一些在Lua性能优化方面的经验。比如说拿到原始数据后,如何处理过滤数据、信息的经验,从而更快更准确地定位问题。如果大家有更好更精准的处理数据、过滤信息的方法请不吝赐教。
  • 布局优化   我们可以通过手机开发者选项中的调试GPU过度来查看布局绘制的复杂情况。 避免overdraw,使用RelativeLayout来替换掉多层LineraLayout嵌套 减少View树的层数,Google Api文档中建议View树...
  • SQLServer 2014 内存优化

    千次阅读 2016-11-14 11:45:12
    内存优化表是 SQLServer 2014 的新功能,它是可以将表放在内存中,这会明显提升DML性能。 关于内存优化表,更多可参考两位大侠的文章:SQL Server 2014新特性探秘(1)-内存数据库 试试SQLSERVER2014的内存优化表 ...
1 2 3 4 5 ... 20
收藏数 805,917
精华内容 322,366
关键字:

内存优化