精华内容
下载资源
问答
  • 我们在浏览网页时,也许会经常看到一些弹出广告,例如“整理内存碎片、提升系统性能”、或者“大大减少系统和程序崩溃的...看上去,这些工具软件确实不错,但是实际上最好的情况,这些所谓的内存优化工具没有任何效...

    我们在浏览网页时,也许会经常看到一些弹出广告,例如“整理内存碎片、提升系统性能”、或者“大大减少系统和程序崩溃的可能性,回收垃圾内存”等等。如果点击这些弹出广告链接,则会看到某些所谓的优化软件,声称只需花费9.95、14.95或者29.95美元,就可以轻松实现所有的功能。看上去,这些工具软件确实不错,但是实际上最好的情况,这些所谓的内存优化工具没有任何效用;而最差的情况,则可能会严重降低系统性能。

    盆盆评注 在国内,广告语更加精彩,“让您的系统运行如飞”、“让Windows插翅能飞”,充分体现汉语的优越性。

    市面上这些所谓的“内存优化工具”有很多—有些是商业软件,有些则是自由软件。可能我们已经在系统上安装了这些软件。这些软件到底能够起什么作用?它们是怎样试图愚弄我们,让我们误认为它们可以达到所承诺的目的? 那就让我们一起好好分析一下内存优化程序的内部原理,看看它们如何让Windows内存计数器显示出诱人的结果。

    用户界面

    通常,内存优化工具会在用户界面上显示一个图形标签,以表明当前的可用内存,还会显示一个指示条,以表明当前设置的阈值,超过这个阈值,该工具就会开始行动(所谓的优化内存)。还有一个指示条,用来显示优化工具可以释放的内存容量。通常用户可以配置以上两种或者其中一种设置,还可以手动触发内存优化,或者设置计划任务。某些工具还会详细显示执行过程。

    当执行内存优化的计划任务时,通常这些程序的可用内存计数器会上升,有时候还可以显示动态效果,给人的感觉好像是这些工具确实正在释放内存,以供我们的应用程序使用。要理解这些内存优化工具如何让可用内存的数字不断上升,我们必须理解Windows是如何管理物理内存的。

    Windows内存管理

    和大多数现代操作系统一样,Windows实现按需调页的虚拟内存机制。由于操作系统使用了虚拟内存,这就给应用程序造成了一个假象,以为计算机安装的内存远远超过自己所需要的数量。

    在32位的Windows计算机上,进程具有4GB的虚拟内存地址空间,操作系统通常会把这4GB的地址空间划分为进程和系统两个部分。因此,每个进程可以获得2GB的虚拟内存,根据可用的容量。分配给所有进程的虚拟内存总数不能超过页面文件和大多数物理内存的总和(操作系统本身也要占据一小部分物理内存)。

    有了这种机制,加上足够大的页面文件,就可以给进程分配超过物理内存容量的虚拟内存,Windows内存管理子系统必须让多个进程和缓存的文件数据(由缓存管理器管理)共享物理内存。如图1所示,内存管理器给每个进程(例如Windows Explorer、记事本和Word)指派一部分物理内存,这叫做进程的工作集。可分页的内核和驱动程序部分,加上可分页的内核内存缓冲区(叫做分页池),还有缓存管理器所管理的物理内存,它们具有自己的工作集,叫做系统工作集。

    图1
    盆盆译注:可分页表示可以经过调页转移到页面文件上,以便空出可用内存供其他进程使用。但是有些重要部分是不可以进行分页的,例如内存管理器本身就不能分页,还有凭据缓存里的重要凭据信息(例如长期密钥、Kerberos票据等)都不能进行分页。
    工作集就是进程在物理内存中的部分,可以用一个队列(数据结构)来表示。也就是说如果进程访问的页在工作集里,就不会发生页面错误事件。话反过来说,如果进程所需访问的页不在工作集中,就必须进行分页操作。

    内存管理器可以扩展或者缩短系统和进程的工作集,以便让进程可以快速访问其代码和数据。计算机的内存管理硬件设备要求Windows按照“页”的块级别来管理工作集和虚拟内存。在32位的x86处理器上,页通常是4096字节(4KB)。然而操作系统和其他需要大量访问内存的应用程序,也可以使用4MB的“页”,以便优化性能。
    盆盆译注:为什么使用4MB的“页”可以优化性能?通过阅读《Windows Internals》第七章内存管理部分,我们可以了解到,Windows的内存管理采用TLB的硬件数据结构来缓存页表里的数据。如果采用4MB的页,由于总的页数比较少,TLB缓存中的项就不需要经常刷新,这样就不会由于TLB里没有缓存,而被迫到页表里读取地址,以增加性能。
    但是为什么不默认采用4MB,而要采用4KB呢? 《Windows Internals》进一步提到,由于页需要采用保护机制,大的页面不利于设置更细微的保护粒度。例如在4MB的数据中,一部分需要只读访问,另一部分需要读写访问,则整个页只能设置为读写访问。

    如果进程所访问的虚拟内存页不在工作集中,进程就会触发一个页面错误的硬件异常。如果发生这种情况,内存管理器会分配一个可用的物理内存页,以存放最近访问的数据。另外,内存管理器可能会给进程工作集添加新的页,以扩展进程的工作集。然而,如果内存管理器认为进程的工作集已经足够大了,它就会把工作集中的一个页调出去,以便腾出空间存放新的页。替换的策略是选择进程最近最少访问的页,这主要是假设进程在未来访问该页的可能性很小。

    当内存管理器把页从进程的工作集中移走,它必须决定应该怎样处理这个页。如果该页已经修改过了,内存管理器会首先把它放到已修改列表中,这个列表中的页最终会写入到页面文件,或者写入这些页所对应的内存映射文件中。内存管理器会把页从已修改列表移动到备用列表中。未修改的页会直接移动到备用列表中。所以,我们可以把备用列表看成是文件数据的缓存。
    盆盆译注:总结一下,当进程的工作集已满,想要访问新的页,则必须把老的页移走。这些老的页,如果已经修改过,则会移动到已修改列表,如果没有修改过,则会移动到备用列表。
    到现在为止,物理内存的两个列表(数据结构)已经出来了,一个是备用列表,另一个是已修改列表。

    可用内存

    在本文的前面部分,曾经提到发生页面错误时,内存管理器会给进程提供一个可用的物理内存页,但是还没有讨论什么叫做可用内存。物理内存中的备用列表就是内存管理器所认为的一部分可用内存。其他一部分可用内存,它们的页所包含的数据属于已释放的虚拟内存(例如这些页包含已退出进程的数据);还有一部分可用内存,包含刚被释放的页,这些页随后会被内存管理器的低优先级页清零线程用全零来填充。这些类型的页分别保存在内存管理器的自由列表和清零页列表中。
    盆盆译注:物理内存的另外两个列表(数据结构)自由列表和清零页列表。可用内存由三部分(三个数据结构)组成:备用列表、自由列表和清零页列表,正如图1所示的那样。

    图2显示了工作集和这些页列表之间的转化。系统线程每秒钟唤醒一次,调用内存管理器的工作集管理程序,对系统和进程工作集进行分析。如果发现可用内存很低,工作集管理程序就会挑选一些进程,这些进程在过去的一秒钟里页面错误发生率最低,然后从它们的工作集中移走一些页。这些移走的页会转移到已修改列表或者备用列表(还记得盆盆前面的注释吧),这样就可以腾出可用内存了(盆盆译注:其实只有备用列表里的部分,才算是可用内存)。这种协调机制的其中一个重要的副作用是:如果系统需要为其他进程分配内存,内存管理器会从空闲进程的工作集中抽取内存页。所以,空闲进程的工作集会最终消失,这意味着,如果进程长时间处于空闲状态,那么最终它们将不会占据任何物理内存。

    图2 盆盆汉化 版权所有

    如果进程需要请求新的物理内存页,内存管理器首先会查看该页是不是位于备用列表或者已修改页列表。如果该页曾经从进程工作集中移走,并且没有因为其他目的而被其他进程重新使用,那么该页应该还在这两个列表里(盆盆译注:就是指备用列表或者已修改列表)。把内存页重新拿回,并放到进程的工作集中,这叫做软页面错误,因为和这硬页面错误不一样,软页面错误不需要从硬盘的页面文件或者其他文件中读入数据。
    如果所需请求的内存页并不在备用列表或者已修改列表中,内存管理器就会从某个列表上摘取一页,首先会检查自由列表,然后是清零页列表,最后是备用列表(盆盆译注:这也再次说明了,可用内存由这三部分组成)。如果可用内存不够,内存管理器会触发平衡集管理器,来调整进程的工作集,以便生成上述的三个列表,从而获得可用内存。如果内存管理器不得不从自由列表、备用列表或者清零页列表中移除内存页,以便重新使用,它会确定如何访问目标代码或者数据,这会导致从页面文件或者进程映像文件中读取数据,或者创建由零填充的数据—如果应用程序需要分配新的数据,而且所获得的内存页并不是取自清零页列表(盆盆译注:如果是从清零页列表中获取的页,则无需再由零填充,因为本来就已经清零了)。

    创建可用内存

    理解了内存管理器的行为,现在我们可以把目光转向内存优化工具的行为。内存优化工具所显示的可用内存数值,等同于任务管理器的“性能”标签页上所显示的“可用数”,如图3所示。可用内存的数值是备用列表、自由列表和清零页列表这三个数值的相加。而系统缓存,则是备用列表和系统工作集这两个数值的相加。在Windows NT 4.0和以前的版本,文件缓存则只等于系统工作集的大小。

    图3 盆盆制作 版权所有
    盆盆译注:这部分的内容很好地解释了任务管理器“性能”标签页里的术语,什么叫做可用内存,什么叫做系统缓存。

    内存管理器可以分配、然后释放一大块虚拟内存,内存优化工具正是利用了内存管理器的这一特点。内存优化工具的作用如图4所示。最上方的条状图显示优化之前的工作集和可用内存。中间的条状图显示内存优化工具(RAM optimizer)发出大内存的请求,在极短的时间里产生大量的页面错误。作为回应,内存管理器会增加内存优化工具的工作集。这种工作集的扩展是以牺牲可用内存作为代价—这时候可用内存会变得很低—同时以减少其他进程的工作集作为代价。最下方的条状图显示,内存优化工具会释放内存,内存管理器会把所有的内存页从它的工作集中转移到自由列表中,所以可用内存会大大增加。绝大多数的内存优化工具隐藏了第一阶段中可用内存急剧下降的事实,但是如果我们在优化的时候启动任务管理器,就可以看到可用内存在最初会急剧下降。

    图4

    尽管从表面上来看,可用内存越多越好,其实不然。内存优化工具虽然使得可用内存数大大增加,但是它们会强迫其他进程的数据和代码交换出内存。假设我们正在运行Word,当内存优化工具强制增加可用内存时,Word的打开文档和程序代码原本是在内存中(工作集),结果不得不从磁盘中重新读入,我们才能继续编辑文档。在服务器上,这种性能的损失将会变得非常严重,这是因为缓存在备用列表和系统工作集中的文件数据(还有活动服务端进程所拥有的代码和数据)很可能会丢失。
    盆盆译注:所谓的内存优化工具,是以牺牲其他进程的性能作为代价,同时还会导致系统重要进程的代码和数据交换到磁盘,对于总体性能来说,得不偿失。Windows Vista的内存管理,并没有强调可用内存足够多,而是确保内存里包含正确的内容,这样才能尽可能地减少磁盘I/O,提升整体的用户体验。

    其他的宣传功效

    一些厂商宣传他们的内存优化产品还具有其他一些功能。例如该优化工具可以把一些无用进程所占据的内存释放出来,例如运行在任务栏通知区域上的进程。这些所谓的功效都是不正确的,因为Windows已经自动调整了空闲进程的工作集。内存管理器已经具有所有必需的内存优化功能。
    盆盆译注:盆盆在Windows Vista做实验,证实了Mark Russinovich的说法。以Daemon Tool为例,平时空闲的时候,其工作集只有3MB左右(专用工作集只有350KB),而当打开其配置界面时(使其不处于空闲状态时),其工作集达到10MB左右(专用工作集也达到约3MB)。如图5所示,绿色部分代表空闲时的工作集,而紫色部分代表活动时(Active)的工作集大小。另外要注意,Windows Vista中文版的翻译有误,Working Set,不能翻译为工作设置,而应该是工作集。

    图5 盆盆制作 版权所有

    内存优化工具的开发者抛出的另外一个谬论,就是他们的产品可以对内存碎片进行整理。 分配一大块虚拟内存,然后再释放,也许会生成一大片物理连续的可用内存。然而,由于虚拟内存对进程屏蔽了物理内存的布局,进程不能直接从虚拟内存(由连续的物理内存所提供)中得益。当进程运行时,由于工作集的调整和扩展,虽然有一大片连续的可用内存,但是进程的虚拟内存到物理内存的映射,还是会产生很多碎片。
    获得连续的可用内存,对以下情况是有利的:
    当内存管理器希望能够最大化CPU高速缓存的效率时,会采用一种叫做“页面染色”(Page Coloring)的机制,以确定从自由列表(或清零页列表)中把哪些内存页分配给进程。
    尽管如此,虽然获得连续的可用内存,可以获得一点好处,但是这种好处要以把有用的代码和数据移出内存作为代价,是得不偿失的。
    盆盆译注:为了进一步了解什么叫做“页面染色”机制,盆盆费心在网上搜索不少资料,终于了解到以下的背景资料(经过缩写):
    为了确保在进程上下文切换时,CPU高速缓存里的内容不受影响,现代CPU的L1 Cache会对物理内存页进行缓存,而不是缓存虚拟内存页(否则的话,上下文切换时,必须刷新缓存中的内容,导致缓存性能急剧下降)。这就要求相邻的虚拟内存页最好分配相邻的物理内存页,以获得最好的L1 Cache性能。
    更具体的内容,可以参考这篇文档。

    最后,软件开发商宣传他们的内存优化工具可以重新获取因泄漏而丢失的内存。这种宣传可能是最虚假的说法。

    在所有时刻,内存管理器都知道进程所拥有的物理内存和虚拟内存。然而,如果进程分配内存,但是由于Bug而无法释放内存(内存泄漏),内存管理器就可能无法了解这些已分配的内存无法重新访问,而必须等到进程退出时回收内存。

    哪怕产生内存泄漏的进程无法退出,内存管理器的工作集调整机制也会最终从进程的工作集中“盗取”所有的物理页(分配给泄漏的虚拟内存)。该进程会把泄漏的虚拟页发送到页面文件,这样系统就可以腾出物理内存挪作它用。所以对可用物理内存来说,内存泄漏只有有限的影响。真正的影响在于虚拟内存的消耗(在任务管理器中叫做PF使用率和提交更改,如图3所示)。没有任何工具可以解决这种虚拟内存的消耗,除非杀死消耗内存的进程。

    欺诈软件

    笔者(Mark Russinovich)还在寻找能够实践其承诺的内存优化工具。如果仔细查看,我们会经常发现开发商会在其网站上隐藏着冗长的免责声明,其中包括本文所述的内容—该产品可能对系统性能没有什么影响,甚至可能导致性能下降。甚至无需知道这些产品是如何借助内存管理器来实现其具有明显视觉效果、具有煽情名字的内存管理机制,只要用正常思维就可以明白如果这种内存优化机制是可行的话(而且那么多三流厂商都可以实现),微软的开发工程师早就应该在内核里实现这种功能。
    盆盆译注:说实话,国外的厂商还算是讲点道义,毕竟还提供一些免责声明。看看国内的情况,骗子太多了,例如让人哭笑不得的“智慧星”,还有无孔不入的抄袭者,不提也罢~~

    展开全文
  • 但作为一名软件工程师,虚拟内存管理将直接影响你开发程序的性能! 灵魂拷问作为一名程序员,小编认为以下关于虚拟内存的知识需要搞懂:1)什么是虚拟内存? 核心观点是什么?2)页表机制和页面置换算法的基本原理?...

    开门见山

    虚拟内存的初衷是采用页表管理机制使得程序即使在物理内存不足时也可以运行。在内存大幅度增加的当下,你可能会问虚拟内存还有用吗?如果你只是普通用户,买根内存条完事!但作为一名软件工程师,虚拟内存管理将直接影响你开发程序的性能!

    灵魂拷问

    作为一名程序员,小编认为以下关于虚拟内存的知识需要搞懂:
    1)什么是虚拟内存? 核心观点是什么?
    2)页表机制和页面置换算法的基本原理?为什么需要多级页表?
    3)哪些虚拟内存的关键点可能影响程序性能?
    4)应用程序开发应该从哪些方面减少缺页异常和 TLB 不命中带来的性能影响?
    5)动态内存申请和释放和虚拟内存的关系?
    6)操作系统如何管理每个进程的虚拟内存?切换进程有什么影响?

    一鸣惊人

    虚拟内存是大多数操作系统(例如 MAC OS,Windows 和 Linux)的组成部分。要了解虚拟内存的工作方式,我们必须追溯到虚拟内存出道之前。在Windows版本1或2的日子里,如果没有安装足够的物理RAM,我们实际上无法运行许多应用程序。众所周知,系统本身正在使用一部分RAM。如果我们运行更多的应用程序,则每个应用程序还将获得其自己的RAM部分。如果我们运行太多的应用程序,那么某一时刻将耗尽RAM。届时,我们将无法打开任何其他应用程序。那时,我们不得不忍受这一点。如果没有足够的内存,则无法运行应用程序。虚拟内存的出现打破了这一僵局。它利用程序的局部性原理使得物理内存充当缓存的角色,如果出现物理内存不够时,它会将一部分不常用的内容写回磁盘上的交换区或文件,进而让当前运行的程序可以继续使用。

    页表机制

    在虚拟内存管理下,每个进程直接访问的是一块相互独立的连续虚拟内存,而实际的数据和程序存储在物理内存之中,这也意味着它需要建立一种从虚拟内存到物理内存的映射关系。虚拟内存和物理内存都采用页机制管理。虚拟内存的逻辑页和物理内存的页帧具有相同的大小。Linux 用户可以采用如下命令查看页面大小,页面大小是一个可配参数,当前大小为4K.

    getconf PAGE_SIZE
    4096
    

    当进程需要访问物理页帧中的具体内容时,它会利用页表机制将虚拟地址映射到物理地址。这也意味着页表记录的实际是从逻辑地址到物理地址的映射。

    页表和缺页

    虚拟地址和物理地址的格式如下:
    在这里插入图片描述
    在这里插入图片描述

    如果页大小为4K,则0-11表示页内偏移,12-31位表示页号。在实际访问RAM时我们需要访问页表将逻辑地址映射到物理地址。页表存储的是物理页号和标志位。其中物理页号表示物理页的偏移地址。如下图所示:在这里插入图片描述

    物理地址的查找逻辑如下:
    1)根据逻辑页号找到页表的偏移地址。
    2)如果标志位为1,则表示找到物理页号对应的物理偏移地址,转入第 3)步。否则产生缺页,进入缺页处理程序:如果此时页表项存在磁盘地址,则需要分配一个物理页并将内容从磁盘重新拷贝进物理内存,建立起映射关系(此时成为major fault,耗时较高);如果页表项为空,则直接分配物理页并建立映射关系(此时成为 minor fault)。此外,如果分配物理内存时,没有空闲页也将启动页面置换算法。待缺页处理完毕,继续转入第 3)步。
    3)物理地址=页偏移地址+页内偏移地址。
    至此,我们已经借助一级页表完整讲述了地址映射和缺页。值得一提的是,虚拟内存管理中,每一个进程都有一个独立的虚拟内存空间,每一个进程的页表项都常驻内存。在上面的一级页表中,每一个逻辑页号都需要在页表中有一个映射条目。对于一个32位地址空间,4KB的页面,每个页表项占用4个字节来说,每一个进程的应用程序将占用 4MB 的存储空间。如果是64位系统,这个问题将更加严重。现在有两个问题需要解决:
    1)页表常驻内存,每次访问页表时都需要访问内存,这意味着当应用程序需要访问内存的实际数据时,即使是一级页表也产生了两次内存访问,这引入的性能副作用无疑是不可接受的!(需要多级页表)
    2)每个进程一级页表占用了很多额外的存储空间,浪费了内存,这无疑也是不可接受的!(需要MMU,引入TLB)要解决以上两个问题,请继续关注"程序员的大厂之路"微信公众号下一篇关于多级页表和 MMU (TLB)的分享。

    展开全文
  • 内存优化

    2016-12-03 00:28:19
    Random Access Memory(RAM)在任何软件开发环境中都是一个很宝贵的资源。 尽管 Android 的 Dalvik 虚拟机扮演了常规的垃圾回收角色...这里总结一些内存管理的知识,以及在开发 Android 应用时如何主动减少内存的使用。

    Android开发进阶(从小工到专家)读书笔记——内存优化


    开发的APP:MyApp

    Random Access Memory(RAM)在任何软件开发环境中都是一个很宝贵的资源。 尽管 Android 的 Dalvik 虚拟机扮演了常规的垃圾回收角色,但并不意味着可以忽视 App 的内存分配与释放的时机与地点。这里总结一些内存管理的知识,以及在开发 Android 应用时如何主动减少内存的使用。

    1. 珍惜 Services 资源

    • Service 在非触发执行任务阶段,都应该是非运行状态。
    • Service 在完成任务后可能会因为停止 Service 失败而引起泄露。
    • 启动一个 Service,系统会倾向为了保留 Service 而一直保留 Service 所在进程,这使得进程的运行代价很高,因为系统没办法把 Service 所占用的 RAM 让给其他组件或者被 paged out。

    解决途径
      1. 使用 IntentService,它会在处理完扔给它的 intent 任务之后尽快结束自己。
      2. 在 Service 不需要的时候结束他。


    2. 当UI隐藏是释放内存

    • 当切换到其他应用并且MyApp的UI不可见时,应释放UI上所占用的所有资源,可显著增加系统缓存进程的能力。
      • 为了接收用户离开MyApp的UI通知,需实现 Activity 类里面的 onTrimMemory() 回调方法。
        • 监听 TRIM_MEMORY_UI_HIDDEN 级别的的回调,此时意味着MyApp的UI已经隐藏,应该释放那些被MyApp使用的资源。

      注意:只有在MyApp所有UI组件被隐藏是才会收到 onTrimMemory() 的回调并带有参数 TRIM_MEMORY_UI_HIDDEN。与 onStop() 的回调不同。onStop 会在 Activity 的实例隐藏时执行,如MyApp的某一个 Activity 跳转到另一个 Activity 时,onStop() 会被执行,因此,应该实现 onStop 回调,并且在此回调里面释放 Activity 的资源,例如,网络连接,unregister 广播接受者;除非接收到 onTrimMemory(TRIM_MEMORY_UI_HIDDEN) 的回调,否则不应该释放MyApp的UI资源。


    3. 当内存紧张是释放部分内存

      MyApp生命周期的任何阶段,onTrimMemory 回调方法可以获取整个设备的内存资源,可根据 onTrimMemory 方法中的内存级别来进一步决定释放哪些资源
    - TRIM_MEMORY_RUNNING_MODERATE: MyApp正在运行并且不会被列为可杀死。但是,设备此时正运行于低内存状态下,系统开始出发杀死LRU Cache中的Process机制。
    - TRIM_MEMORY_RUNNING_LOW: MyApp正在运行并且不会被列为可杀死的。但是,设备正运行于更低内存的状态下,应该释放不用的资源来提升系统性能。
    - TRIM_MEMORY_RUNNING_CRITICAL: MyApp仍在运行,但是系统已经把 LRU Cache 中的大多数进程都杀死了,因此,应该立即释放所有非必须的资源。如果系统不能回收到足够的RAM数量,系统将会清除所有的 LRU 缓存中的资源,并且开始杀死那些之前被认为不应该杀死的进程。

      在MyApp进程被 cached 时,可能会接受到从 onTrimMemory() 中返回的下面的值之一:
    - TRIM_MEMORY_BACKGROUND: 系统正运行于低内存状态并且MyApp的进程正处于LRU缓存中最不容易杀掉的位置。此时系统可能已经开始杀掉 LRU Cache 中的其他进程了,应该释放那些容易恢复的资源,以便MyApp的进程可以保留下来。
    - TRIM_MEMORY_MODERATE: 系统正运行于低内存状态并且MyApp的进程已经接近 LRU Cache 名单的中部位置,如果系统开始变得更加内存紧张,MyApp进程是有可能被杀死的。
    - TRIM_MEMORY_COMPLETE: 系统正运行与低内存的状态并且MyApp进程正处于 LRU Cache 名单中最容易被杀掉的位置,应该释放任何不影响MyApp恢复状态的资源。

      onTrimMemory() 的回调是在API 14 才被加进来的,对于低版本,可以使用 onLowMemory 回调来兼容,相当于 TRIM_MEMORY_COMPLETE
    - Note: 当系统开始清除 LRU Cache 中的进程是,尽管他首先按照LRU的顺序来操作,但同样会考虑进程的内存使用量,消耗越低的进程越容易被留下来。


    4. 检查应该使用多少内存

    • 可通过 getMemoryClass() 来获取MyApp的可用 heap 大小,如果MyApp尝试申请更多的内存,会出现 OutOfMemory 的错误。
    • 可通过在 manifest 的 application 标签下添加 largeHeap=true 的属性来声明一个更大的空间,此时可通过 getLargeMemoryClass() 来获取到一个更大的 heap size。
    • 不要轻易地因为MyApp需要使用大量的内存而去请求一个大的 heap size,使用额外的内存会影响系统整体的用户体验,并且会使GC的每次运行时间更长,在任务切换时,系统的性能会变得大打则扣。large heap 并不一定能够取得更大的heap,在某些严格限制的机器上,large heap 的大小通常跟 heap size 的大小一样,此时,可以通过执行 getMemoryClass() 来检查实际获取到的 heap 大小。

    5. 避免 Bitmaps 的浪费

    • 当加载一个 bitmap 时,仅保留适配当前屏幕分辨率的数据即可,若原图高于设备分辨率,需要做缩小动作。增加 bitmap 的尺寸会对内存呈现出 2 次方的增加。(X 与 Y 都在增加)
    • 如果MyApp需要使用图片加载功能,使用成熟的 ImageLoader 框架。如 Glide、Picasso、Fresco 等。

    6. 使用优化的数据容器

    • 利用 Android Framework 中优化过的容器类,如 SparseArray、SparseBooleanArray、LongSparseArray。通常的 HaspMap 实现方式需要一个额外的实例对象来记录 Mapping 操作,更加消耗内存,而 SparseArray 避免了对 key 与 value 的 autobox 自动封装,避免了装箱后的解箱。

    7. 注意内存开销

    • 对所使用的语言与库的成本与开销有所了解,在设计App的过程中谨记这些信息,如:
      • Enums 的内存消耗通常是 static constants 的 2 倍,应尽量避免在 Android 上使用 enums
      • Java 中的每一个类(包括匿名内部类)都会使用大概 500bytes
      • 每一个类的实例产生的花销是 12 - 16 bytes
      • 往 HashMap 添加一个 entry 需要一个额外占用的 32 bytes 的 entry 对象。

    8. 注意代码“抽象”

    • 抽象能够提升代码的灵活性与可维护性,但抽象也会导致一个显著的开销:通常他们需要同等量的代码用于可执行,那些代码会被 map 到内存中。因此,如果抽象没有显著的提升效率,应尽量避免。

    9. 为序列化的数据使用 nano protobufs

    • Protocal buffers 是 Google 为序列化结构数据而设计的,一种语言无关,平台无关,具有良好扩展性的协议。类似 xml,却比 xml 更加轻量、快速、简单。若为数据实现协议化,应在客户端的代码中总是使用 nano protobufs。协议化操作会生成大量繁琐的代码,容易给APP带来许多问题:增加RAM的使用量,显著增加 APK 的大小,更慢的执行速度,更容易达到DEX的字符限制。

    10. 避免使用依赖注入框架

    • 依赖注入框架会通过扫描APP的代码执行许多初始化操作,这会导致代码需要大量的 RAM 来 map 代码,mapped pages 会长时间的被保留在 RAM 中。

    11. 谨慎使用外部库

    • 很多 library 的代码都不是为了移动开发环境而编写的,运用到移动开发是会导致影响APP效率。
    • 每一个 library 所做的事情都是不一样的,不同的 lib 有不同的实现方式,这样的冲突可能会发生在输出日志、加载图片、缓存等模块里面。
    • 不要陷入为了少数的功能而导入整个 library 的陷阱。

    12. 优化整体代码

    • 参考 Google 官方文档:Best Practices for Performance,阅读 optimizing your UI 来为 layout 进行优化。

    13. 使用 ProGuard 剔除不需要的代码


    14. 对最终的APK使用 zipalign

    • 如果不做这个步骤,会导致 MyApp 需要更多的 RAM,因为一些类似图片资源的东西不能被 mapping。注意:Google Play 不接受没经过 zipalign 的 APK

    15. 使用多进程

    • 通过把App的组件切分成多个组件,运行在不同的进程中。适用情况:当App需要在后台运行与前台一样的大量的任务的时候。
    • 如:可长时间后台播放的 Music Player,这样的 App 可切分成 2 个进程:一个操作 UI,另一个用来后台的 Service。
    • 可通过在 manifest 文件中声明 android:progress属性来实现某个组件运行在另一个进程的操作:
    <service android:name=".PlaybackService" android:process="backgroung" />

    展开全文
  • Random Access Memory(RAM)在任何软件开发环境中都是一个很宝贵的资源。这一点在物理内存通常很有限的移动操作系统上,显得尤为突出。尽管Android的Dalvik虚拟机扮演了常规的垃圾...一、Android系统是如何管理内存
        Random Access Memory(RAM)在任何软件开发环境中都是一个很宝贵的资源。这一点在物理内存通常很有限的移动操作系统上,显得尤为突出。尽管Android的Dalvik虚拟机扮演了常规的垃圾回收的角色,但这并不意味着你可以忽视app的内存分配与释放的时机与地点。于大多数apps来说,Dalvik的GC会自动把离开活动线程的对象进行回收。

    一、Android系统是如何管理内存的
        Android并没有提供内存的交换区(Swap space),但是它有使用paging(内存分页)memory-mapping(mmapping)的机制来管理内存。这意味着任何你修改的内存(无论是通过分配新的对象还是访问到mmaped pages的内容)都会贮存在RAM中,而且不能被paged out。因此唯一完整释放内存的方法是释放那些你可能hold住的对象的引用,这样使得它能够被GC回收。只有一种例外是:如果系统想要在其他地方reuse这个对象。

    1> 内存共享
       Android通过下面几个方式在不同的Process中来共享RAM:
       1.每一个app的process都是从同一个被叫做Zygote的进程中fork出来的。Zygote进程在系统启动并且载入通用的framework的代码与资源之后开始启动。为了启动一个新的程序进程,系统会fork Zygote进程生成一个新的process,然后在新的process中加载并运行app的代码。这使得大多数的RAM pages被用来分配给framework的代码与资源,并在应用的所有进程中进行共享。
       2.大多数static的数据被mmapped到一个进程中。这不仅仅使得同样的数据能够在进程间进行共享,而且使得它能够在需要的时候被paged out。例如下面几种static的数据:
         ① Dalvik code (by placing it in a pre-linked .odex file for direct mmapping
         ② App resources (by designing the resource table to be a structure that can be mmapped and by aligning the zip entries of the APK)
         ③ Traditional project elements like native code in .so files.
       3.在许多地方,Android通过显式的分配共享内存区域(例如ashmem或者gralloc)来实现一些动态RAM区域的能够在不同进程间的共享。例如,window surfaces在app与screen compositor之间使用共享的内存,cursor buffers在content provider与client之间使用共享的内存。

    2> 分配与回收内存
       这里有下面几点关于Android如何分配与回收内存的事实:
       1.每一个进程的Dalvik heap都有一个限制的虚拟内存范围。这就是逻辑上讲的heap size,它可以随着需要进行增长,但是会有一个系统为它所定义的上限。
       2.逻辑上讲的heap size和实际物理上使用的内存数量是不等的,Android会计算一个叫做Proportional Set Size(PSS)的值,它记录了那些和其他进程进行共享的内存大小。(假设共享内存大小是10M,一共有20个Process在共享使用,根据权重,可能认为其中有0.3M才能真正算是你的进程所使用的)
       3.Dalvik heap与逻辑上的heap size不吻合,这意味着Android并不会去做heap中的碎片整理用来关闭空闲区域。Android仅仅会在heap的尾端出现不使用的空间时才会做收缩逻辑heap size大小的动作。但是这并不是意味着被heap所使用的物理内存大小不能被收缩。在垃圾回收之后,Dalvik会遍历heap并找出不使用的pages,然后使用madvise把那些pages返回给kernal。因此,成对的allocations与deallocations大块的数据可以使得物理内存能够被正常的回收。然而,回收碎片化的内存则会使得效率低下很多,因为那些碎片化的分配页面也许会被其他地方所共享到。

    3> 限制应用的内存
       为了维持多任务的功能环境,Android为每一个app都设置了一个硬性的heap size限制。准确的heap size限制随着不同设备的不同RAM大小而各有差异。如果你的app已经到了heap的限制大小并且再尝试分配内存的话,会引起OutOfMemoryError的错误。

    在一些情况下,你也许想要查询当前设备的heap size限制大小是多少,然后决定cache的大小。可以通过getMemoryClass()来查询。这个方法会返回一个整数,表明你的app heap size限制是多少megabates。

    4> 切换应用
       Android并不会在用户切换不同应用时候做交换内存的操作。Android会把那些不包含foreground组件的进程放到LRU cache中。例如,当用户刚开始启动了一个应用,这个时候为它创建了一个进程,但是当用户离开这个应用,这个进程并没有离开。系统会把这个进程放到cache中,如果用户后来回到这个应用,这个进程能够被resued,从而实现app的快速切换。

    如果你的应用有一个当前并不需要使用到的被缓存的进程,它被保留在内存中,这会对系统的整个性能有影响。因此当系统开始进入低内存状态时,它会由系统根据LRU的规则与其他因素选择杀掉某些进程。

    二、该如何管理/优化你的应用内存
       你应该在开发过程的每一个阶段都考虑到RAM的有限性,甚至包括在开发开始之前的设计阶段。有许多种设计与实现方式,他们有着不同的效率,尽管是对同样一种技术的不断组合与演变。

    为了使得你的应用效率更高,你应该在设计与实现代码时,遵循下面的技术要点:
    1、珍惜Services资源
       如果你的app需要在后台使用service,除非它被触发执行一个任务,否则其他时候都应该是非运行状态。同样需要注意当这个service已经完成任务后停止service失败引起的泄漏。

    当你启动一个service,系统会倾向为了这个Service而一直保留它的Process。这使得process的运行代价很高,因为系统没有办法把Service所占用的RAM让给其他组件或者被Paged out。这减少了系统能够存放到LRU缓存当中的process数量,它会影响app之间的切换效率。它甚至会导致系统内存使用不稳定,从而无法继续Hold住 所有目前正在运行的Service。

    限制你的service的最好办法是使用IntentService, 它会在处理完扔给它的intent任务之后尽快结束自己

    2、当你的应用UI隐藏时 释放内存 
       当用户切换到其它app并且你的app UI不再可见时,你应该释放应用视图所占的资源。在这个时候释放UI资源可以显著的增加系统cached process的能力,它会对用户的质量体验有着直接的影响。

    为了能够接收到用户离开你的UI时的通知,你需要实现Activtiy类里面的onTrimMemory()回调方法。你应该使用这个方法来监听到TRIM_MEMORY_UI_HIDDEN级别, 它意味着你的UI已经隐藏,你应该释放那些仅仅被你的UI使用的资源。

    请注意:你的app仅仅会在所有UI组件的被隐藏的时候接收到onTrimMemory()的回调并带有参数TRIM_MEMORY_UI_HIDDEN。这与onStop()的回调是不同的,onStop会在activity的实例隐藏时会执行,例如当用户从你的app的某个activity跳转到另外一个activity时onStop会被执行。因此你应该实现onStop回调,所以说你虽然实现了onStop()去释放 activity 的资源例如网络连接或者未注册的广播接收者, 但是应该直到你收到 onTrimMemory(TRIM_MEMORY_UI_HIDDEN)才去释放视图资源否则不应该释放视图所占用的资源。这确保了用户从其他activity切回来时,你的UI资源仍然可用,并且可以迅速恢复activity。

    3、当内存紧张时 释放部分内存 
       在你的app生命周期的任何阶段,onTrimMemory回调方法同样可以告诉你整个设备的内存资源已经开始紧张。你应该根据onTrimMemory方法中的内存级别来进一步决定释放哪些资源。

       ① TRIM_MEMORY_RUNNING_MODERATE:你的app正在运行并且不会被列为可杀死的。但是设备正运行于低内存状态下,系统开始激活杀死LRU Cache中的Process的机制。
       ② TRIM_MEMORY_RUNNING_LOW:你的app正在运行且没有被列为可杀死的。但是设备正运行于更低内存的状态下,你应该释放不用的资源用来提升系统性能,这会直接影响了你的app的性能。
       ③ TRIM_MEMORY_RUNNING_CRITICAL:你的app仍在运行,但是系统已经把LRU Cache中的大多数进程都已经杀死,因此你应该立即释放所有非必须的资源。如果系统不能回收到足够的RAM数量,系统将会清除所有的LRU缓存中的进程,并且开始杀死那些之前被认为不应该杀死的进程,例如那个进程包含了一个运行中的Service。

    同样,当你的app进程正在被cached时,你可能会接受到从onTrimMemory()中返回的下面的值之一:
       ① TRIM_MEMORY_BACKGROUND: 系统正运行于低内存状态并且你的进程正处于LRU缓存名单中最不容易杀掉的位置。尽管你的app进程并不是处于被杀掉的高危险状态,系统可能已经开始杀掉LRU缓存中的其他进程了。你应该释放那些容易恢复的资源,以便于你的进程可以保留下来,这样当用户回退到你的app的时候才能够迅速恢复。
       ② TRIM_MEMORY_MODERATE: 系统正运行于低内存状态并且你的进程已经已经接近LRU名单的中部位置。如果系统开始变得更加内存紧张,你的进程是有可能被杀死的。
       ③ TRIM_MEMORY_COMPLETE: 系统正运行与低内存的状态并且你的进程正处于LRU名单中最容易被杀掉的位置。你应该释放任何不影响你的app恢复状态的资源。

    因为onTrimMemory()的回调是在API 14才被加进来的,对于老的版本,你可以使用onLowMemory)回调来进行兼容。onLowMemory相当与TRIM_MEMORY_COMPLETE。

    Note: 当系统开始清除LRU缓存中的进程时,尽管它首先按照LRU的顺序来操作,但是它同样会考虑进程的内存使用量。因此消耗越少的进程则越容易被留下来。

    4、检查你应该使用多少内存
       通过调用getMemoryClass()来获取你的app的可用heap大小。
       在一些特殊的情景下,你可以通过在manifest的application标签下添加largeHeap=true的属性来声明一个更大的heap空间。如果你这样做,你可以通过getLargeMemoryClass())来获取到一个更大的heap size。

    然而,能够获取更大heap的设计本意是为了一小部分会消耗大量RAM的应用(例如一个大图片的编辑应用)。不要轻易的因为你需要使用大量的内存而去请求一个大的heap size。只有当你清楚的知道哪里会使用大量的内存并且为什么这些内存必须被保留时才去使用large heap. 因此请尽量少使用large heap。使用额外的内存会影响系统整体的用户体验,并且会使得GC的每次运行时间更长。在任务切换时,系统的性能会变得大打折扣。

    5、避免Bitmap的浪费
       当你加载一个bitmap时,仅仅需要保留适配当前屏幕设备分辨率的数据即可,如果原图高于你的设备分辨率,需要做缩小的动作。请记住,增加bitmap的尺寸会对内存呈现出2次方的增加,因为X与Y都在增加。
       使用完Bitmap后 确定不会再使用 则需要注意Bitmap的内存回收(recycle())处理。

    6、使用优化的数据容器
       利用Android Framework里面优化过的容器类,例如SparseArray,SparseBooleanArray, 与LongSparseArray. 通常的HashMap的实现方式更加消耗内存,因为它需要一个额外的实例对象来记录Mapping操作。另外,SparseArray更加高效在于他们避免了对key与value的autobox自动装箱,并且避免了装箱后的解箱。

    7、请注意内存开销(资源的开启关闭 如数据库查询过程游标的关闭、IO流的关闭、线程池的使用)

    8、请注意代码编写优化 (Java代码的性能优化)

    9、为序列化的数据使用nano protobufs  【小吕 暂未接触过这点,】

    10、Avoid dependency injection frameworks
       使用类似Guice或者RoboGuice等framework injection包是很有效的,因为他们能够简化你的代码。然而,那些框架会通过扫描你的代码执行许多初始化的操作,这会导致你的代码需要大量的RAM来map代码。但是mapped pages会长时间的被保留在RAM中。

    11、谨慎使用external libraries
       很多External library的代码都不是为移动网络环境而编写的,在移动客户端则显示的效率不高。至少,当你决定使用一个external library的时候,你应该针对移动网络做繁琐的porting与maintenance的工作。

    12、优化整体性能
       官方有列出许多优化整个app性能的文章:Best Practices for Performance. 这篇文章就是其中之一。有些文章是讲解如何优化app的CPU使用效率,有些是如何优化app的内存使用效率。
    其他 如 layout布局优化。同样还应该关注lint工具所提出的建议,进行优化。

    13、使用ProGuard来剔除不需要的代码
       ProGuard能够通过移除不需要的代码,重命名类,域与方法等方对代码进行压缩,优化与混淆。使用ProGuard可以是的你的代码更加紧凑,这样能够使用更少mapped代码所需要的RAM。

    14、对最终的APK使用zipalign
       在编写完所有代码,并通过编译系统生成APK之后,你需要使用zipalign对APK进行重新校准。如果你不做这个步骤,会导致你的APK需要更多的RAM,因为一些类似图片资源的东西不能被mapped。

    15、分析你的RAM使用情况
       一旦你获取到一个相对稳定的版本后,需要分析你的app整个生命周期内使用的内存情况,并进行优化,更多细节请参考Investigating Your RAM Usage.

    16、使用多进程(注意是多进程,别看成多线程了啊!!!)
       如果合适的话,有一个更高级的技术可以帮助你的app管理内存使用:通过把你的app组件切分成多个组件,运行在不同的进程中。这个技术必须谨慎使用,大多数app都不应该运行在多个进程中。因为如果使用不当,它会显著增加内存的使用,而不是减少。当你的app需要在后台运行与前台一样的大量的任务的时候,可以考虑使用这个技术。

    一个典型的例子是创建一个可以长时间后台播放的Music Player。如果整个app运行在一个进程中,当后台播放的时候,前台的那些UI资源也没有办法得到释放。类似这样的app可以切分成2个进程:一个用来操作UI,另外一个用来后台的Service.

    在各个应用的 manifest 文件中为各个组件申明 android:process 属性就可以分隔为不同的进程.例如你可以指定你一运行的服务从主进程中分隔成一个新的进程来并取名为"background"(当然名字可以任意取)
    <service android:name=".PlaybackService"
             android:process=":background" />

    进程名字必须以冒号开头":"以确保该进程属于你应用中的私有进程。


    原文:  http://developer.android.com/training/articles/memory.html


    展开全文
  • Random Access Memory(RAM)在任何软件开发环境中都是一个很宝贵的资源。这一点在物理内存通常很有限的移动操作系统上,显得尤为突出。尽管Android的Dalvik虚拟机扮演了常规的垃圾回收的角色,但这并不意味着你可以...
  • 一个空白WinForm在任务管理器中都占用几十兆内存,的确有点可怕!通常有3种方法: 1. 不要管他。  CLR &amp; GC 会自动管理内存占用,根据当前环境参数自动调整,这样会得到一个最佳化的运行效率。 2. 设置...
  • 内存优化专家 v7.5.zip

    2019-07-14 06:19:41
    专业的内存优化软件,完全提升系统效率. >特色功能: >●采用和系统内核连接的优化核心,优化释放内存快捷简单. >●与传统排除到虚拟内存文件的内存优化完全区分. >●系统底层操作内存优化,优化后不影响程序...
  • 但作为一名软件工程师,虚拟内存管理将直接影响你开发程序的性能!灵魂拷问作为一名程序员,小编认为以下关于虚拟内存的知识需要搞懂:1)什么是虚拟内存? 核心观点是什么?2)页表机制和页面置换算法的基本原理?为...
  • 原文:The Memory-Optimization Hoax:RAM optimizers make false promises 作者:Mark Russinovich 译者:盆盆   我们在浏览网页时,也许...如果点击这些弹出广告链接,则会看到某些所谓的优化软件,声称只需花费...
  • 如何管理优化APP内存

    2019-09-17 18:54:08
    随机存取存储器(RAM)在任何软件开发环境中都是非常有价值的资源,但对于物理内存经常受到限制的移动操作系统来说,它更有价值。 尽管Android运行时(ART)和Dalv...
  • AssetBundle内存优化

    2017-05-03 15:27:43
    对使用unity开发网络游戏和VR软件时,能合理地管理资源,熟悉Assetbundle资源更新流程,熟悉AssetBundle资源所占内存,并能对Assetbundle资源合理利用
  • 内存管理软件包括虚拟内存系统、地址转换、交换、换页和分配。与性能密切相关的内容包括:内存释放、空闲链表、页扫描、交换、进程地址空间和内存分配器。在Linux中,空闲链表通常由分配器消耗,如内核的slab分配器...
  • Android 内存优化(一)

    2018-05-19 20:03:20
    在说 Android APP 的内存优化之前,必须先了解 Java 的内存管理机制,以及在此基础上 Android 是如何对内存进行管理的。 Java 内存管理 在内存管理方面,与 C、C++ 手动管理内存相比,Java 拥...
  • 随机存取存储器 (RAM) 在任何软件开发环境中都是一项宝贵资源,但在移动操作系统中,由于物理内存通常都有限,因此 RAM 就更宝贵了。虽然 Android 运行时 (ART) 和 Dalvik 虚拟机都执行例行的垃圾回收任务,但这并不...
  • 软件上,将操作系统从Windows 2000升级到Windows Server 2003;在硬件上,将服务器中的内存由原来的512MB增加到1GB(1024MB)。 在升级后的开始几个星期之内,服务器在使用中表现良好。但是不久后就发现,在...
  • RamCleaner 是一款小巧、美观的内存优化整理软件。因为 Windows 操作系统本身在内存管理机制上存在着固有的缺陷,所以往往是系统运行一段时间后速度就会减慢,当有较多程序同时运行时还有可能导致内存耗竭而死机。而...
  • RAM对于软件开发环境而言是有价值的资源,但它对受限于物理内存限制的操作系统具有更大的价值。即使Android Runtime和Dalvik virtual machein执行常规的垃圾回收,但这并不意味着你可以忽略app在何时何地指派和释放...
  • Android 性能优化——管理应用的内存

    千次阅读 2017-04-04 01:53:37
    随机存取存储器(RAM)在任何软件开发环境中都是一个很宝贵的资源。这一点在物理内存通常很有限的移动操作系统上,显得尤为突出。你需要避免引入内存泄漏(通常是由于静态成员变量中持有对象引用导致),并在适当的...
  • 内存管理机制的优劣程度极大地影响着嵌入式系统的整体性能,因此在嵌入式RTOS的内存管理机制中必须满足以下3个要求:  ①实时性。在嵌入式RTOS中不仅要求调度机制的实时性,资源的分配和回收的实时性也十分重要。 ...
  • 对使用unity开发网络游戏和VR软件时,能合理地管理资源,熟悉Assetbundle资源更新流程,熟悉AssetBundle资源所占内存,并能对Assetbundle资源合理利用
  • 内存管理

    2021-01-15 22:26:00
    分配给浏览器的内存通常比分配给桌面软件的要少很多,分配给移动浏览器的就更少了。这更多出于安全考虑而不是别的,就是为了避免运行大量JavaScript的网页耗尽系统内存而导致操作系统崩溃。这个内存限制不仅影响变量...
  • 关于java内存优化

    2008-10-18 10:57:00
     用内存分析软件分析好N久也没找到啥. 不过最后还是给我找到了个比较好的办法.虽然有点笨. 组xml直接用StringBuffer组.特别是向数据库这种组xml频繁的情况下.解xml用SAX.内存占用比dom4j少. 自动管理内存是java...
  • 引 言 在嵌入式领域中,嵌入式实时操作系统(RTOS)正得到越来越广泛的应用。采用嵌入式实时操作系统可以更合理、更有效地利用CPU的资源,简化...内存资源作为嵌入式系统中极为重要的资源之一,其管理机制历来是嵌入式系
  • 虽然该选项仍然存在且在有些环境下需要用到,但通常情况下管理员不必指定为 SQL Server 分配多少内存。 所有数据库软件的主要设计目标之一是尽量减少磁盘 I/O,因为磁盘的读取和写入操作占用大量资源。SQL Server ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 877
精华内容 350
关键字:

内存优化管理软件