精华内容
下载资源
问答
  • Linux内存管理机制
    千次阅读
    2017-02-16 04:07:24

    在Linux中经常发现空闲内存很少,似乎所有的内存都被系统占用了,表面感觉是内存不够用了,其实不然。这是Linux内存管理的一个优秀特性,主要特点是,无论物理内存有多大,Linux 都将其充份利用,将一些程序调用过的硬盘数据读入内存(buffer/cache),利用内存读写的高速特性来提高Linux系统的数据访问性能。在这方面,区别于Windows的内存管理。本文从Linux的内存管理机制入手,简单介绍linux如何使用内存、监控内存,linux与windows内存管理上的区别简介,linux内存使用的一大特点(buffer/cache的异同)。

    一、Linux内存管理机制

    物理内存和虚拟内存

      我们知道,直接从物理内存读写数据要比从硬盘读写数据要快的多,因此,我们希望所有数据的读取和写入都在内存完成,而内存是有限的,这样就引出了物理内存与虚拟内存的概念。

      物理内存就是系统硬件提供的内存大小,是真正的内存,相对于物理内存,在linux下还有一个虚拟内存的概念,虚拟内存就是为了满足物理内存的不足而提出的策略,它是利用磁盘空间虚拟出的一块逻辑内存,用作虚拟内存的磁盘空间被称为交换空间(Swap Space)。
      
      作为物理内存的扩展,linux会在物理内存不足(注意这一条件,这一条件的量化分析请参考https://www.douban.com/note/349467816/)时,使用交换分区的虚拟内存,更详细的说,就是内核会将暂时不用的内存块信息写到交换空间,这样以来,物理内存得到了释放,这块内存就可以用于其它目的,当需要用到原始的内容时,这些信息会被重新从交换空间读入物理内存。
      
      Linux的内存管理采取的是分页存取机制(详细可参考http://www.linuxeye.com/Linux/1931.html),为了保证物 理内存能得到充分的利用,内核会在适当的时候将物理内存中不经常使用的数据块自动交换到虚拟内存中,而将经常使用的信息保留到物理内存。

      要深入了解linux内存运行机制,需要知道下面提到的几个方面:Linux系统会根据系统配置不时地进行页面交换操作,以保持一定量的空闲物理内存,有些配置下即使并没有什么事情需要内存,Linux也会交换出暂时不用的内存页面。这可以避免等待交换所需的时间。相关的配置有/etc/sysctl.conf中的vm.swappiness配置(配制方法请参考http://www.vcaptain.com/?id=17),该参数的作用简单描述就是“当 swappiness 内容的值为 0 时,表示最大限度地使用物理内存,物理内存使用完毕后,才会使用 swap 分区;当 swappiness 内容的值为 100 时,表示积极地使用 swap 分区,并且把内存中的数据及时地置换到 swap 分区。Linux 系统初始安装完成时,其默认值为 60, 这表示空闲物理内存少于 60% 时开始启用内存置换算法,将内存中不常使用的数据置换到 swap 分区。”(具体如何起作用请参考https://www.douban.com/note/349467816/
      
      Linux 进行页面交换是有条件的,不是所有页面在不用时都交换到虚拟内存,linux内核根据”最近最经常使用“算法,仅仅将一些不经常使用的页面文件交换到虚拟 内存,有时我们会看到这么一个现象:linux物理内存还有很多,但是交换空间也使用了很多。其实,这并不奇怪,例如:一个占用很大内存的进程运行时,需 要耗费很多内存资源,此时就会有一些不常用页面文件被交换到虚拟内存中,但后来这个占用很多内存资源的进程结束并释放了很多内存时,刚才被交换出去的页面 文件并不会自动的交换进物理内存,除非有这个必要,那么此刻系统物理内存就会空闲很多,同时交换空间也在被使用,就出现了刚才所说的现象了。关于这点,不 用担心什么,只要知道是怎么一回事就可以了。
      交换空间的页面在使用时会首先被交换到物理内存,如果此时没有足够的物理内存来容纳这些页 面,它们又会被马上交换出去,如此以来,虚拟内存中可能没有足够空间来存储这些交换页面,最终会导致linux出现假死机、服务异常等问题,linux虽 然可以在一段时间内自行恢复,但是恢复后的系统已经基本不可用了。

      分配太多的Swap空间会浪费磁盘空间,而Swap空间太少,则系统会发生错误。 如果系统的物理内存用光了,系统就会跑得很慢,但仍能运行;如果Swap空间用光了,那么系统就会发生错误。例如,Web服务器能根据不同的请求数量衍生 出多个服务进程(或线程),如果Swap空间用完,则服务进程无法启动,通常会出现“application is out of memory”的错误,严重时会造成服务进程的死锁。因此Swap空间的分配是很重要的。
      
      因此,合理规划和设计Linux内存的使用,是非常重要的。

    二、linux和windows内存管理的区别

       Linux 优先使用物理内存,当物理内存还有空闲时,linux是不会释放内存的,即时占用内存的程序已经被关闭了(这部分内存就用来做缓存了)。也就是说,即时你 有很大的内存,用过一段时间后,也会被占满。这样做的好处是,启动那些刚开启过的程序、或是读取刚存取过得数据会比较快,对于服务器很有好处。
      windows则总是给内存留下一定的空闲空间,即时内存有空闲也会让程序使用一些虚拟内存,这样做的好处是,启动新的程序比较快,直接分给它些空闲 内存就可以了,而linux下呢?由于内存经常处于全部被使用的状态,则要先清理出一块内存,再分配给新的程序使用,因此,新程序的启动会慢一些。

    三、buffers与cached

    异同点

      在Linux 操作系统中,当应用程序需要读取文件中的数据时,操作系统先分配一些内存,将数据从磁盘读入到这些内存中,然后再将数据分发给应用程序;当需要往文件中写 数据时,操作系统先分配内存接收用户数据,然后再将数据从内存写到磁盘上。然而,如果有大量数据需要从磁盘读取到内存或者由内存写入磁盘时,系统的读写性 能就变得非常低下,因为无论是从磁盘读数据,还是写数据到磁盘,都是一个很消耗时间和资源的过程,在这种情况下,Linux引入了buffers和 cached机制。

      buffers与cached都是内存操作,用来保存系统曾经打开过的文件以及文件属性信息,这样当操作系统需要读取某些文件时,会首先在buffers 与cached内存区查找,如果找到,直接读出传送给应用程序,如果没有找到需要数据,才从磁盘读取,这就是操作系统的缓存机制,通过缓存,大大提高了操 作系统的性能。但buffers与cached缓冲的内容却是不同的。

      buffers是用来缓冲块设备做的,它只记录文件系统的元数据(metadata)以及 tracking in-flight pages,而cached是用来给文件做缓冲。更通俗一点说:buffers主要用来存放目录里面有什么内容,文件的属性以及权限等等。而cached直接用来记忆我们打开过的文件和程序。

    内存释放

    linux系统中/proc是一个虚拟文件系统,我们可以通过对它的读写操作做为与kernel实体间进行通信的一种手段。也就是说可以通过修改/proc中的文件,来对当前kernel的行为做出调整。那么我们可以通过调整/proc/sys/vm/drop_caches来释放内存。

    四、Linux 进程在内存数据结构

      可以看到一个可执行程序在存储(没有调入内存)时分为代码段,数据段,未初始化数据段三部分:

    • 代码段:存放CPU执行的机器指令。通常代码区是共享的,即其它执行程序可调用它。假如机器中有数个进程运行相同的一个程序,那么它们就可以使用同一个代码段。

    • 数据段:存放已初始化的全局变量,静态变量(包括全局和局部的),常量。static全局变量和static函数只能在当前文件中被调用。

    • 未初始化数据区(uninitializeddata segment,BSS):存放全局未初始化的变量。BSS的数据在程序开始执行之前被初始化为0或NULL。
      代码区所在的地址空间最低,往上依次是数据区和BSS区,并且数据区和BSS区在内存中是紧挨着的。。
      可执行程序在运行时又多出了两个区域:栈段(Stack)和堆段(Heap)。

    • 栈区:由编译器自动释放,存放函数的参数值,局部变量等。每当一个函数被调用时,该函数的返回类型和一些调用的信息被存储到栈中。然后这个被调用的函数再为它的自动变量和临时变量在栈上分配空间。每调用一个函数一个新的栈就会被使用。栈区是从高地址位向低地址位增长的,是一块连续的内在区域,最大容量是由系统预先定义好的,申请的栈空间超过这个界限时会提示溢出,用户能从栈中获取的空间较小。

    • 堆段:用于存放进程运行中被动态分配的内存段,位于BSS和栈中间的地址位。由程序员申请分配(malloc)和释放(free)。堆是从低地址位向高地址位增长,采用链式存储结构。频繁地malloc/free造成内存空间的不连续,产生碎片。当申请堆空间时库函数按照一定的算法搜索可用的足够大的空间。因此堆的效率比栈要低的多。

    这个5中内存区域中数据段、BSS和堆通常是被连续存储的——内存位置上是连续的,而代码段和栈往往会被独立存放。有趣的是堆和栈两个区域关系很“暧昧”,他们一个向下“长”(i386体系结构中栈向下、堆向上),一个向上“长”,相对而生。但你不必担心他们会碰头,因为他们之间间隔很大(到底大到多少,你可以从下面的例子程序计算一下),绝少有机会能碰到一起。

    下图简要描述了进程内存区域的分布:
    这里写图片描述

    五、总结

    Linux操作系统的内存运行原理,很大程度上是根据服务器的需求来设计的,例如系统的缓冲机制会把经常使用到的文件和数据缓存在cached 中,linux总是在力求缓存更多的数据和信息,这样再次需要这些数据时可以直接从内存中取,而不需要有一个漫长的磁盘操作,这种设计思路提高了系统的整 体性能。

    更多相关内容
  • 以下为文章节选 本文背景: 在编程中,很多Windows或...对Windows内存管理机制了解清楚,有效的利用C++内存函数管理和使用内存。 本文内容: 本文一共有六节,由于篇幅较多,故按节发表。 1. 进程地址空间 1.1地址空间
  • Windows内存管理机制.pdf
  • 从进程空间,内存状态,虚拟内存,内存映射文件,堆,堆栈方面,介绍了Windows内存管理机制及C++内存分配。《windows核心编程》中的精华浓缩。
  • Windows内存管理机制(20211010010646).pdf
  • Windows 2000/XP 内存管理机制 冯秋 2004-9-27 概 述 . 1 地 址 空 间 的 布 局 . 1 地 址 转 换 机 制 . 2 用 户 空 间 内 存 分 配 方 式 . 4 系 统 内 存 分 配 . 5 缺 页 处 理 . 5 工 作 集 . 6 物 理 内 存 管...
  • 很好的介绍windows内存管理的文章,很全面,对于初学者很有帮助的哦!
  • Windows内存管理机制.doc
  • 全面介绍Windows内存管理机制及C++内存分配实例
  • 介绍Windows内存管理机制及C++内存分配实例
  • Windows 内存机制说明

    千次阅读 2018-11-06 22:41:29
    1.内存分布概述  每个进程都有自己的虚拟地址空间,对于32位进程来说,这个地址空间的大小为4GB,这是因为32位指针可以表示从0x00000000到0xFFFFFFFF之间的任意一值。对于64位进程来说,由于64位指针可以表示从0x...

    1.内存分布概述

           每个进程都有自己的虚拟地址空间,对于32位进程来说,这个地址空间的大小为4GB,这是因为32位指针可以表示从0x00000000到0xFFFFFFFF之间的任意一值。对于64位进程来说,由于64位指针可以表示从0x00000000’00000000到0xFFFFFFFF’FFFFFFFF之间的任一值,因此这个地址空间大小为16EB。因为每个进程都有自己专用的地址空间,当进程中的各线程运行时,它们只能访问属于该进程的内存。线程既看不到属于其他进程的内存,也无法访问它们。

    2.虚拟地址空间分区

           应用程序虽然有这么大的地址空间可用,但是这只是虚拟地址空间,不是物理存储器。这个地址空间只不过是一个内存的地址区间。

           每个进虚拟地址空间被划分为许多分区(partion)。由于地址空间的分区依赖于操作系统的底层实现。因此会随着Windows内核的不同而略有变化。

    分区

    x86 32

    Windows

    3G用户模式下的

    x86 32位Windows

    x64 64位Windows

    IA-64 64位的 Windows

    空指针

    赋值分区

    0x00000000

    0xFFFFFFFF

    0x00000000

    0xFFFFFFFF

    0x00000000’00000000

    0x00000000’0000FFFF

    0x00000000’00000000

    0x00000000’0000FFFF

    用户模式

    分区

    0x00010000

    0x7FFEFFFF

    0x00010000

    0xBFFEFFFF

    0x00000000’00010000

    0x000007FF’FFFFFFFF

    0x00000000’00010000

    0x000006FB’FFFEFFFF

    64K禁入

    分区

    0x7FFF0000

    0x7FFFFFFF

    0xBFFF0000

    0xBFFFFFFF

    0x000007FF’FFFF0000

    0x000007FF’FFFFFFFF

    0x000006FB’FFFF0000

    0x000006FB’FFFFFFFF

    内核模式

    分区

    0x80000000

    0xFFFFFFFF

    0xC0000000

    0xFFFFFFFF

    0x00000800’00000000

    0xFFFFFFFF’FFFFFFFF

    0x000006FC’00000000

    0xFFFFFFFF’FFFFFFFF

          从上表中可能看到,32位Winows内核和64位Windows内核的分区基本一致,唯一的不同在于分区的大小和分区的位置。

    内存使用

     

    2.1.空指针赋值分区

           这一分区是进程地址空间中从0x00000000到0x0000FFFF的闭区间,保留该分区的目的是为了帮助程序捕获对于空指针的赋值。没有任何办法可以让我们分配到位于这一地址空间的虚拟内存,即使是使用Win32的应用程序编程接口(appliction programming interface,通常简称API)也不例外。如果进程中的线程试图读取或写入于这一分区内的内存地址,就会引发访问违规。

            如果malloc无法分配足够的内在,那么它会返回NULL。地址空间中的这一分区是禁止访问的,所以会引发内存访问违规并导致进程被终止。这一特性可以帮助开发人员发现应用程序中的缺陷。

    2.2.用户模式分区

            这一分区是进程地址空间的驻地,可用的地址空间和用户模式分区的大小取决于CPU体系结构。进程无法通过指针来读取、写入或以任何方式,访问驻留在这一分区中的其他进程的数据。对所有应用程序来说,进程的大部分数据都保存在这一分区。由于每个进程都有自己的数据分区。因此一个应用程序破另一个应用程序的可能性就非常小,从而使得整个系统更加坚固。

           在早期版本的Windows中,Microsoft不允许用户程序访问2GB以上的地址空间,为了让此类应用程序即使在用模式分区大于2GB的环境下仍能正常运行,Microsoft提供代了一种模式来增大用户模式分区,最多不超过3GB。

           当前系统即将运行一个应用程序时,它会检查应用程序在链接时是否使用了/LARGEADDRESSAWARE链接器开关。如果是,则相应于应用程序在声明它会充分利用大用户模式地址空间,而不会对内在地址进行任何不当的操作。反之,如果应用程序在链接时没有使用/LARGEADDRESSAWARE开关,那么操作系统会保留用户模式分区中2GB以上到内核模式开始处的整个部分。

    2.2.1.程序内存使用分布

    栈

    可参考https://en.wikipedia.org/wiki/Data_segment#Program_memory

    2.2.1.1.Text

    【原文】

    The code segment, also known as a text segment or simply as text, is where a portion of an object file or the corresponding section of the program's virtual address space that contains executable instructions is stored and is generally read-only and fixed size.

           代码段,通常是指用来存放程序执行代码的一块内存区域。这部分区域的大小在程序运行前就已经确定,并且内存区域通常属于只读。

          在代码段中,也有可能包含一些只读的常数变量,例如字符串常量等。

           代码段,也称为文本段,或简称为文本,是指存储有可执行指令的对象文件或程序的虚拟地址空间的相应部分的一部分,并且通常是只读的和固定大小的。

    2.2.1.2.Data

    【原文】

    The .data segment contains any global or static variables which have a pre-defined value and can be modified. That is any variables that are not defined within a function (and thus can be accessed from anywhere) or are defined in a function but are defined as static so they retain their address across subsequent calls. Examples, in C, include:

    int val = 3;

    char string[] = "Hello World";

    The values for these variables are initially stored within the read-only memory (typically within .text) and are copied into the .data segment during the start-up routine of the program.

           数据段包含任何具有预定义值并可被修改的全局变量或静态变量。这是任何函数中未定义的变量(因此可以从任何地方访问)或在函数中定义,但被定义为静态的,因此它们在随后的调用中保留它们的地址。C中的例子包括:

    int val = 3;

    char string[] = "Hello World";

           这些变量的值最初存储在只读存储器(通常在.text中),并在程序的启动例程期间复制到.DATA段。

    2.2.1.3.BSS

    【原文】

    The BSS segment, also known as uninitialized data, is usually adjacent to the data segment. The BSS segment contains all global variables and static variables that are initialized to zero or do not have explicit initialization in source code. For instance, a variable defined as static int i; would be contained in the BSS segment.

            BBS段,也称为未初始化数据段,通常与数据段相邻,包含初始化为或在源码中未进行显式初始化的全局变量和静态变量。例如,定义为静态整型变量i将被包含在BSS段中。

    2.2.1.4.Heap

    【原文】

    The heap area commonly begins at the end of the .bss and .data segments and grows to larger addresses from there. The heap area is managed by malloc, calloc, realloc, and free, which may use the brk and sbrk system calls to adjust its size (note that the use of brk/sbrk and a single "heap area" is not required to fulfill the contract of malloc/calloc/realloc/free; they may also be implemented using mmap/munmap to reserve/unreserve potentially non-contiguous regions of virtual memory into the process' virtual address space). The heap area is shared by all threads, shared libraries, and dynamically loaded modules in a process.

            堆区域通常在.BSS和.DATA段的末尾开始,并从那里扩展到更大的地址。堆区域由Maloc、CaloC、ReLoLc和空闲管理,可以使用BRK和SBRK系统调用来调整其大小(注意使用BRK/SBRK和单个堆区)不需要履行MalC/CaloC/ReAlOLC/FILE的合同;也可以使用MMAP/MunMMAP来实现。将虚拟内存的潜在非相邻区域保留/不保留到进程的“虚拟地址空间”中。堆区域由进程中的所有线程、共享库和动态加载模块共享。

    2.2.1.5.Stack

    【原文】

    Main article: Call stack

    The stack area contains the program stack, a LIFO structure, typically located in the higher parts of memory. A "stack pointer" register tracks the top of the stack; it is adjusted each time a value is "pushed" onto the stack. The set of values pushed for one function call is termed a "stack frame". A stack frame consists at minimum of a return address. Automatic variables are also allocated on the stack.

    The stack area traditionally adjoined the heap area and they grew towards each other; when the stack pointer met the heap pointer, free memory was exhausted. With large address spaces and virtual memory techniques they tend to be placed more freely, but they still typically grow in a converging direction. On the standard PC x86 architecture the stack grows toward address zero, meaning that more recent items, deeper in the call chain, are at numerically lower addresses and closer to the heap. On some other architectures it grows the opposite direction.

     主要文章:调用堆栈

              堆栈区域包含程序栈,即LIFO结构,通常位于内存的较高部分。一个“堆栈指针”寄存器跟踪堆栈的顶部;每当一个值被“推”到堆栈时,它就被调整。一个函数调用所推的值集称为“堆栈框架”。堆栈帧包括返回地址的最小值。自动变量也被分配到堆栈上。

           堆栈区域传统上毗邻堆区域,它们彼此生长;当堆栈指针遇到堆指针时,空闲内存被耗尽。随着大的地址空间和虚拟内存技术,它们倾向于更自由地放置,但它们仍然通常在收敛的方向上生长。在标准的PC x86架构上,堆栈向地址零增长,这意味着在调用链中较近的项在数值较低的地址上更接近堆。在其他一些架构上,它生长了相反的方向。

    2.3.内核模式分区

            这一分区是操作系统代码的驻地,与线程调度、内存管理、文件系统支持、网络支持以及设备驱动程序相关的代码都载入到该分区。在这一分区内的任何东西为所有进程共有。虽然这一分区就在每个进程中用户模式分区的上方,但该分区中的所有代码和数据都被完全保护起来。如果一个应用程序试图读取或写入这一分区中的内存地址,会引发违规。在默认情况下,访问违规会导致系统先向用户显示一个消息框,然后结束应用程序。

    3.堆栈段说明
    3.1.栈(Stack)
           当系统创建线程时,会为线程栈预订一块空间区域(每个线程都有自己的栈),并给区域调拨一些物理存储器。默认情况下,系统会预订1MB的地址空间并调拨两个页面的存储器。但是,在构建应用程序时开发人员可以通过两种方法来改变该默认值,一种方法是使用Microsoft C++编译器的/F选项,另一种方法是使用Microsoft C++链接器的/STACK选项。在构建应用程序时,链接器会把想要的栈的大小写入到.exe或.dll文件的PE文件头中。当前系统创线栈的时候,会根据PE文件头中的大小来预订地址空间的区域。但是在调用CreateThread或_beginthreadex函数时,开发人员也可以另外指定需要在一开始就调拨的存储器数量。这两个函数都有一个参数,可以用来指定一开始要调拨给线程栈的地址空间区域的存储器的大小。如果该参数设为0,那么系统会使用PE文件头中指定的大小,所使用的都是默认值(即区域大小为1MB),每次调拨一个存储页面。
    3.2.堆(Heap)
           堆非常适用分配大量的小型数据。堆是用来管理链表和树的最佳方式。堆的优点是它能让我们专心解决手头上的问题,而不必理会分配粒度和页面边界这类事情。堆的缺点是分配和释放内存块的速度比其他方式慢,而且也无法对物理存储器的调拨和摊销进行直接控制。
           进程初始化时候,系统会在进程的地址空间中创建一个堆,这个堆被称为进程的默认堆(Default heep)。在默认情况下,这个堆的地址空间区域的大小是1MB。但是系统可以增大进程的默认堆,使它大于1MB。我们也可以在创建应用程序的时候用/HEAP链接器开关来改变默认区域大小。由于动态链接库(.DLL)没有与之关联的堆,因此在创建DLL的时候不应使用/HEAP开关。

    展开全文
  • 这是Linux内存管理的一个优秀特性,在这方面,区别于 Windows内存管理。主要特点是,无论物理内存有多大,Linux 都将其充份利用,将一些程序调用过的硬盘数据读入内存,利用内存读写的高速特性来提高Linux系统的...
  • windows内存管理

    2022-02-16 17:30:11
    window 内存管理机制,如何查看进程的内存占用,以及相关的概念介绍:工作集,私有内存,共享内存 等。我用官方的html文档做成的电子书。
  • Windows驱动编程视频教程 详尽的讲解 里面还有屏幕录制的录像
  • Windows 内存管理机制方面地资料整理 以下关于 windows 地内存管理方面地资料整理 ,中间有些部分是根据我个人理解 写上去地 ,有些知识点是资料中摘录地 .对于 windows 内存管理机制方面比较感兴 趣地朋友可以看看 ....
  • 本文背景:在编程中,很多Windows或...本文目的:对Windows内存管理机制了解清楚,有效的利用C++内存函数管理和使用内存。本文内容:本文一共有六节,由于篇幅较多,故按节发表。其他章节请看本人博客的Windows内存管

    本文背景:

    在编程中,很多WindowsC++的内存函数不知道有什么区别,更别谈有效使用;根本的原因是,没有清楚的理解操作系统的内存管理机制,本文企图通过简单的总结描述,结合实例来阐明这个机制。

    本文目的:

    Windows内存管理机制了解清楚,有效的利用C++内存函数管理和使用内存。

    本文内容:

    本文一共有六节,由于篇幅较多,故按节发表。其他章节请看本人博客的Windows内存管理及C++内存分配实例(一)(二)(四)(五)和(六)。

     

    3.      内存管理机制 -- 虚拟内存 (VM)

    ·        虚拟内存使用场合

    虚拟内存最适合用来管理大型对象或数据结构。比如说,电子表格程序,有很多单元格,但是也许大多数的单元格是没有数据的,用不着分配空间。也许,你会想到用动态链表,但是访问又没有数组快。定义二维数组,就会浪费很多空间。

    它的优点是同时具有数组的快速和链表的小空间的优点。

     

    ·        分配虚拟内存

    如果你程序需要大块内存,你可以先保留内存,需要的时候再提交物理存储器。在需要的时候再提交才能有效的利用内存。一般来说,如果需要内存大于1M,用虚拟内存比较好。

     

    ·        保留

    用以下Windows 函数保留内存块

    VirtualAlloc (PVOID 开始地址,SIZE_T 大小,DWORD 类型,DWORD 保护属性)

    一般情况下,你不需要指定开始地址,因为你不知道进程的那段空间是不是已经被占用了;所以你可以用NULL大小是你需要的内存字节;类型MEM_RESERVE(保留)、MEM_RELEASE(释放)和MEM_COMMIT(提交)。保护属性在前面章节有详细介绍,只能用前六种属性。

    如果你要保留的是长久不会释放的内存区,就保留在较高的空间区域,这样不会产生碎片。用这个类型标志可以达到:

    MEM_RESERVE|MEM_TOP_DOWN

    C++程序:保留1G的空间

    LPVOID pV=VirtualAlloc(NULL,1000*1024*1024,MEM_RESERVE|MEM_TOP_DOWN,PAGE_READWRITE); 

                if(pV==NULL)

                cout<<"没有那么多虚拟空间!"<<endl;

                MEMORYSTATUS memStatusVirtual1;

                GlobalMemoryStatus(&memStatusVirtual1);

                cout<<"虚拟内存分配:"<<endl;

                printf("指针地址=%x/n",pV);

    cout<<"减少物理内存="<<memStatusVirtual.dwAvailPhys-memStatusVirtual1.dwAvailPhys<<endl;

    cout<<"减少可用页文件="<<memStatusVirtual.dwAvailPageFile-memStatusVirtual1.dwAvailPageFile<<endl;

    cout<<"减少可用进程空间="

    <<memStatusVirtual.dwAvailVirtual-memStatusVirtual1.dwAvailVirtual<<endl<<endl;

    结果如下:

     

    可见,进程空间减少了1G;减少的物理内存和可用页文件用来管理页目和页表。但是,现在访问空间的话,会出错的:

    int * iV=(int*)pV;

       //iV[0]=1;现在访问会出错,出现访问违规

     

    ·        提交

    你必须提供一个初始地址和提交的大小。提交的大小系统会变成页面的倍数,因为只能按页面提交。指定类型是MEM_COMMIT。保护属性最好跟区域的保护属性一致,这样可以提高系统管理的效率。

    C++程序:提交100M的空间

    LPVOID pP=VirtualAlloc(pV,100*1024*1024,MEM_COMMIT,PAGE_READWRITE);    

                if(pP==NULL)

                cout<<"没有那么多物理空间!"<<endl;

                int * iP=(int*)pP;

                iP[0]=3;

                iP[100/sizeof(int)*1024*1024-1]=5;//这是能访问的最后一个地址

                //iP[100/sizeof(int)*1024*1024]=5;访问出错

      

    ·        保留&提交

    你可以用类型MEM_RESERVE|MEM_COMMIT一次全部提交。但是这样的话,没有有效地利用内存,和使用一般的C++动态分配内存函数一样了。

     

    ·        更改保护属性

    更改已经提交的页面的保护属性,有时候会很有用处,假设你在访问数据后,不想别的函数再访问,或者出于防止指针乱指改变结构的目的,你可以更改数据所处的页面的属性,让别人无法访问。

    VirtualProtect (PVOID 基地址,SIZE_T 大小,DWORD 新属性,DWORD 旧属性)

    基地址是你想改变的页面的地址,注意,不能跨区改变。

    C++程序:更改一页的页面属性,改为只读,看看还能不能访问

    DWORD protect;

                iP[0]=8;

                VirtualProtect(pV,4096,PAGE_READONLY,&protect);

                int * iP=(int*)pV;

    iP[1024]=9;//可以访问,因为在那一页之外

                //iP[0]=9;不可以访问,只读

                //还原保护属性

                VirtualProtect(pV,4096,PAGE_READWRITE,&protect);

       cout<<"初始值="<<iP[0]<<endl;//可以访问

     

    ·        清除物理存储器内容

    清除页面指的是,将页面清零,也就是说当作页面没有改变。假设数据存在物理内存中,系统没有RAM页面后,会将这个页面暂时写进虚拟内存页文件中,这样来回的倒腾系统会很慢;如果那一页数据已经不需要的话,系统可以直接使用。当程序需要它那一页时,系统会分配另一页给它。

    VirtualAlloc (PVOID 开始地址,SIZE_T 大小,DWORD 类型,DWORD 保护属性)

    大小如果小于一个页面的话,函数会执行失败,因为系统使用四舍五入的方法;类型MEM_RESET

    有人说,为什么需要清除呢,释放不就行了吗?你要知道,释放了后,程序就无法访问了。现在只是因为不需要结构的内容了,顺便提高一下系统的性能;之后程序仍然需要访问这个结构的。

    C++程序:

    清除1M的页面:

    PVOID re=VirtualAlloc(pV,1024*1024,MEM_RESET,PAGE_READWRITE);

                if(re==NULL)

       cout<<"清除失败!"<<endl;

    这时候,页面可能还没有被清零,因为如果系统没有RAM请求的话,页面内存保存不变的,为了看看被清零的效果,程序人为的请求大量页面:

    C++程序:

    VirtualAlloc((char*)pV+100*1024*1024+4096,memStatus.dwAvailPhys+10000000,MEM_COMMIT,PAGE_READWRITE);//没访问之前是不给物理内存的。   

                char* pp=(char*)pV+100*1024*1024+4096;

                for(int i=0;i<memStatus.dwAvailPhys+10000000;i++)

                pp[i]='V';//逼他使用物理内存,而不使用页文件

                GlobalMemoryStatus(&memStatus);

                cout<<"内存初始状态:"<<endl;

                cout<<"长度="<<memStatus.dwLength<<endl;

                cout<<"内存繁忙程度="<<memStatus.dwMemoryLoad<<endl;

                cout<<"总物理内存="<<memStatus.dwTotalPhys<<endl;

                cout<<"可用物理内存="<<memStatus.dwAvailPhys<<endl;

                cout<<"总页文件="<<memStatus.dwTotalPageFile<<endl;

                cout<<"可用页文件="<<memStatus.dwAvailPageFile<<endl;

                cout<<"总进程空间="<<memStatus.dwTotalVirtual<<endl;

                cout<<"可用进程空间="<<memStatus.dwAvailVirtual<<end;

       cout<<"清除后="<<iP[0]<<endl;

    结果如下:

     

    当内存所剩无几时,系统将刚清除的内存页面分配出去,同时不会把页面的内存写到虚拟页面文件中。可以看见,原先是8的值现在是0了。

     

    ·        虚拟内存的关键之处

    虚拟内存存在的优点是,需要的时候才真正分配内存。那么程序必须决定何时才提交内存。

    如果访问没有提交内存的数据结构,系统会产生访问违规的错误。提交的最好方法是,当你程序需要访问虚拟内存的数据结构时,假设它已经是分配内存的,然后异常处理可能出现的错误。对于访问违规的错误,就提交这个地址的内存。

     

    ·        释放

    可以释放整个保留的空间,或者只释放分配的一些物理内存。

    释放特定分配的物理内存:

    如果不想释放所有空间,可以只释放某些物理内存。

    开始地址是页面的基地址,这个地址不一定是第一页的地址,一个窍门是提供一页中的某个地址就行了,因为系统会做页边界处理,取该页的首地址;大小是页面的要释放的字节数;类型MEM_DECOMMIT

    C++程序:

                //只释放物理内存

                VirtualFree((int*)pV+2000,50*1024*1024,MEM_DECOMMIT);

                int* a=(int*)pV;

                a[10]=2;//可以使用,没有释放这一页

                MEMORYSTATUS memStatusVirtual3;

                GlobalMemoryStatus(&memStatusVirtual3);

                cout<<"物理内存释放:"<<endl;

    cout<<"增加物理内存="<<memStatusVirtual3.dwAvailPhys-memStatusVirtual2.dwAvailPhys<<endl;

    cout<<"增加可用页文件="<<memStatusVirtual3.dwAvailPageFile-memStatusVirtual2.dwAvailPageFile<<endl;

       cout<<"增加可用进程空间="

    <<memStatusVirtual3.dwAvailVirtual-memStatusVirtual2.dwAvailVirtual<<endl<<endl;

    结果如下:

     

    可以看见,只释放物理内存,没有释放进程的空间。

     

    释放整个保留的空间:

    VirtualFree (LPVOID 开始地址,SIZE_T 大小,DWORD 类型)

    开始地址一定是该区域的基地址;大小必须是0,因为只能释放整个保留的空间;类型MEM_RELEASE

    C++程序:

    VirtualFree(pV,0,MEM_RELEASE);

                //a[10]=2;不能使用了,进程空间也释放了

     

                MEMORYSTATUS memStatusVirtual4;

                GlobalMemoryStatus(&memStatusVirtual4);

                cout<<"虚拟内存释放:"<<endl;

    cout<<"增加物理内存="<<memStatusVirtual4.dwAvailPhys-memStatusVirtual3.dwAvailPhys <<endl;

    cout<<"增加可用页文件="<<memStatusVirtual4.dwAvailPageFile-memStatusVirtual3.dwAvailPageFile<<endl;

    cout<<"增加可用进程空间="

    <<memStatusVirtual4.dwAvailVirtual-memStatusVirtual3.dwAvailVirtual<<endl<<endl;

    结果如下:

     

    整个分配的进程区域被释放了,包括所占的物理内存和页文件。

     

    ·        何时释放

    如果数组的元素大小是小于一个页面4K的话,你需要记录哪些空间不需要,哪些在一个页面上,可以用一个元素一个Bit来记录;另外,你可以创建一个线程定时检测无用单元。

     

    ·        扩展地址AWE

    AWE是内存管理器功能的一套应用程序编程接口 (API) ,它使程序能够将物理内存保留为非分页内存,然后将非分页内存部分动态映射到程序的内存工作集。此过程使内存密集型程序(如大型数据库系统)能够为数据保留大量的物理内存,而不必交换分页文件以供使用。相反,数据在工作集中进行交换,并且保留的内存超过 4 GB 范围。

    对于物理内存小于2G进程空间时,它的作用是:不必要在物理内存和虚拟页文件中交换。

    对于物理内存大于2G进程空间时,它的作用是:应用程序能够访问的物理内存大于2G,也就相当于进程空间超越了2G的范围;同时具有上述优点。

    3GB

    当在boot.ini 上加上 /3GB 选项时,应用程序的进程空间增加了1G,也就是说,你写程序时,可以分配的空间又增大了1G,而不管物理内存是多少,反正有虚拟内存的页文件,大不了慢点。

    PAE

    当在boot.ini上加上 /PAE 选项时,操作系统可以支持大于4G的物理内存,否则,你加再多内存操作系统也是不认的,因为管理这么大的内存需要特殊处理。所以,你内存小于4G是没有必要加这个选项的。注意,当要支持大于16G的物理内存时,不能使用/3G选项,因为,只有1G的系统空间是不能管理超过16G的内存的。

    AWE

    当在boot.ini上加上 /AWE选项时,应用程序可以为自己保留物理内存,直接的使用物理内存而不通过页文件,也不会被页文件交换出去。当内存大于3G时,就显得特别有用。因为可以充分利用物理内存。

    当物理内存大于4G时,需要/PAE的支持。

    以下是一个boot.ini的实例图,是我机器上的:

     

     

     

    要使用AWE,需要用户具有Lock Pages in Memory权限,这个在控制面板中的本地计算机政策中设置。

    第一,分配进程虚拟空间:

    VirtualAlloc (PVOID 开始地址,SIZE_T 大小,DWORD 类型,DWORD 保护属性)

    开始地址可以是NULL,由系统分配进程空间;类型MEM_RESERVE|MEM_PHYSICAL保护属性只能是

    PAGE_READWRITE

    MEM_PHYSICAL指的是区域将受物理存储器的支持。

    第二,你要计算出分配的页面数目PageCount

    利用本文第二节的GetSystemInfo可以计算出来。

    第三,分配物理内存页面:

    AllocateUserPhysicalPages (HANDLE 进程句柄,SIZE_T 页数,ULONG_PTR 页面指针数组)

    进程句柄可以用GetCurrentProcess()获得;页数是刚计算出来的页数PageCount;页面数组指针unsigned long* Array[PageCount]

    系统会将分配结果存进这个数组。

    第四,将物理内存与虚拟空间进行映射:

    MapUserPhysicalPages (PVOID 开始地址,SIZE_T 页数,ULONG_PTR 页面指针数组)

    开始地址是第一步分配的空间;

    这样的话,虚拟地址就可以使用了。

    如果页面指针数组NULL,则取消映射。

    第五,释放物理页面

    FreeUserPhysicalPages (HANDLE 进程句柄,SIZE_T 页数,ULONG_PTR 页面指针数组)

    这个除了释放物理页面外,还会取消物理页面的映射。

    第六,释放进程空间

    VirtualFree (PVOID 开始地址,0MEM_RELEASE)

     

    C++程序:

    首先,在登录用户有了Lock Pages in Memory权限以后,还需要调用Windows API激活这个权限。

    BOOL VirtualMem::LoggedSetLockPagesPrivilege ( HANDLE hProcess,BOOL bEnable)                     

    {

                struct {

                            DWORD Count;//数组的个数

                            LUID_AND_ATTRIBUTES Privilege [1];} Info;

                HANDLE Token;

                //打开本进程的权限句柄

                BOOL Result = OpenProcessToken ( hProcess,

                            TOKEN_ADJUST_PRIVILEGES,

                            & Token);

                If (Result!= TRUE )

                {

                            printf( "Cannot open process token./n" );

                            return FALSE;

                }

                //我们只改变一个属性

                Info.Count = 1;

                //准备激活

                if( bEnable )

                        Info.Privilege[0].Attributes = SE_PRIVILEGE_ENABLED;

                else

                            Info.Privilege[0].Attributes = 0;

                //根据权限名字找到LGUID

                Result = LookupPrivilegeValue ( NULL,

                            SE_LOCK_MEMORY_NAME,

                            &(Info.Privilege[0].Luid));

                if( Result != TRUE )

                {

                            printf( "Cannot get privilege for %s./n", SE_LOCK_MEMORY_NAME );

                            return FALSE;

                }

                // 激活Lock Pages in Memory权限

    Result = AdjustTokenPrivileges ( Token, FALSE,(PTOKEN_PRIVILEGES) &Info,0, NULL, NULL);

                if( Result != TRUE )

                {

                            printf ("Cannot adjust token privileges (%u)/n", GetLastError() );

                            return FALSE;

                }

                else

                {

                            if( GetLastError() != ERROR_SUCCESS )

                            {

    printf ("Cannot enable the SE_LOCK_MEMORY_NAME privilege; ");

                                        printf ("please check the local policy./n");

                                        return FALSE;

                            }

                }

                CloseHandle( Token );

                return TRUE;

    }

     

    分配100M虚拟空间:

    PVOID pVirtual=VirtualAlloc(NULL,100*1024*1024,MEM_RESERVE|MEM_PHYSICAL,PAGE_READWRITE);

                if(pVirtual==NULL)

                            cout<<"没有那么大连续进程空间!"<<endl;

     

                MEMORYSTATUS memStatusVirtual5;

                GlobalMemoryStatus(&memStatusVirtual5);

                cout<<"虚拟内存分配:"<<endl;

    cout<<"减少物理内存="<<memStatusVirtual4.dwAvailPhys-memStatusVirtual5.dwAvailPhys<<endl

    cout<<"减少可用页文件="<<memStatusVirtual4.dwAvailPageFile-memStatusVirtual5.dwAvailPageFile<<endl;

       cout<<"减少可用进程空间="

    <<memStatusVirtual4.dwAvailVirtual-memStatusVirtual5.dwAvailVirtual<<endl<<endl;

    结果如下:

     

    可以看见,只分配了进程空间,没有分配物理内存。

     

    分配物理内存:

    ULONG_PTR pages=(ULONG_PTR)100*1024*1024/sysInfo.dwPageSize;

                ULONG_PTR *frameArray=new ULONG_PTR[pages];

                //如果没激活权限,是不能调用这个方法的,可以调用,但是返回FALSE

    BOOL flag=AllocateUserPhysicalPages(GetCurrentProcess(),

    &pages,frameArray);

                if(flag==FALSE)

                            cout<<"分配物理内存失败!"<<endl;

                MEMORYSTATUS memStatusVirtual6;

                GlobalMemoryStatus(&memStatusVirtual6);

                cout<<"物理内存分配:"<<endl;

    cout<<"减少物理内存="<<memStatusVirtual5.dwAvailPhys-memStatusVirtual6.dwAvailPhys<<endl

    cout<<"减少可用页文件="<<memStatusVirtual5.dwAvailPageFile-memStatusVirtual6.dwAvailPageFile<<endl;

    cout<<"减少可用进程空间="<<memStatusVirtual5.dwAvailVirtual-memStatusVirtual6.dwAvailVirtual<<endl<<endl;

      结果如下:

     

    分配了物理内存,可能分配时需要进程空间管理。

     

    物理内存映射进程空间:

    int* pVInt=(int*)pVirtual;

                //pVInt[0]=10;这时候访问会出错

                flag=MapUserPhysicalPages(pVirtual,1,frameArray);

                if(flag==FALSE)

                            cout<<"映射物理内存失败!"<<endl;

                MEMORYSTATUS memStatusVirtual7;

                GlobalMemoryStatus(&memStatusVirtual7);

                cout<<"物理内存分配:"<<endl;

    cout<<"减少物理内存="<<memStatusVirtual6.dwAvailPhys-memStatusVirtual7.dwAvailPhys<<endl

    cout<<"减少可用页文件="<<memStatusVirtual6.dwAvailPageFile-memStatusVirtual7.dwAvailPageFile<<endl;

    cout<<"减少可用进程空间="

    <<memStatusVirtual6.dwAvailVirtual-memStatusVirtual7.dwAvailVirtual<<endl<<endl;

    结果如下:

     

    这个过程没有损失任何东西。

     

    看看第一次映射和第二次映射的值:

    pVInt[0]=10;

                cout<<"第一次映射值="<<pVInt[0]<<endl;

                            flag=MapUserPhysicalPages(pVirtual,1,frameArray+1);

                if(flag==FALSE)

                            cout<<"映射物理内存失败!"<<endl;

                pVInt[0]=21;

                cout<<"第二次映射值="<<pVInt[0]<<endl;

                flag=MapUserPhysicalPages(pVirtual,1,frameArray);

                if(flag==FALSE)

                            cout<<"映射物理内存失败!"<<endl;

                cout<<"再现第一次映射值="<<pVInt[0]<<endl;

    结果如下:

     

    可以看出,第二次映射的值没有覆盖第一次映射的值,也就是说,用同一个进程空间地址可以取出两份数据,这样的话,相当于进程的地址空间增大了。

     

     

    1.      内存管理机制--进程地址空间

       http://blog.csdn.net/yeming81/archive/2008/01/16/2046193.aspx

     

    2.      内存管理机制--内存状态查询函数

     

       http://blog.csdn.net/yeming81/archive/2008/01/16/2046207.aspx

     

    4.      内存管理机制--内存映射文件 (Map)

          http://blog.csdn.net/yeming81/archive/2008/01/18/2050521.aspx

    5.      内存管理机制-- (Heap)

           http://blog.csdn.net/yeming81/archive/2008/01/19/2052311.aspx

    6.      内存管理机制--堆栈 (Stack)

           http://blog.csdn.net/yeming81/archive/2008/01/19/2052312.aspx

     

    ----文章结束----

    展开全文
  • Android 内存管理机制

    千次阅读 多人点赞 2018-09-03 14:41:45
    Linux的内存管理机制:Android毕竟是基于Linux内核实现的操作系统,因此有必要了解一下Linux的内存管理机制。 Android的内存管理相关知识:Android又不同于Linux,它是一个移动操作系统,因此其内存管理上也有...

    本文主要包括三大部分内容:

    内存管理基础:从整个计算机领域简述主要的内存管理技术。
    Linux的内存管理机制:Android毕竟是基于Linux内核实现的操作系统,因此有必要了解一下Linux的内存管理机制。
    Android的内存管理相关知识:Android又不同于Linux,它是一个移动操作系统,因此其内存管理上也有自己的特性,这一部分详细讲述Android的内存管理相关知识,包括 内存管理机制进程管理

    内存管理基础

    概述

    CPU只能访问其寄存器(Register)和内存(Memory), 无法直接访问硬盘(Disk)。 存储在硬盘上的数据必须首先传输到内存中才能被CPU访问。从访问速度来看,对寄存器的访问非常快,通常为1纳秒; 对内存的访问相对较慢,通常为100纳秒(使用缓存加速的情况下);而对硬盘驱动器的访问速度最慢,通常为10毫秒。

    寄存器(Register):CPU内部的高速存储区域

    当一个程序加载到内存中时,它由四个内存区域组成:

    • 堆栈(Stack):存储由该程序的每个函数创建的临时变量
    • 堆(Heap):该区域特别适用于动态内存分配
    • 数据(Data):存储该程序的全局变量和静态变量
    • 代码(Code):存储该程序的指令

    主要的内存管理技术

    • Base and limit registers(基址寄存器和界限寄存器)
    • Virtual memory(虚拟内存)
    • Swapping(交换)
    • Segmentation(分段)
    • Paging(分页)
    Base and limit registers(基址寄存器和界限寄存器)

    必须限制进程,以便它们只能访问属于该特定进程的内存位置。

    每个进程都有一个基址寄存器和限制寄存器:

    • 基址寄存器保存最小的有效存储器地址
    • 限制寄存器指定范围的大小

    例如,process 2的有效内存地址是300040到420940

    那么每个来自用户进程的内存访问都将首先针对这两个寄存器进行一次检查:

    操作系统内核可以访问所有内存位置,因为它需要管理整个内存。

    Virtual memory(虚拟内存)

    虚拟内存(VM)是OS为内存管理提供的基本抽象。

    • 所有程序都使用虚拟内存地址
    • 虚拟地址会被转换为物理地址
    • 物理地址表示数据的实际物理位置
    • 物理位置可以是内存或磁盘

    虚拟地址到物理地址的转换由存储器管理单元(MMU - Memory Management Unit)处理。MMU使用重定位寄存器(relocation register),其值在硬件级别上被添加到每个内存请求中。

    Swapping(交换)

    交换是一种可以暂时将进程从内存交换到后备存储,而之后又可以将其返回内存以继续执行的技术。

    后备存储通常是一个硬盘驱动器,其访问速度快,且大小足以存储内存映像的副本。

    如果没有足够的可用内存来同时保留内存中的所有正在运行的进程,则某些当前未使用CPU的进程可能会被交换到后备存储中。

    交换是一个非常缓慢的过程。 主要耗时部分是数据传输。例如,如果进程占用10MB内存并且后备存储的传输速率为40MB/秒,则需要0.25秒来进行数据传输。 再加上将数据交换回内存的时间,总传输时间可能是半秒,这是一个巨大的延迟,因此,有些操作系统已经不再使用交换了。

    Segmentation(分段)

    分段是一种将内存分解为逻辑片段的技术,其中每个片段代表一组相关信息。 例如,将每个进程按照堆栈,堆,数据以及代码分为不同的段,还有OS内核的数据段等。

    将内存分解成较小的段会增加寻找空闲内存的机会。

    每个段都有一对寄存器:

    • 基址寄存器:包含段驻留在内存中的起始物理地址
    • 限制寄存器:指定段的长度

    **段表(Segment table)**存储每个段的基址和限制寄存器信息。

    使用分段时,虚拟内存地址是一对:<段号,偏移量>

    • 段号(Segment Number):用作段表的索引以查找特定条目
    • 偏移量(Offset):首先与限制寄存器进行比较,然后与基址结合以计算物理内存地址

    Paging(分页)

    有时可用内存被分成许多小块,其中没有一块足够大以满足下一个内存需求,然而他们的总和却可以。这个问题被称为碎片(Fragmentation),许多内存分配策略都会受其影响。

    分页是一种内存管理技术,它允许进程的物理内存不连续。它通过在称为页面(Page)的相同大小的块中分配内存来消除碎片问题,是目前比较优秀的内存管理技术。

    分页将物理内存划分为多个大小相等的块,称为帧(Frame)。并将进程的逻辑内存空间也划分为大小相等的块,称为页面(Page)

    任何进程中的任何页面都可以放入任何可用的帧中。

    **页表(Page Table)**用于查找此刻存储特定页面的帧。

    使用分页时,虚拟内存地址是一对:<页码,偏移量>

    • 页码(Page Number):用作页表的索引,以查找此页面的条目
    • 偏移量(Offset):与基址相结合,以定义物理内存地址

    举一个分页地址转换的例子:

    虚拟内存地址为0x13325328,页表项0x13325包含的值是0x03004,那么物理地址是什么?

    答案:
    物理地址是0x03004328
    页码为0x13325,偏移量为0x328
    相应的帧号是0x03004

    Linux的内存管理机制

    在Linux系统下,监控内存常用的命令是free、top等,下面是一个free命令的执行结果:

    要了解Linux的内存管理,首先要明白上例中各个名词的意义:

    • total:物理内存的总大小。
    • used:已经使用的物理内存多小。
    • free:空闲的物理内存值。
    • shared:多个进程共享的内存值。
    • buffers / cached:用于磁盘缓存的大小(这部分是从物理内存中划出来的)。
    • 第二行Mem:代表物理内存使用情况。
    • 第三行(-/+ buffers/cached):代表磁盘缓存使用状态。
    • 第四行:Swap表示交换空间内存使用状态(这部分实际上是从磁盘上虚拟出来的逻辑内存)。

    free命令输出的内存状态,可以从两个角度来看:内核角度、应用层角度。

    1.从内核角度来查看内存的状态:

    就是内核目前可以直接分配到,不需要额外的操作,即free命令第二行 Mem 的输出。从上例中可见,41940 + 16360492 = 16402432,也就是说Mem行的 free + used = total,注意,这里的free并不包括buffers和cached。

    2.从应用层角度来查看内存的状态:

    也就是Linux上运行的程序可以使用的内存大小,即free命令第三行 -/+ buffers/cache 的输出。再来做一个计算41940+(465404+12714880)=13222224,即Mem行的free + buffers + cached = -/+ buffers/cache行的free,也就是说应用可用的物理内存值是Mem行的free、buffers和cached三者之和,可见-/+ buffers/cache行的free是包括buffers和cached的。

    对于应用程序来说,buffers/cached占有的内存是可用的,因为buffers/cached是为了提高文件读取的性能,当应用程序需要用到内存的时候,buffers/cached会很快地被回收,以供应用程序使用。

    物理内存和虚拟内存

    物理内存就是系统硬件提供的内存大小,是真正的内存。在linux下还有一个虚拟内存的概念,虚拟内存就是为了满足物理内存的不足而提出的策略,它是利用磁盘空间虚拟出的一块逻辑内存,用作虚拟内存的磁盘空间被称为 交换空间(Swap Space)

    linux的内存管理采取的是分页存取机制,为了保证物理内存能得到充分的利用,内核会在适当的时候将物理内存中不经常使用的数据块自动交换到虚拟内存中,而将经常使用的信息保留到物理内存。而进行这种交换所遵循的依据是“LRU”算法(Least Recently Used,最近最少使用算法)。

    Buffers和Cached有什么用

    在任何系统中,文件的读写都是一个耗时的操作,当应用程序需要读写文件中的数据时,操作系统先分配一些内存,将数据从磁盘读入到这些内存中,然后应用程序读写这部分内存数据,之后系统再将数据从内存写到磁盘上。如果是大量文件读写甚至重复读写,系统读写性能就变得非常低下,在这种情况下,Linux引入了缓存机制。

    buffers与cached都是从物理内存中分离出来的,主要用于实现磁盘缓存,用来保存系统曾经打开过的文件以及文件属性信息,这样当操作系统需要读取某些文件时,会首先在buffers与cached内存区查找,如果找到,直接读出传送给应用程序,否则,才从磁盘读取,通过这种缓存机制,大大降低了对磁盘的IO操作,提高了操作系统的数据访问性能。而这种磁盘高速缓存则是基于两个事实:第一,内存访问速度远远高于磁盘访问速度;第二,数据一旦被访问,就很有可能短期内再次被访问。

    另外,buffers与cached缓存的内容也是不同的。buffers是用来缓冲块设备做的,它只记录文件系统的元数据(metadata)以及 tracking in-flight pages,而cached是用来给文件做缓冲。更通俗一点说:buffers主要用来存放目录里面有什么内容,文件的属性以及权限等等。而cached直接用来记忆我们打开过的文件和程序

    为了验证我们的结论是否正确,可以通过vi打开一个非常大的文件,看看cached的变化,然后再次vi这个文件,感觉一下是不是第二次打开的速度明显快于第一次?

    接着执行下面的命令:

    find /* -name  *.conf
    

    看看buffers的值是否变化,然后重复执行find命令,看看两次显示速度有何不同。

    Linux内存管理的哲学

    Free memory is wasted memory.

    Linux的哲学是尽可能多的使用内存,减少磁盘IO,因为内存的速度比磁盘快得多。Linux总是在力求缓存更多的数据和信息,内存不够时,将一些不经常使用的数据转移到交换分区(Swap Space)中以释放更多可用物理内存,当然,如果交换分区的数据再次被读写时,又会被转移到物理内存中,这种设计思路提高了系统的整体性能。而Windows的处理方式是,内存和虚拟内存一起使用,不是以内存操作为主,结果就是IO的负担比较大,可能拖慢处理速度。

    Linux和Windows在内存管理机制上的区别

    在Linux系统使用过程中,你会发现,无论你的电脑内存配置多么优越,仍然不时的发生可用内存吃紧的现象,感觉内存不够用了,其实不然。这是Linux内存管理的优秀特性,无论物理内存有多大,Linux都将其充分利用,将一些程序调用过的硬盘数据缓存到内存,利用内存读写的高速性提高系统的数据访问性能。而Window只在需要内存时,才为应用分配内存,不能充分利用大容量的内存空间。换句话说,每增加一些内存,Linux都能将其利用起来,充分发挥硬件投资带来的好处,而Windows只将其作为摆设。

    所以说,一般我们不需要太关注Linux的内存占用情况,而如果Swap占用率一直居高不下的话,就很有可能真的是需要扩展内存了

    Android的内存管理机制

    Android使用虚拟内存和分页,不支持交换

    垃圾收集

    无论是ART还是Dalvik虚拟机,都和众多Java虚拟机一样,属于一种托管内存环境(程序员不需要显示的管理内存的分配与回收,交由系统自动管理)。托管内存环境会跟踪每个内存分配, 一旦确定程序不再使用一块内存,它就会将其释放回堆中,而无需程序员的任何干预。 回收托管内存环境中未使用内存的机制称为垃圾回收

    垃圾收集有两个目标:

    • 在程序中查找将来无法访问的数据对象;
    • 回收这些对象使用的资源。

    Android的垃圾收集器不带压缩整理功能(Compact),即不会对Heap做碎片整理。

    Android的内存堆是分代式(Generational)的,意味着它会将所有分配的对象进行分代,然后分代跟踪这些对象。 例如,最近分配的对象属于年轻代(Young Generation)。 当一个对象长时间保持活动状态时,它可以被提升为年老代(Older Generation),之后还能进一步提升为永久代(Permanent Generation)

    每一代的对象可占用的内存总量都有其专用上限。 每当一代开始填满时,系统就会执行垃圾收集事件以试图释放内存。 垃圾收集的持续时间取决于它在收集哪一代的对象以及每一代中有多少活动对象

    虽然垃圾收集速度非常快,但它仍然会影响应用程序的性能。通常情况下你不需要控制代码中何时执行垃圾收集事件。 系统有一组用于确定何时执行垃圾收集的标准。 满足条件后,系统将停止执行当前进程并开始垃圾回收。 如果在像动画或音乐播放这样的密集处理循环中发生垃圾收集,则会增加处理时间。 这种增加可能会导致你的应用程序中的代码执行超过建议的16ms阈值。

    为实现高效,流畅的帧渲染,Android建议绘制一帧的时间不要超过16ms。

    此外,你的代码可能会执行各种工作,这些工作会导致垃圾收集事件更频繁地发生,或使其持续时间超过正常范围。 例如,如果在Alpha混合动画的每个帧期间在for循环的最内部分配多个对象,则大量的对象就会污染内存堆。 此时,垃圾收集器会执行多个垃圾收集事件,并可能降低应用程序的性能。

    共享内存

    Android可以跨进程共享RAM页面(Pages)。 它可以通过以下方式实现:

    • 每个应用程序进程都是从名为Zygote的现有进程分叉(fork)出来的。 Zygote进程在系统引导并加载framework代码和资源(例如Activity Themes)时启动。 要启动新的应用程序进程,系统会fork Zygote进程,然后在新进程中加载并运行应用程序的代码。 这种方法允许在所有应用程序进程中共享大多数的为framework代码和资源分配的RAM页面

    • 大多数静态数据都被映射到一个进程中。 该技术允许在进程之间共享数据,并且还允许在需要时将其Page out。这些静态数据包括:Dalvik代码(通过将其置于预链接的.odex文件中进行直接的memory-mapping),app资源(通过将资源表设计为可以mmap的结构并通过对齐APK的zip条目) 和传统的项目元素,如.so文件中的本地代码。
    • 在许多地方,Android使用显式分配的共享内存区域(使用ashmem或gralloc)在进程间共享相同的动态RAM。 例如,Window surface在应用程序和屏幕合成器之间使用共享内存,而游标缓冲区在Content Provider和客户端之间使用共享内存。

    分配和回收应用的内存

    Android为每个进程分配内存的时候,采用了弹性分配方式,也就是刚开始并不会一下分配很多内存给每个进程,而是给每一个进程分配一个“够用”的虚拟内存范围。这个范围是根据每一个设备实际的物理内存大小来决定的,并且可以随着应用后续需求而增加,但最多也只能达到系统为每个应用定义的上限。

    堆的逻辑大小与其使用的物理内存总量并不完全相同。 在检查应用程序的堆时,Android会计算一个名为“比例集大小”(PSS)的值,该值会考虑与其他进程共享的脏页面和干净页面,但其总量与共享该RAM的应用程序数量成正比。 此PSS总量就是系统认为是你的物理内存占用量。

    Android会在内存中尽量长时间的保持应用进程,即使有些进程不再使用了。这样,当用户下次启动应用的时候,只需要恢复当前进程就可以了,不需要重新创建进程,进而减少应用的启动时间。只有当Android系统发现内存不足,而其他为用户提供更紧急服务的进程又需要内存时,Android就会决定关闭某些进程以回收内存。关于这部分内容,稍后再细说。

    限制应用的内存

    为了维护高效的多任务环境,Android为每个应用程序设置了堆大小的硬性限制。 该限制因设备而异,取决于设备总体可用的RAM。 如果应用程序已达到该限制并尝试分配更多内存,则会收到 OutOfMemoryError

    在某些情况下,你可能希望查询系统以准确确定当前设备上可用的堆空间大小,例如,确定可以安全地保留在缓存中的数据量。 你可以通过调用 getMemoryClass() 来查询系统中的这个数字。 此方法返回一个整数,指示应用程序堆可用的兆字节数。

    切换应用

    当用户在应用程序之间切换时,Android会将非前台应用程序(即用户不可见或并没有运行诸如音乐播放等前台服务的进程)缓存到一个最近最少使用缓存(LRU Cache)中。例如,当用户首次启动应用程序时,会为其创建一个进程; 但是当用户离开应用程序时,该进程不会退出。 系统会缓存该进程。 如果用户稍后返回应用程序,系统将重新使用该进程,从而使应用程序切换更快。

    如果你的应用程序具有缓存进程并且它保留了当前不需要的内存,那么即使用户未使用它,你的应用程序也会影响系统的整体性能。 当系统内存不足时,就会从最近最少使用的进程开始,终止LRU Cache中的进程。另外,系统还会综合考虑保留了最多内存的进程,并可能终止它们以释放RAM。

    当系统开始终止LRU Cache中的进程时,它主要是自下而上的。 系统还会考虑哪些进程占用更多内存,因为在它被杀时会为系统提供更多内存增益。 因此在整个LRU列表中消耗的内存越少,保留在列表中并且能够快速恢复的机会就越大。

    Android对Linux系统的内存管理机制进行的优化

    Android对内存的使用方式同样是“尽最大限度的使用”,这一点继承了Linux的优点。只不过有所不同的是,Linux侧重于尽可能多的缓存磁盘数据以降低磁盘IO进而提高系统的数据访问性能,而Android侧重于尽可能多的缓存进程以提高应用启动和切换速度。Linux系统在进程活动停止后就结束该进程,而Android系统则会在内存中尽量长时间的保持应用进程,直到系统需要更多内存为止。这些保留在内存中的进程,通常情况下不会影响系统整体运行速度,反而会在用户再次激活这些进程时,加快进程的启动速度,因为不用重新加载界面资源了,这是Android标榜的特性之一。所以,Android现在不推荐显式的“退出”应用

    那为什么内存少的时候运行大型程序会慢呢,原因是:在内存剩余不多时打开大型程序会触发系统自身的进程调度策略,这是十分消耗系统资源的操作,特别是在一个程序频繁向系统申请内存的时候。这种情况下系统并不会关闭所有打开的进程,而是选择性关闭,频繁的调度自然会拖慢系统。

    Android中的进程管理

    说到Android的内存管理,就不得不提到进程管理,因为进程管理确确切切的影响着系统内存。在了解进程管理之前,我们首先了解一些基础概念。

    当某个应用组件启动且该应用没有运行其他任何组件时,Android 系统会使用单个执行线程为应用启动新的 Linux 进程。默认情况下,同一应用的所有组件在相同的进程和线程(称为“主”线程)中运行。 如果某个应用组件启动且该应用已存在进程(因为存在该应用的其他组件),则该组件会在此进程内启动并使用相同的执行线程。 但是,你也可以安排应用中的其他组件在单独的进程中运行,并为任何进程创建额外的线程。

    Android应用模型的设计思想取自Web 2.0的Mashup概念,是基于组件的应用设计模式。在该模型下,每个应用都由一系列的组件搭建而成,组件通过应用的配置文件描述功能。Android系统依照组件的配置信息,了解各个组件的功能并进行统一调度。这就意味着,来自不同应用的组件可以有机地结合在一起,共同完成任务,各个Android应用,只有明确的组件边界,而不再有明确的进程边界和应用边界。这种设计,也令得开发者无需耗费精力去重新开发一些附属功能,而是可以全身心地投入到核心功能的开发中。这样不但提高了应用开发的效率,也增强了用户体验(比如电子邮件中选择图片作为附件的功能,可以直接调用专门的图片应用的功能,不用自己从头开发)。

    系统不会为每个组件实例创建单独的线程。运行于同一进程的所有组件均在 UI 线程中实例化,并且对每个组件的系统调用均由该线程进行分派。 因此,响应系统回调的方法(例如,报告用户操作的 onKeyDown() 或生命周期回调方法)始终在进程的 UI 线程中运行(四大组件的各个生命周期回调方法都是在UI线程中触发的)。

    进程的生命周期

    Android的一个不寻常的基本特征是应用程序进程的生命周期并非是由应用本身直接控制的。相反,进程的生命周期是由系统决定的,系统会权衡每个进程对用户的相对重要程度,以及系统的可用内存总量来确定。比如说相对于终止一个托管了正在与用户交互的Activity的进程,系统更可能终止一个托管了屏幕上不再可见的Activity的进程,否则这种后果是可怕的。因此,是否终止某个进程取决于该进程中所运行组件的状态。Android会有限清理那些已经不再使用的进程,以保证最小的副作用。

    作为应用开发者,了解各个应用组件(特别是Activity、Service和BroadcastReceiver)如何影响应用进程的生命周期非常重要。不正确的使用这些组件,有可能导致系统在应用执行重要工作时终止进程。

    举个常见的例子, BroadcastReceiver 在其 onReceive() 方法中接收到Intent时启动一个线程,然后从该函数返回。而一旦返回,系统就认为该 BroadcastReceiver 不再处于活动状态,因此也就不再需要其托管进程(除非该进程中还有其他组件处于活动状态)。这样一来,系统就有可能随时终止进程以回收内存,而这也最终会导致运行在进程中的线程被终止。此问题的解决方案通常是从 BroadcastReceiver 中安排一个 JobService ,以便系统知道在该进程中仍有活动的工作。

    为了确定在内存不足时终止哪些进程,Android会根据进程中正在运行的组件以及这些组件的状态,将每个进程放入“重要性层次结构”中。必要时,系统会首先杀死重要性最低的进程,以此类推,以回收系统资源。这就相当于为进程分配了优先级的概念。

    进程优先级

    Android中总共有5个进程优先级(按重要性降序):

    Foreground Process:前台进程(正常不会被杀死)

    用户当前操作所必需的进程。有很多组件能以不同的方式使得其所在进程被判定为前台进程。如果一个进程满足以下任一条件,即视为前台进程:

    • 托管用户正在交互的 Activity(已调用 Activity 的 onResume() 方法)
    • 托管某个 Service,后者绑定到用户正在交互的 Activity
    • 托管正执行一个生命周期回调的 Service(onCreate()、onStart() 或 onDestroy())
    • 托管正执行其 onReceive() 方法的 BroadcastReceiver

    通常,在任意给定时间前台进程都为数不多。只有在内存不足以支持它们同时继续运行这一万不得已的情况下,系统才会终止它们。 此时,设备往往已达到内存分页状态,因此需要终止一些前台进程来确保用户界面正常响应。

    Visible Process:可见进程(正常不会被杀死)

    没有任何前台组件、但仍会影响用户在屏幕上所见内容的进程。杀死这类进程也会明显影响用户体验。 如果一个进程满足以下任一条件,即视为可见进程:

    • 托管不在前台、但仍对用户可见的 Activity(已调用其 onPause() 方法)。例如,启动了一个对话框样式的前台 activity ,此时在其后面仍然可以看到前一个Activity。

      运行时权限对话框就属于此类。
      考虑一下,还有哪种情况会导致只触发onPause而不触发onStop?

    • 托管通过 Service.startForeground() 启动的前台Service。

      Service.startForeground():它要求系统将它视为用户可察觉到的服务,或者基本上对用户是可见的。

    • 托管系统用于某个用户可察觉的特定功能的Service,比如动态壁纸、输入法服务等等。

    可见进程被视为是极其重要的进程,除非为了维持所有前台进程同时运行而必须终止,否则系统不会终止这些进程。如果这类进程被杀死,从用户的角度看,这意味着当前 activity 背后的可见 activity 会被黑屏代替。

    Service Process:服务进程(正常不会被杀死)

    正在运行已使用 startService() 方法启动的服务且不属于上述两个更高类别进程的进程。尽管服务进程与用户所见内容没有直接关联,但是它们通常在执行一些用户关心的操作(例如,后台网络上传或下载数据)。因此,除非内存不足以维持所有前台进程和可见进程同时运行,否则系统会让服务进程保持运行状态。

    已经运行很久(例如30分钟或更久)的Service,有可能被降级,这样一来它们所在的进程就可以被放入Cached LRU列表中。这有助于避免一些长时间运行的Service由于内存泄漏或其他问题而消耗过多的RAM,进而导致系统无法有效使用缓存进程的情况。

    Background / Cached Process:后台进程(可能随时被杀死)

    这类进程一般会持有一个或多个目前对用户不可见的 Activity (已调用 Activity 的 onStop() 方法)。它们不是当前所必须的,因此当其他更高优先级的进程需要内存时,系统可能随时终止它们以回收内存。但如果正确实现了Activity的生命周期,即便系统终止了进程,当用户再次返回应用时也不会影响用户体验:关联Activity在新的进程中被重新创建时可以恢复之前保存的状态。

    在一个正常运行的系统中,缓存进程是内存管理中唯一涉及到的进程:一个运行良好的系统将始终具有多个缓存进程(为了更高效的切换应用),并根据需要定期终止最旧的进程。只有在非常严重(并且不可取)的情况下,系统才会到达这样一个点,此时所有的缓存进程都已被终止,并且必须开始终止服务进程。

    Android系统回收后台进程的参考条件
    LRU算法:自下而上开始终止,先回收最老的进程。越老的进程近期内被用户再次使用的几率越低。杀死的进程越老,对用户体验的影响就越小。
    回收收益:系统总是倾向于杀死一个能回收更多内存的进程,因为在它被杀时会为系统提供更多内存增益,从而可以杀死更少的进程。杀死的进程越少,对用户体验的影响就越小。换句话说,应用进程在整个LRU列表中消耗的内存越少,保留在列表中并且能够快速恢复的机会就越大。

    这类进程会被保存在一个伪LRU列表中,系统会优先杀死处于列表尾部(最老)的进程,以确保包含用户最近查看的 Activity 的进程最后一个被终止。这个LRU列表排序的确切策略是平台的实现细节,但通常情况下,相对于其他类型的进程,系统会优先尝试保留更有用的进程(比如托管用户主应用程序的进程,或者托管用户看到的最后一个Activity的进程,等等)。还有其他一些用于终止进程的策略:对允许的进程数量硬限制,对进程可以持续缓存的时间量的硬限制,等等。

    在一个健康的系统中,只有缓存进程或者空进程会被系统随时终止,如果服务进程,或者更高优先级的可见进程以及前台进程也开始被系统终止(不包括应用本身糟糕的内存使用导致OOM),那就说明系统运行已经处于一个亚健康甚至极不健康的状态,可用内存已经吃紧。

    Empty Process:空进程(可以随时杀死)

    不含任何活跃组件的进程。保留这种进程的的唯一目的是用作缓存(为了更加有效的使用内存而不是完全释放掉),以缩短下次启动应用程序所需的时间,因为启动一个新的进程也是需要代价的。只要有需要,Android会随时杀死这些进程。

    内存管理中对于前台/后台应用的定义,与用于Service限制目的的后台应用定义不同。从Android 8.0开始,出于节省系统资源、优化用户体验、提高电池续航能力的考量,系统进行了前台/后台应用的区分,对于后台service进行了一些限制。在该定义中,如果满足以下任意条件,应用将被视为处于前台:

    • 具有可见 Activity(不管该 Activity 已启动还是已暂停)。
    • 具有前台 Service。
    • 另一个前台应用已关联到该应用(不管是通过绑定到其中一个 Service,还是通过使用其中一个内容提供程序)。 例如,如果另一个应用绑定到该应用的 Service,那么该应用处于前台:
      IME
      壁纸 Service
      通知侦听器
      语音或文本 Service
      如果以上条件均不满足,应用将被视为处于后台。详见后台Service限制

    Android系统如何评定进程的优先级

    根据进程中当前活动组件的重要程度,Android 会将进程评定为它可能达到的最高级别。例如,如果某进程同时托管着 Service 和可见 Activity,则会将此进程评定为可见进程,而不是服务进程。

    此外,一个进程的级别可能会因其他进程对它的依赖而有所提高,即服务于另一进程的进程其级别永远不会低于其所服务的进程。 例如,如果进程 A 中的内容提供程序为进程 B 中的客户端提供服务,或者如果进程 A 中的服务绑定到进程 B 中的组件,则进程 A 始终被视为至少与进程 B 同样重要。

    由于运行服务的进程其级别高于托管后台 Activity 的进程,因此,在 Activity 中启动一个长时间运行的操作时,最好为该操作启动服务,而不是简单地创建工作线程,当操作有可能比 Activity 更加持久时尤要如此。例如,一个文件上传的操作就可以考虑使用服务来完成,这样一来,即使用户退出 Activity,仍可在后台继续执行上传操作。使用服务可以保证,无论 Activity 发生什么情况,该操作至少具备“服务进程”优先级。 同理, BroadcastReceiver 也应使用服务,而不是简单地将耗时冗长的操作放入线程中。

    Home键退出和返回键退出的区别

    Home键退出,程序保留状态为后台进程;而返回键退出,程序保留状态为空进程,空进程更容易被系统回收。Home键其实主要用于进程间切换,返回键则是真正的退出程序。

    从理论上来讲,无论是哪种情况,在没有任何后台工作线程(即便应用处于后台,工作线程仍然可以执行)的前提下,被置于后台的进程都只是保留他们的运行状态,并不会占用CPU资源,所以也不耗电。只有音乐播放软件之类的应用需要在后台运行Service,而Service是需要占用CPU时间的,此时才会耗电。所以说没有带后台服务的应用是不耗电也不占用CPU时间的,没必要关闭,这种设计本身就是Android的优势之一,可以让应用下次启动时更快。然而现实是,很多应用多多少少都会有一些后台工作线程,这可能是开发人员经验不足导致(比如线程未关闭或者循环发送的Handler消息未停止),也可能是为了需求而有意为之,导致整个Android应用的生态环境并不是一片干净。

    作为用户,你需要手动管理内存吗?

    你有“内存使用率过高”恐慌症吗?

    无论是使用桌面操作系统还是移动操作系统,很多人都喜欢随时关注内存,一旦发现内存使用率过高就难受,忍不住的要杀进程以释放内存。这种习惯很大程度上都是源自Windows系统,当然这在Windows下也确实没错。然而很多人在使用Linux系统时仍然有这个习惯,甚至到了Android系统下,也改不掉(尤其是Android手机刚出现的几年),Clean Master等各种清理软件铺天盖地。毫不客气的说,Windows毒害了不少人!当然,这也不能怪Windows,毕竟Windows的普及率太高了,而大部分普通用户(甚至一些计算机相关人员)又不了解Windows和Linux在内存管理方面的差别。

    何时需要清理手机的RAM?

    考虑到许多手机厂商都内置了“清理”功能,那这个东西可能也有些道理。事实上,关闭应用程序以节省内存的做法,仅在少数情况下是值得尝试的 —— 当应用崩溃或无法正常运行时。比如以下情况:

    • 你的微信在启动时需要加载很久
    • 某个应用启动时闪退或者运行过程中闪退
    • 系统响应速度非常缓慢

    这些症状可能非常多样化,甚至原因不明的手机发热也可能是由于某个崩溃的应用造成的。

    管理你的手机RAM:结论

    究竟需不需要手动清空内存?答案是:No!

    清空内存意味着你需要不断重启应用,这需要花费时间和电量,甚至会缩短电池寿命。内存占用高其实并非是一件坏事,甚至是需要的。因为Android是基于Linux内核的操作系统,而Linux的内存哲学是:

    Free memory is wasted memory.

    你只需要在手机明显变慢时采取行动。一般来说,系统的自动RAM管理才是最快最高效的,也是Android标榜的优势之一。关闭应用可能释放一些内存,但却对高效使用内存毫无作用。

    Leave the memory management to Android, and it will leave the fun to you.

    参考资料

    浅谈Linux的内存管理机制
    Processes and Threads Overview
    Process and Application Lifecycle
    Overview of Memory Management
    RAM management on Android: why you shouldn’t clear memory

    展开全文
  • 9. Windows下C语言程序的内存布局(内存模型) 10. 用户模式和内核模式 11. 栈(Stack)是什么?栈溢出又是怎么回事? 12. 一个函数在栈上到底是怎样的? 13. 函数调用惯例(Calling Convention)
  • 会带领大家领略Windows CE操作系统的内存管理机制,从硬件MMU的功能讲起,然后介绍虚拟地址空间布局,虚拟内存分配与释放,物理内存管理等细致入微的话题,也会在不同的操作系统之间进行一些横向纵向对比。...
  • 本文背景:在编程中,很多Windows...本文目的:对Windows内存管理机制了解清楚,有效的利用C++内存函数管理和使用内存。本文内容:本文一共有六节,由于篇幅较多,故按节发表。其他章节请看本人博客的Windows内存管理及
  • 本文背景:在编程中,很多Windows或C++的内存...本文目的:对Windows内存管理机制了解清楚,有效的利用C++内存函数管理和使用内存。本文内容:本文一共有六节,由于篇幅较多,故按节发表。其他章节请看本人博客的Window
  • spark2.x-内存管理机制

    千次阅读 2018-05-18 10:38:47
    管理在jvm内部的spark整体的内存使用,该组件实现了将可用内存按任务划分的策略。在内存内存使用缓存和数据传输)和执行之间分配内存(计算所使用的内存,如shuffles、joins、sorts和aggregations)。 执行内存指...
  • 页目录,页表2.Windows内存管理3.CPU段式内存管理4.CPU页式内存管理 一、基本概念1. 两个内存概念物理内存:人尽皆知,就是插在主板上的内存条。他是固定的,内存条的容量多大,物理内存就有多大(集成显卡系统除外...
  • 本文背景:在编程中,很多Windows...本文目的:对Windows内存管理机制了解清楚,有效的利用C++内存函数管理和使用内存。本文内容:本文一共有六节,由于篇幅较多,故按节发表。其他章节请看本人博客的Windows内存管理及
  • JVM-Java虚拟机自动内存管理机制

    千次阅读 2020-07-04 21:39:06
    那么JAVA中把内存管理完全交给了虚拟机管理,我们还有必要学习JVM吗?答案是要的。学习JVM有利于我们编程时内存优化和上线后出现内存溢出/内存泄漏问题排查。 JVM内存模型 JAVA运行时数据区分为五大区域...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 166,519
精华内容 66,607
关键字:

windows内存管理机制