精华内容
下载资源
问答
  • 2020-09-10 17:06:41

    原因:实现内存隔离,进程A的虚拟地址和进程B的虚拟地址不同,这样就防止了进程A将数据信息写入进程B

    1.         每个进程的4G内存空间只是虚拟内存空间,每次访问内存空间的某个地址,都需要把地址翻译为实际物理内存地址

    2.         所有进程共享同一物理内存,每个进程只把自己目前需要的虚拟内存空间映射并存储到物理内存上。

    3.         进程要知道哪些内存地址上的数据在物理内存上,哪些不在,还有在物理内存上的哪里,需要用页表来记录

    4.         页表的每一个表项分两部分,第一部分记录此页是否在物理内存上,第二部分记录物理内存页的地址(如果在的话)

    5.         当进程访问某个虚拟地址,去看页表,如果发现对应的数据不在物理内存中,则缺页异常

    6.         缺页异常的处理过程,就是把进程需要的数据从磁盘上拷贝到物理内存中,如果内存已经满了,没有空地方了,那就找一个页覆盖,当然如果被覆盖的页曾经被修改过,需要将此页写回磁盘

     

    每个android进程拥有1g内核空间,3g用户空间,大小参数可配置,内核空间共享

    Binder借助了内存映射的方法,在内核空间和接收方用户空间的数据缓存区之间做了一层内存映射,就相当于直接拷贝到了接收方用户空间的数据缓存区,从而减少了一次数据拷贝

     

    参考:https://mubu.com/doc/explore/21079

    https://www.cnblogs.com/dyllove98/archive/2013/06/12/3132940.html

     

    更多相关内容
  • android虚拟内存

    2015-05-09 14:27:46
    1.使用root大师root 2.安装Swapper2,设定大小位置,自动启动 3.设定自动启动,红米->安全中心->授权管理->自动启动应用管理->手工添加->找到Swapper2 4.查看效果cmd-> adb shell -> busybox free 查看Swap Swap: ...
  • 在 Android 的 /data 目录下添加虚拟内存Android 系统上添加虚拟内存已经不是什么新鲜事了, 很多手机都支持, 通常都是将虚拟内存添加到 SD 卡上, 不过由于 SD 卡的的读写速度有速度限制, 再加上如果不是用高速的...

    在 Android 的 /data 目录下添加虚拟内存

    Android 系统上添加虚拟内存已经不是什么新鲜事了, 很多手机都支持, 通常都是将虚拟内存添加到 SD 卡上, 不过由于 SD 卡的的读写速度有速度限制, 再加上如果不是用高速的 SD 卡的话, 将虚拟内存添加到 SD 卡上的效果就不是很明显了, 其实还有一个地方可以添加虚拟内存, 那就是 /data 目录。 为什么把虚拟内存添加到 /data 目录呢, 原因如下:

    1、 /data 目录有足够的的剩余空间容纳虚拟内存

    几乎所有的 Android 手机都支持 App2SD 或者 App2Ext , 将应用安装到 SD 卡之后, /data 目录剩余的空间比较大, 是足以容纳下虚拟内存需要的空间。

    2、 存取速度比 SD 卡要快

    /data 目录使用手机内部的 flash 存储器, 内置存储比外加的 SD 卡品质要好很多, 读写速度也比 SD 卡要快很多。

    鉴于以上的两个优点, 将虚拟内存添加到 /data 目录应该是首选, 以我手上的 Moto Milestone 为例, 由于手机很旧, 只有 256 内存, 如果想运行最新的 CM9 或者 CM10 的话, 内存明显紧张, 好在最新版的 CM10 安装之后将很多系统文件都移动到了 /sd-ext 分区, /data 目录下剩余空间达 160M 多, 因此可以考虑在 /data 目录下添加 64M 的虚拟内存。

    原来在 /sd-ext 分区启用虚拟内存的脚本为:

    #!/system/bin/sh

    # turn swap if it file exists /sd-ext/swap.swp

    if [ -e /sd-ext/swap.swp ];

    then

    busybox swapon /sd-ext/swap.swp

    fi;

    脚本很简单, 检查一下如果 /sd-ext 目录下存在 swap.swp 文件的话, 就将它挂载为虚拟内存。 现在就将 swap.swp 文件复制到 /data 目录下, 并将脚本修改为:

    #!/system/bin/sh

    # turn swap if it file exists /data/swap.swp

    if [ -e /data/swap.swp ];

    then

    busybox swapon /data/swap.swp

    fi;

    运行发现不能挂载虚拟内存, 得到的提示是:

    swapon: /data/swap.swp: Invalid argument

    看来不能按照原来的方式添加虚拟内存, 经过一番搜索, 在 xda 论坛上找到了解决方法, 就是将 swap.swp 文件模拟成一个设备, 然后将这个设备挂载为虚拟内存, 最终挂载虚拟内存的脚本如下:

    #!/system/bin/sh

    # turn swap if it file exists /data/swap.swp

    if [ -e /data/swap.swp ];

    then

    losetup /dev/block/loop0 /data/swap.swp

    busybox swapon /dev/block/loop0

    fi;

    将这个脚本复制到 /data/local/userinit.d 目录或者保存为 /data/local/userinit.sh , 重启手机就可以自动挂载虚拟内存了。

    展开全文
  • 操作系统对内存的管理 没有内存抽象的年代 在早些的操作系统中,并没有引入内存抽象的概念。程序直接访问和操作的都是物理内存。比如当执行如下指令时:mov reg1,1000 这条指令会毫无想象力的将物理地址1000中的...

    操作系统对内存的管理

    没有内存抽象的年代

    在早些的操作系统中,并没有引入内存抽象的概念。程序直接访问和操作的都是物理内存。比如当执行如下指令时:mov reg1,1000

    这条指令会毫无想象力的将物理地址1000中的内容赋值给寄存器。不难想象,这种内存操作方式使得操作系统中存在多进程变得完全不可能,比如MS-DOS,你必须执行完一条指令后才能接着执行下一条。如果是多进程的话,由于直接操作物理内存地址,当一个进程给内存地址1000赋值后,另一个进程也同样给内存地址赋值,那么第二个进程对内存的赋值会覆盖第一个进程所赋的值,这回造成两条进程同时崩溃。

    没有内存抽象对于内存的管理通常非常简单,除去操作系统所用的内存之外,全部给用户程序使用。或是在内存中多留一片区域给驱动程序使用

    无内存抽象存在的问题

    1. 用户程序可以访问任意内存,容易破坏操作系统,造成崩溃

    2. 同时运行多个程序特别困难

    内存抽象:地址空间

    1. 基址寄存器与界限寄存器可以简单的动态重定位,每个内存地址送到内存之前,都会自动加上基址寄存器的内容。

    2. 交换技术把一个进程完全调入内存,使该进程运行一段时间,然后把它存回磁盘。空闲进程主要存在磁盘上,所以当他们不运行时就不会占用内存。

    为什么要有地址空间?

    首先直接把物理地址暴露给进程会带来严重问题

    1. 如果用户程序可以寻址内存的每个字节,就有很大的可能破坏操作系统,造成系统崩溃

    2. 同时运行多个程序十分困难 地址空间创造了一个新的内存抽象,地址空间是一个进程可用于寻址内存的一套地址的集合。每个进程都有一个自己的地址空间,并且这个地址空间独立于其它进程的地址空间。使用基址寄存器和界限器可以实现。

    虚拟内存

    虚拟内存是现代操作系统普遍使用的一种技术。前面所讲的抽象满足了多进程的要求,但很多情况下,现有内存无法满足仅仅一个大进程的内存要求(比如很多游戏,都是10G+的级别)。在早期的操作系统曾使用覆盖(overlays)来解决这个问题,将一个程序分为多个块,基本思想是先将块0加入内存,块0执行完后,将块1加入内存。依次往复,这个解决方案最大的问题是需要程序员去程序进行分块,这是一个费时费力让人痛苦不堪的过程。后来这个解决方案的修正版就是虚拟内存。

    虚拟内存的基本思想是,每个进程有用独立的逻辑地址空间,内存被分为大小相等的多个块,称为(Page).每个页都是一段连续的地址。对于进程来看,逻辑上貌似有很多内存空间,其中一部分对应物理内存上的一块(称为页框,通常页和页框大小相等),还有一些没加载在内存中的对应在硬盘上。

    由上图可以看出,虚拟内存实际上可以比物理内存大。当访问虚拟内存时,会通过MMU(内存管理单元)去匹配对应的物理地址,而如果虚拟内存的页并不存在于物理内存中,会产生缺页中断,从磁盘中取得缺的页放入内存,如果内存已满,还会根据某种算法将磁盘中的页换出。

    虚拟内存和物理内存的匹配是通过页表实现,页表存在MMU中,页表中每个项通常为32位,既4byte,除了存储虚拟地址和页框地址之外,还会存储一些标志位,比如是否缺页,是否修改过,写保护等。可以把MMU想象成一个接收虚拟地址项返回物理地址的方法。

    因为页表中每个条目是4字节,现在的32位操作系统虚拟地址空间会是2的32次方,即使每页分为4K,也需要2的20次方 * 4字节 = 4M的空间,为每个进程建立一个4M的页表并不明智。因此在页表的概念上进行推广,产生二级页表,二级页表每个对应4M的虚拟地址,而一级页表去索引这些二级页表,因此32位的系统需要1024个二级页表,虽然页表条目没有减少,但内存中可以仅仅存放需要使用的二级页表和一级页表,大大减少了内存的使用。

    引入多级页表的原因是避免把全部页表一直存在内存中

    虚拟地址和物理地址匹配规则

    虚拟页号可用做页表的索引,以找到该虚拟页面对应页表项。由页表项可以找到页框号。然后把页框号拼接到偏移量的高位端,以替换虚拟页号,形成送往内存的物理地址。

    页表的目的是把虚拟页面映射为页框,从数学的角度来说,页表是一个函数,它的参数是,虚拟页号,结果是物理页框号。通过这个函数可以把虚拟地址中的虚拟页面域替换为页框域,从而形成物理地址。

    页面置换算法

    地址映射过程中,若在页面中发现所要访问的页面不再内存中,则产生缺页中断。当发生缺页中断时操作系统必须在内存选择一个页面将其移出内存,以便为即将调入的页面让出空间。如果要换出的页面在内存驻留期间已经被修改过,就必须把它写回磁盘以更新该页面在磁盘的副本;如果该页面没有被修改过,那么它在磁盘上的副本已经是最新的,不需要回写。直接用调入的页面覆盖掉被淘汰的页面就可以了。而用来选择淘汰哪一页的规则叫做页面置换算法。

    因为在计算机系统中,读取少量数据硬盘通常需要几毫秒,而内存中仅仅需要几纳秒。一条CPU指令也通常是几纳秒,如果在执行CPU指令时,产生几次缺页中断,那性能可想而知,因此尽量减少从硬盘的读取无疑是大大的提升了性能。而前面知道,物理内存是极其有限的,当虚拟内存所求的页不在物理内存中时,将需要将物理内存中的页替换出去,选择哪些页替换出去就显得尤为重要,如果算法不好将未来需要使用的页替换出去,则以后使用时还需要替换进来,这无疑是降低效率的,让我们来看几种页面替换算法。

    最优页面置换算法(Optimal Page Replacement Algorithm)

    最饥饿页面置换算法是将未来最久不使用的页替换出去,这听起来很简单,但是无法实现。根据页面被访问前所需要的指令数作为标记,根据指令数的由多到少进行置换,这个方法对评价页面置换算法很有用,但它在实际系统中却不能使用,因为无法真正的实现。这种算法可以作为衡量其它算法的基准。

    最近最少使用页面置换算法(Least Recently Used)

    通常在前几条指令中使用频繁的页面很可能在后面几条指令中页频繁使用。LRU算法就是在缺页发生时首先置换最长时间未被使用的页面。优秀但是难以实现。

    最近未使用页面置换算法(Not Recently Used Replacement Algorithm)

    在最近的一个时钟周期内,淘汰一个没有被访问的已修改页面,近似 LRU 算法,NRU 只是更粗略些。

    这种算法给每个页一个标志位,R表示最近被访问过,M表示被修改过。定期对R进行清零。这个算法的思路是首先淘汰那些未被访问过R=0的页,其次是被访问过R=1,未被修改过M=0的页,最后是R=1,M=1的页。

    先进先出的页面置换算法(First-In First-Out Page Replacement Algorithm)

    这种算法的思想是淘汰在内存中最久的页,这种算法的性能接近于随机淘汰。可能抛弃重要的页面,并不好。

    第二次机会页面置换算法(Second Chance Page Replacement Algorithm)

    这种算法是在FIFO的基础上,为了避免置换出经常使用的页,增加一个标志位R,如果最近使用过将R置1,当页将会淘汰时,如果R为1,则不淘汰页,将R置0.而那些R=0的页将被淘汰时,直接淘汰。这种算法避免了经常被使用的页被淘汰。

    时钟替换算法(Clock Page Replacement Algorithm)

    虽然改进型FIFO算法避免置换出常用的页,但由于需要经常移动页,效率并不高。因此在改进型FIFO算法的基础上,将队列首位相连形成一个环路,当缺页中断产生时,从当前位置开始找R=0的页,而所经过的R=1的页被置0,并不需要移动页。

    下表是上面几种算法的简单比较:

    算法描述
    最佳置换算法无法实现,最为测试基准使用
    最近不常使用算法和LRU性能差不多
    先进先出算法有可能会置换出经常使用的页
    改进型先进先出算法和先进先出相比有很大提升
    最久未使用算法性能非常好,但实现起来比较困难
    时钟置换算法非常实用的算法

    分页系统中的设计问题

    1. 在任何分页式系统中,都需要考虑两个主要的问题:虚拟地址到到物理地址的映射必须非常快;如果虚拟地址空间很大,页表也会很大。

    2. 局部分分配策略与全局分配策略,怎样在相互竞争的可运行进程之间分配内存.

      • 局部分配为每个进程分配固定的内存片段,即使有大量的空闲页框存在,工作集的增长也会颠簸。

      • 全局分配在进程间动态地分配页框,分配给各个进程的页框数是动态变化的。给每个进程分配一个最小的页框数使无论多么小的进程都可以运行,再需要更大的内存时去公共的内存池里去取。

      • FIFO LRU 既适用于局部算法,也适用于全局算法。WSClock 工作集更适用于局部算法。

    3. 负载控制 , 即使使用了最优的页面置换算法,最理想的全局分配。当进程组合的工作集超出内存容量时,就可能发生颠簸。这时只能根据进程的特性(IO 密集 or CPU 密集)将进程交换到磁盘上。

    4. 页面大小 的确定不存在全局最优的结果,小页面减少页面内内存浪费,但是小页面,意味着更大的页表,更多的计算转换时间。现在一般的页面大小是 4KB 或 8KB

    5. 地址空间太小,所以分离指令空间,数据空间

    6. 共享页面,在数据空间和指令空间分离的基础上很容易实现程序的共享,linux 采取了 copy on write 的方案,也有数据的共享。

    7. 共享库,多个程序并发,如果有一个程序已经装在了共享库,其他程序就没有必要再进行装载,减少内存浪费。而且共享库并不会一次性的装入内存,而是根据需要以页面为单位进行装载的。共享时不使用绝对地址,使用相对偏移量的代码(位置无关代码 position-independent code)

    8. 共享库是内存映射文件的一种特例,核心思想是进程可以发起一个系统调用,将一个文件映射到其虚拟地址空间的一部分,在多数实现中在映射共享的页面时不会实际读入页面的内容,而是在访问时被每次一页的读入,磁盘文件被当作后背存储。当进程退出或显示的接触文件时,所有被改动的页面会被写入到文件中。

    9. 清除策略,发生缺页中断时有大量的空闲页框,此时的分页系统在最佳状态,有一个分页守护(paging daemon)的后台进程,它在多数时候睡眠,但会被定期唤醒,如果空闲页框过少,分页守护进程通过预定的页面置换算法选择页面换出内存。

    10. 虚拟内存接口,在一些高级系统中,程序员可以对内存映射进行控制,允许控制的原因是为了允许两个或多个进程共享一部分内存。页面共享可以用来实现高性能的消息传递系统。

    Linux内存管理

    内核空间

    页(page)是内核的内存管理的基本单位

    struct page {
     page_flags_t flags;  页标志符
     atomic_t _count;    页引用计数
     atomic_t _mapcount;     页映射计数
     unsigned long private;    私有数据指针
     struct address_space *mapping;    该页所在地址空间描述结构指针,用于内容为文件的页帧
     pgoff_t index;               该页描述结构在地址空间radix树page_tree中的对象索引号即页号
     struct list_head lru;        最近最久未使用struct slab结构指针链表头变量
     void *virtual;               页虚拟地址
    };
    
    • flags:页标志包含是不是脏的,是否被锁定等等,每一位单独表示一种状态,可同时表示出32种不同状态,定义在<linux/page-flags.h>

    • _count:计数值为-1表示未被使用。

    • virtual:页在虚拟内存中的地址,对于不能永久映射到内核空间的内存(比如高端内存),该值为NULL;需要事必须动态映射这些内存。

    尽管处理器的最小可寻址单位通常为字或字节,但内存管理单元(MMU,把虚拟地址转换为物理地址的硬件设备)通常以页为单位处理。内核用struct page结构体表示每个物理页,struct page结构体占40个字节,假定系统物理页大小为4KB,对于4GB物理内存,1M个页面,故所有的页面page结构体共占有内存大小为40MB,相对系统4G,这个代价并不高。

    内核把页划分在不同的区(zone)

    总共3个区,具体如下:

    描述物理内存(MB)
    ZONE_DMADMA使用的页<16
    ZONE_NORMAL可正常寻址的页16 ~896
    ZONE_HIGHMEM动态映射的页>896
    • 执行DMA操作的内存必须从ZONE_DMA区分配

    • 一般内存,既可从ZONE_DMA,也可从ZONE_NORMAL分配,但不能同时从两个区分配;

    用户空间

    用户空间中进程的内存,往往称为进程地址空间

    Linux采用虚拟内存技术。进程的内存空间只是虚拟内存(或者叫作逻辑内存),而程序的运行需要的是实实在在的内存,即物理内存(RAM)。在必要时,操作系统会将程序运行中申请的内存(虚拟内存)映射到RAM,让进程能够使用物理内存。

    地址空间

    每个进程都有一个32位或64位的地址空间,取决于体系结构。 一个进程的地址空间与另一个进程的地址空间即使有相同的内存地址,也彼此互不相干,对于这种共享地址空间的进程称之为线程。一个进程可寻址4GB的虚拟内存(32位地址空间中),但不是所有虚拟地址都有权访问。对于进程可访问的地址空间称为内存区域。每个内存区域都具有对相关进程的可读、可写、可执行属性等相关权限设置。

    内存区域可包含的对象:

    • 代码段(text section): 可执行文件代码

    • 数据段(data section): 可执行文件的已初始化全局变量(静态分配的变量和全局变量)。

    • bss段:程序中未初始化的全局变量,零页映射(页面的信息全部为0值)。

    • 进程用户空间栈的零页映射(进程的内核栈独立存在并由内核维护)

    • 每一个诸如C库或动态连接程序等共享库的代码段、数据段和bss也会被载入进程的地址空间

    • 任何内存映射文件

    • 任何共享内存段

    • 任何匿名的内存映射(比如由malloc()分配的内存)

    这些内存区域不能相互覆盖,每一个进程都有不同的内存片段。

    内存描述符

    内存描述符由mm_struct结构体表示

    struct mm_struct {
     struct vm_area_struct *mmap;
     rb_root_t mm_rb;
     ...
     atomic_t mm_users;
     atomic_t mm_count;
    ​
     struct list_head mmlist;
     ...
    };
    
    • mm_users:代表正在使用该地址的进程数目,当该值为0时mm_count也变为0;

    • mm_count: 代表mm_struct的主引用计数,当该值为0说明没有任何指向该mm_struct结构体的引用,结构体会被撤销。

    • mmap和mm_rb:描述的对象都是相同的

      • mmap以链表形式存放, 利于高效地遍历所有元素

      • mm_rb以红黑树形式存放,适合搜索指定元素

    • mmlist:所有的mm_struct结构体都通过mmlist连接在一个双向链表中,该链表的首元素是init_mm内存描述符,它代表init进程的地址空间。

    在进程的进程描述符(<linux/sched.h>中定义的task_struct结构体)中,mm域记录该进程使用的内存描述符。故current->mm代表当前进程的内存描述符。

    fork()函数 利用copy_mm函数复制父进程的内存描述符,子进程中的mm_struct结构体通过allcote_mm()从高速缓存中分配得到。通常,每个进程都有唯一的mm_struct结构体,即唯一的进程地址空间。

    当子进程与父进程是共享地址空间,可调用clone(),那么不再调用allcote_mm(),而是仅仅是将mm域指向父进程的mm,即 tsk->mm = current->mm。

    相反地,撤销内存是exit_mm()函数,该函数会进行常规的撤销工作,更新一些统计量。

    内核线程

    • 没有进程地址空间,即内核线程对应的进程描述符中mm=NULL

    • 内核线程直接使用前一个进程的内存描述符,仅仅使用地址空间中和内核内存相关的信息

    页表

    应用程序操作的对象时映射到物理内存之上的虚拟内存,而处理器直接操作的是物理内存。故应用程序访问一个虚拟地址时,需要将虚拟地址转换为物理地址,然后处理器才能解析地址访问请求,这个转换工作通过查询页表完成。

    Linux使用三级页表完成地址转换。

    1. 顶级页表:页全局目录(PGD),指向二级页目录;

    2. 二级页表:中间页目录(PMD),指向PTE中的表项;

    3. 最后一级:页表(PTE),指向物理页面。

    多数体系结构,搜索页表工作由硬件完成。每个进程都有自己的页表(线程会共享页表)。为了加快搜索,实现了翻译后缓冲器(TLB),作为将虚拟地址映射到物理地址的硬件缓存。还有写时拷贝方式共享页表,当fork()时,父子进程共享页表,只有当子进程或父进程试图修改特定页表项时,内核才创建该页表项的新拷贝,之后父子进程不再共享该页表项。可见,利用共享页表可以消除fork()操作中页表拷贝所带来的消耗。

    进程与内存

    所有进程都必须占用一定数量的内存,这些内存用来存放从磁盘载入的程序代码,或存放来自用户输入的数据等。内存可以提前静态分配和统一回收,也可以按需动态分配和回收。

    对于普通进程对应的内存空间包含5种不同的数据区:

    • 代码段

    • 数据段

    • BSS段

    • 堆:动态分配的内存段,大小不固定,可动态扩张(malloc等函数分配内存),或动态缩减(free等函数释放);

    • 栈:存放临时创建的局部变量;

    进程内存空间

    Linux采用虚拟内存管理技术,每个进程都有各自独立的进程地址空间(即4G的线性虚拟空间),无法直接访问物理内存。这样起到保护操作系统,并且让用户程序可使用比实际物理内存更大的地址空间。

    • 4G进程地址空间被划分两部分,内核空间和用户空间。用户空间从0到3G,内核空间从3G到4G;

    • 用户进程通常情况只能访问用户空间的虚拟地址,不能访问内核空间虚拟地址。只有用户进程进行系统调用(代表用户进程在内核态执行)等情况可访问到内核空间;

    • 用户空间对应进程,所以当进程切换,用户空间也会跟着变化;

    • 内核空间是由内核负责映射,不会跟着进程变化;内核空间地址有自己对应的页表,用户进程各自有不同额页表。

    内存分配

    进程分配内存,陷入内核态分别由brk和mmap完成,但这两种分配还没有分配真正的物理内存,真正分配在后面会讲。

    • brk: 数据段的最高地址指针_edata往高地址推

      • 当malloc需要分配的内存<M_MMAP_THRESHOLD(默认128k)时,采用brk;

      • brk分配的内存需高地址内存全部释放之后才会释放。(由于是通过推动指针方式)

      • 当最高地址空间的空闲内存大于M_TRIM_THRESHOLD时(默认128k),执行内存紧缩操作;

    • do_mmap:在堆栈中间的文件映射区域找空闲的虚拟内存

      • 当malloc需要分配的内存>M_MMAP_THRESHOLD(默认128k)时,采用do_map();

      • mmap分配的内存可以单独释放

    物理内存

    • 物理内存只有进程真正去访问虚拟地址,发生缺页中断时,才分配实际的物理页面,建立物理内存和虚拟内存的映射关系。

    • 应用程序操作的是虚拟内存;而处理器直接操作的却是物理内存。当应用程序访问虚拟地址,必须将虚拟地址转化为物理地址,处理器才能解析地址访问请求。

    • 物理内存是通过分页机制实现的

    • 物理页在系统中由也结构struct page描述,所有的page都存储在数组mem_map[]中,可通过该数组找到系统中的每一页。

    虚拟内存 转化为 真实物理内存:

    • 虚拟进程空间:通过查询进程页表,获取实际物理内存地址;

    • 虚拟内核空间:通过查询内核页表,获取实际物理内存地址;

    • 物理内存映射区:物理内存映射区与实际物理去偏移量仅PAGE_OFFSET,通过通过virt_to_phys()转化;

    虚拟内存与真实物理内存映射关系:

    其中物理地址空间中除了896M(ZONE_DMA + ZONE_NORMAL)的区域是绝对的物理连续,其他内存都不是物理内存连续。在虚拟内核地址空间中的安全保护区域的指针都是非法的,用于保证指针非法越界类的操作,vm_struct是连续的虚拟内核空间,对应的物理页面可以不连续,地址范围(3G + 896M + 8M) ~ 4G;另外在虚拟用户空间中 vm_area_struct同样也是一块连续的虚拟进程空间,地址空间范围0~3G。

    碎片问题

    • 外部碎片:未被分配的内存,由于太多零碎的不连续小内存,无法满足当前较大内存的申请要求;

      • 原因:频繁的分配与回收物理页导致大量的小块内存夹杂在已分配页面中间;

      • 解决方案:伙伴算法有所改善

    • 内部碎片:已经分配的内存,却不能被利用的内存空间;

      • 缘由:所有内存分配必须起始可被4、8或16(体系结构决定)整除的地址或者MMU分页机制限制;

      • 解决方案:slab分配器有所改善

      • 实例:请求一个11Byte的内存块,系统可能会分配12Byte、16Byte等稍大一些的字节,这些多余空间就产生碎片

    Android进程内存管理

    进程的地址空间

    在32位操作系统中,进程的地址空间为0到4GB,

    示意图如下:

    这里主要说明一下Stack和Heap:

    Stack空间(进栈和出栈)由操作系统控制,其中主要存储函数地址、函数参数、局部变量等等,所以Stack空间不需要很大,一般为几MB大小。

    Heap空间的使用由程序员控制,程序员可以使用malloc、new、free、delete等函数调用来操作这片地址空间。Heap为程序完成各种复杂任务提供内存空间,所以空间比较大,一般为几百MB到几GB。正是因为Heap空间由程序员管理,所以容易出现使用不当导致严重问题。

    进程内存空间和RAM之间的关系

    进程的内存空间只是虚拟内存(或者叫作逻辑内存),而程序的运行需要的是实实在在的内存,即物理内存(RAM)。在必要时,操作系统会将程序运行中申请的内存(虚拟内存)映射到RAM,让进程能够使用物理内存。

    RAM作为进程运行不可或缺的资源,对系统性能和稳定性有着决定性影响。另外,RAM的一部分被操作系统留作他用,比如显存等等,内存映射和显存等都是由操作系统控制,我们也不必过多地关注它,进程所操作的空间都是虚拟地址空间,无法直接操作RAM。

    示意图如下:

    Android中的进程

    Native进程:采用C/C++实现,不包含Dalvik实例的Linux进程,/system/bin/目录下面的程序文件运行后都是以native进程形式存在的。上图 /system/bin/surfaceflinger、/system/bin/rild、procrank等就是Native进程。

    Java进程:实例化了Dalvik虚拟机实例的Linux进程,进程的入口main函数为Java函数。Dalvik虚拟机实例的宿主进程是fork()系统调用创建的Linux进程,所以每一个Android上的Java进程实际上就是一个Linux进程,只是进程中多了一个Dalvik虚拟机实例。因此,Java进程的内存分配比Native进程复杂。下图,Android系统中的应用程序基本都是Java进程,如桌面、电话、联系人、状态栏等等。

    Android中进程的堆内存

    进程空间中的Heap空间是我们需要重点关注的。Heap空间完全由程序员控制,我们使用的C Malloc、C++ new和Java new所申请的空间都是Heap空间, C/C++申请的内存空间在Native Heap中,而Java申请的内存空间则在Dalvik Heap中。

    Android的 Java程序为什么容易出现OOM

    这个是因为Android系统对Dalvik的VM Heapsize作了硬性限制,当java进程申请的Java空间超过阈值时,就会抛出OOM异常(这个阈值可以是48M、24M、16M等,视机型而定),可以通过adb shell getprop | grep dalvik.vm.heapgrowthlimit查看此值。

    也就是说,程序发生OMM并不表示RAM不足,而是因为程序申请的Java Heap对象超过了Dalvik VM Heap Growth Limit。也就是说,在RAM充足的情况下,也可能发生OOM。

    这样的设计似乎有些不合理,但是Google为什么这样做呢?这样设计的目的是为了让Android系统能同时让比较多的进程常驻内存,这样程序启动时就不用每次都重新加载到内存,能够给用户更快的响应。迫使每个应用程序使用较小的内存,移动设备非常有限的RAM就能使比较多的App常驻其中。但是有一些大型应用程序是无法忍受VM Heap Growth Limit的限制的。

    Android如何应对RAM不足

    Java程序发生OMM并不是表示RAM不足,如果RAM真的不足,会发生什么呢?这时Android的Memory Killer会起作用,当RAM所剩不多时,Memory Killer会杀死一些优先级比较低的进程来释放物理内存,让高优先级程序得到更多的内存。

    应用程序如何绕过DalvikVM Heap Size的限制

    对于一些大型的应用程序(比如游戏),内存使用会比较多,很容易超超出VM Heapsize的限制,这时怎么保证程序不会因为OOM而崩溃呢?

    创建子进程

    创建一个新的进程,那么我们就可以把一些对象分配到新进程的Heap上了,从而达到一个应用程序使用更多的内存的目的,当然,创建子进程会增加系统开销,而且并不是所有应用程序都适合这样做,视需求而定。

    创建子进程的方法:使用android:process标签

    使用JNI在Native Heap上申请空间(推荐使用)

    Native Heap的增长并不受Dalvik VM heapsize的限制,它的Native Heap Size已经远远超过了Dalvik Heap Size的限制。

    只要RAM有剩余空间,程序员可以一直在Native Heap上申请空间,当然如果RAM快耗尽,Memory Killer会杀进程释放RAM。大家使用一些软件时,有时候会闪退,就可能是软件在Native层申请了比较多的内存导致的。比如,我就碰到过UC Web在浏览内容比较多的网页时闪退,原因就是其Native Heap增长到比较大的值,占用了大量的RAM,被Memory Killer杀掉了。

    使用显存(操作系统预留RAM的一部分作为显存)

    使用OpenGL Textures等API,Texture Memory不受Dalvik VM Heapsize限制。再比如Android中的GraphicBufferAllocator申请的内存就是显存。

    Bitmap分配在Native Heap还是Dalvik Heap上?

    一种流行的观点是这样的:

    Bitmap是JNI层创建的,所以它应该是分配到Native Heap上,并且为了解释Bitmap容易导致OOM,提出了这样的观点:native heap size + dalvik heapsize <= dalvik vm heapsize。但是Native Heap Size远远超过Dalvik VM Heap Size,所以,事实证明以上观点是不正确的。

    正确的观点:

    大家都知道,过多地创建Bitmap会导致OOM异常,且Native Heap Size不受Dalvik限制,所以可以得出结论:

    Bitmap只能是分配在Dalvik Heap上的,因为只有这样才能解释Bitmap容易导致OOM。

    Android内存管理机制

    从操作系统的角度来说,内存就是一块数据存储区域,是可被操作系统调度的资源。在多任务(进程)的OS中,内存管理尤为重要,OS需要为每一个进程合理的分配内存资源。所以可以从OS对内存和回收两方面来理解内存管理机制。

    • 分配机制:为每一个任务(进程)分配一个合理大小的内存块,保证每一个进程能够正常的运行,同时确保进程不会占用太多的内存。
    • 回收机制:当系统内存不足的时候,需要有一个合理的回收再分配机制,以保证新的进程可以正常运行。回收时杀死那些正在占用内存的进程,OS需要提供一个合理的杀死进程机制。

    同样作为一个多任务的操作系统,Android系统对内存管理有有一套自己的方法,手机上的内存资源比PC更少,需要更加谨慎的管理内存。理解Android的内存分配机制有助于我们写出更高效的代码,提高应用的性能。

    下面分别从**分配 回收 **两方面来描述Android的内存管理机制:

    分配机制

    Android为每个进程分配内存时,采用弹性的分配方式,即刚开始并不会给应用分配很多的内存,而是给每一个进程分配一个“够用”的内存大小。这个大小值是根据每一个设备的实际的物理内存大小来决定的。随着应用的运行和使用,Android会为进程分配一些额外的内存大小。但是分配的大小是有限度的,系统不可能为每一个应用分配无限大小的内存。

    总之,Android系统需要最大限度的让更多的进程存活在内存中,以保证用户再次打开应用时减少应用的启动时间,提高用户体验。

    回收机制

    Android对内存的使用方式是“尽最大限度的使用”,只有当内存水足的时候,才会杀死其它进程来回收足够的内存。但Android系统否可能随便的杀死一个进程,它也有一个机制杀死进程来回收内存。

    Android杀死进程有两个参考条件:

    1. 回收收益

    当Android系统开始杀死LRU缓存中的进程时,系统会判断每个进程杀死后带来的回收收益。因为Android总是倾向于杀死一个能回收更多内存的进程,从而可以杀死更少的进程,来获取更多的内存。杀死的进程越少,对用户体验的影响就越小。

    2. 进程优先级

    下面将从 Application Framework 和 Linux kernel 两个层次分析 Android 操作系统的资源管理机制。

    Android 之所以采用特殊的资源管理机制,原因在于其设计之初就是面向移动终端,所有可用的内存仅限于系统 RAM,必须针对这种限制设计相应的优化方案。当 Android 应用程序退出时,并不清理其所占用的内存,Linux 内核进程也相应的继续存在,所谓“退出但不关闭”。从而使得用户调用程序时能够在第一时间得到响应。当系统内存不足时,系统将激活内存回收过程。为了不因内存回收影响用户体验(如杀死当前的活动进程),Android 基于进程中运行的组件及其状态规定了默认的五个回收优先级:

    Android为每一个进程分配了优先组的概念,优先组越低的进程,被杀死的概率就越大。根据进程的重要性,划分为5级:

    1)前台进程(Foreground process)

    用户当前操作所必需的进程。通常在任意给定时间前台进程都为数不多。只有在内存不足以支持它们同时继续运行这一万不得已的情况下,系统才会终止它们。

    2)可见进程(Visible process)

    没有任何前台组件、但仍会影响用户在屏幕上所见内容的进程。可见进程被视为是极其重要的进程,除非为了维持所有前台进程同时运行而必须终止,否则系统不会终止这些进程。

    3)服务进程(Service process)

    尽管服务进程与用户所见内容没有直接关联,但是它们通常在执行一些用户关心的操作(例如,在后台播放音乐或从网络下载数据)。因此,除非内存不足以维持所有前台进程和可见进程同时运行,否则系统会让服务进程保持运行状态。

    4)后台进程(Background process)

    后台进程对用户体验没有直接影响,系统可能随时终止它们,以回收内存供前台进程、可见进程或服务进程使用。 通常会有很多后台进程在运行,因此它们会保存在 LRU 列表中,以确保包含用户最近查看的 Activity 的进程最后一个被终止。如果某个 Activity 正确实现了生命周期方法,并保存了其当前状态,则终止其进程不会对用户体验产生明显影响,因为当用户导航回该 Activity 时,Activity 会恢复其所有可见状态。

    5)空进程(Empty process)

    不含任何活动应用组件的进程。保留这种进程的的唯一目的是用作缓存,以缩短下次在其中运行组件所需的启动时间。 为使总体系统资源在进程缓存和底层内核缓存之间保持平衡,系统往往会终止这些进程。

    通常,前面三种进程不会被杀死。

    ActivityManagerService 集中管理所有进程的内存资源分配。所有进程需要申请或释放内存之前必须调用 ActivityManagerService 对象,获得其“许可”之后才能进行下一步操作,或者 ActivityManagerService 将直接“代劳”。ActivityManagerService类中涉及到内存回收的几个重要的成员方法如下:trimApplications()、updateOomAdjLocked()、activityIdleInternal() 。这几个成员方法主要负责 Android 默认的内存回收机制,若 Linux 内核中的内存回收机制没有被禁用,则跳过默认回收。

    默认回收过程

    Android 操作系统中的内存回收可分为两个层次,即默认内存回收与Linux内核级内存回收,所有代码可参见 ActivityManagerService.java。

    回收动作入口:activityIdleInternal()

    Android 系统中内存回收的触发点大致可分为三种情况。第一,用户程序调用 StartActivity(), 使当前活动的 Activity 被覆盖;第二,用户按 back 键,退出当前应用程序;第三,启动一个新的应用程序。这些能够触发内存回收的事件最终调用的函数接口就是 activityIdleInternal()。当 ActivityManagerService 接收到异步消息 IDLE_TIMEOUT_MSG 或者 IDLE_NOW_MSG 时,activityIdleInternal() 将会被调用。



    作者:Mr槑
    链接:https://www.jianshu.com/p/2b11639905ec
    来源:简书
    简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

    展开全文
  • Android内存管理基础

    千次阅读 2021-11-09 20:49:18
    内存管理概览:https://developer.android.com/topic/performance/memory-overview 内存分配:https://developer.android.com/topic/performance/memory-management Android Studio Profiler:...

    一、内存管理概览

    Android 运行时 (ART) 和 Dalvik 虚拟机使用分页内存映射来管理内存。

    1.1 内存类型

    Android 设备包含三种不同类型的内存:RAM、zRAM 和存储器。请注意,CPU 和 GPU 访问同一个 RAM。
    memory-types.svg

    • RAM(Random Access Memory,随机存取存储器) 是最快的内存类型,但其大小通常有限。
    • zRAM 是用于交换空间的 RAM 分区。所有数据在放入 zRAM 时都会进行压缩,然后在从 zRAM 向外复制时进行解压缩。这部分 RAM 会随着页面进出 zRAM 而增大或缩小。设备制造商可以设置 zRAM 大小上限。
    • 存储器中包含所有持久性数据(例如文件系统等),以及为所有应用、库和平台添加的对象代码。在 Android 上,存储器不像在其他 Linux 实现上那样用于交换空间,因为频繁写入会导致这种内存出现损坏,并缩短存储媒介的使用寿命。

    1.2 物理内存 虚拟内存

    1.2.1 物理内存

    物理内存是指真实存在的插在主板内存槽上的内存条的容量,是Android系统中所有进程共享的存储空间;

    1.2.2 虚拟内存

    当进程申请内存时得到的是虚拟内存,虚拟内存是对物理内存的抽象;每个进程操作的是自己的虚拟内存,并由CPU负责虚拟内存地址与实际物理地址的映射。
    500px-Virtual_memory.svg.png

    需要注意的是,假如进程没有使用这些虚拟内存,那么不会建立虚拟地址与物理内存的映射。例如:

    void main()
    {
        for (int i= 0; i < 512; i++)
        {
            malloc(1024 * 1024);
        }
    }
    // 结果分析:malloc只申请了内存并未使用,因此进程的虚拟内存大小VSZ是539M,而物理内存RSS只有3M。
    

    进程在真正使用内存时,虚拟内存与物理内存才建立映射关系,例如:

    vector<void*> mems;
    void main()
    {
        for (int i= 0; i < 512; i++)
        {
            void *p = malloc(1024 * 1024);
            memset(p, 0, 1024 * 1024);
            mems.push_back(p);
        }
    }
    // 结果分析:用memset对申请的内存执行了写入操作,因此进程的虚拟内存大小VSZ是539M,而物理内存RSS增加到了527M。
    

    1.2.3 虚拟内存的好处

    (1)使应用程序不必管理共享内存空间;由于所有进程共享物理内存,所以物理内存常常是不连续的,而对应用程序来说,虚拟内存的内存地址是连续的,由CPU负责虚拟内存地址的映射;
    (2)能够在进程之间共享库使用的内存;详见1.2.4;
    (3)由于内存隔离而提高了安全性;
    (4)通过使用分页或分段技术,可使用比物理内存大小更多的内存。

    1.2.4 共享库内存

    共享库所占内存:同一个共享库的代码在物理内存中只会存在一份,这块内存会映射到不同进程的虚拟内存中,对各个进程来说,就像是自己私有的内存一样,而对于系统来说,则是节省了内存的资源。

    Android系统实现跨进程共享RAM页面的方式有:

    • 每个应用进程都从一个名为 Zygote 的现有进程分叉。系统启动并加载通用框架代码和资源(如 Activity 主题背景)时,Zygote 进程随之启动。为启动新的应用进程,系统会分叉 Zygote 进程,然后在新进程中加载并运行应用代码。这种方法使为框架代码和资源分配的大多数 RAM 页面可在所有应用进程之间共享。
    • 大多数静态数据会内存映射到一个进程中。这种方法使得数据不仅可以在进程之间共享,还可以在需要时换出。静态数据示例包括:Dalvik 代码(通过将其放入预先链接的 .odex 文件中进行直接内存映射)、应用资源(通过将资源表格设计为可内存映射的结构以及通过对齐 APK 的 zip 条目)和传统项目元素(如 .so 文件中的原生代码)。
    • 在很多地方,Android 使用明确分配的共享内存区域(通过 ashmem 或 gralloc)在进程间共享同一动态 RAM。例如,窗口 surface 使用在应用和屏幕合成器之间共享的内存,而光标缓冲区则使用在内容提供器和客户端之间共享的内存。

    1.3 VSS RSS PSS USS区别

    一个进程的内存信息可以用VSS RSS PSS USS来表示,含义如下:
    VSS(Virtual Set Size):虚拟内存大小(ps命令用VSZ表示);
    RSS(Resident Set Size):常驻内存大小,应用使用的共享和非共享页面的数量;
    PSS(Proportional Set Size):按比例分摊的内存大小,应用使用的非共享内存加上共享内存的均匀分摊数量(例如,如果三个进程共享 3MB,则每个进程的 PSS 为 1MB);
    USS(Unique Set Size):进程独占用的物理内存(不包含共享库占用的内存);
    20200414230206723.png

    举例:已知:
    (1)共享库libTest.so所占内存共100M,被进程A和进程B共同占用;
    (2)进程A申请了100M内存,但是实际使用了60M内存;
    则,进程A的VSS=100M+100M=200M; RSS=60M+100M=150M; PSS=60M+100/2=110M; USS=60M;
    

    所以,如果想要知道所有进程使用了多少内存,那么可以使用PSS 或 RSS。计算 PSS 需要花很长时间,因为系统需要确定共享的页面以及共享页面的进程数量。RSS 不区分共享和非共享页面(因此计算起来更快),更适合跟踪内存分配量的变化。

    1.4 Android系统的页面置换

    RAM分为多个“页面”。通常,每个页面为 4KB 的内存。

    • 干净页:存储器中未经修改的文件副本;
    • 脏页:存储器中经过修改的文件副本。

    Android系统上,不像在其他 Linux 实现上那样使用ROM存储器用于交换空间,因为频繁写入会导致ROM出现损坏,并缩短存储媒介的使用寿命。
    内核交换守护进程 (kswapd) 是 Linux 内核的一部分,用于将已使用内存转换为可用内存。当设备上的可用内存不足时,该守护进程将变为活动状态。Linux 内核设有可用内存上下限阈值。当可用内存降至下限阈值以下时,kswapd 开始回收内存。当可用内存达到上限阈值时,kswapd 停止回收内存。
    Android系统使用zRAM用于交换空间,对于私有脏页可由 kswapd 移动到zRAM/在zRAM中进行压缩以增加可用内存,在应用程序需要使用这部分内存时,再从zRAM中取出/在zRAM中解压。详见:内存页面置换策略

    二、内存分析方法

    2.1 adb常用内存分析命令

    2.1.1 ps命令

    ps命令可以列出Android系统中当前所有进程的信息:

    >adb shell ps
    USER           PID  PPID     VSZ    RSS WCHAN            ADDR S NAME           
    root             1     0   60588   1360 0                   0 S init
    root             2     0   60588   1360 0                   0 S [kthreadd]
    root           144     2       0      0 0                   0 S [kswapd0]
    root          1034     1 4286332  22916 0                   0 S zygote64
    root          1035     1 1616152  25532 0                   0 S zygote
    system         570     1   11356   1196 0                   0 S servicemanager
    system        1655   958 4786828 277192 0                   0 S system_server
    u0_a1166      9742  1035 1716624     20 0                   0 T com.bc.sample
    root           727     1    9968   2204 0                   0 S lmkd
    
    >adb shell ps | grep com.bc.sample
    u0_a1166      9742  1035 1716624     20 0                   0 T com.bc.sample
    

    含义解释:
    USER:进程所属用户;
    PID:进程ID;
    PPID:父进程ID;
    VSZ:Virtual Memory Size虚拟内存占用大小,单位:kb
    RSS:Resident Set Size物理内存占用大小,单位:kb
    NAME:进程名;

    2.1.2 dumpsys meminfo

    dumpsys meminfo命令可以根据进程名(一般是包名)获取进程当前的内存使用情况:

    #命令行输入
    >adb shell dumpsys meminfo com.bc.sample
    Applications Memory Usage (in Kilobytes):
    Uptime: 654784890 Realtime: 2062282674
    
    ** MEMINFO in pid 13705 [com.bc.sample] **
                       Pss  Private  Private  SwapPss     Heap     Heap     Heap
                     Total    Dirty    Clean    Dirty     Size    Alloc     Free
                    ------   ------   ------   ------   ------   ------   ------
      Native Heap     3445     3396        0       50     7680     6140     1539
      Dalvik Heap      667      612        0       68     2671     1135     1536
     Dalvik Other      360      348        0        0                           
            Stack       48       48        0        0                           
           Ashmem        2        0        0        0                           
          Gfx dev     1664     1576       88        0                           
        Other dev       12        0       12        0                           
         .so mmap     1528      180        0       31                           
        .apk mmap      166        0       28        0                           
        .ttf mmap       58        0        0        0                           
        .dex mmap     3834        4     1744        0                           
        .oat mmap      102        0        0        0                           
        .art mmap     3368     3036        4        7                           
       Other mmap       13        4        0        0                           
       EGL mtrack    18496    18496        0        0                           
        GL mtrack    14964    14964        0        0                           
          Unknown      473      464        0        6                           
            TOTAL    49362    43128     1876      162    10351     7275     3075 
    
     App Summary
                           Pss(KB)
                            ------
               Java Heap:     3652
             Native Heap:     3396
                    Code:     1956
                   Stack:       48
                Graphics:    35124
           Private Other:      828
                  System:     4358 
                   TOTAL:    49362       TOTAL SWAP PSS:      162
    
     Objects
                   Views:       22         ViewRootImpl:        2
             AppContexts:        3           Activities:        1
                  Assets:        3        AssetManagers:        4
           Local Binders:       15        Proxy Binders:       21
           Parcel memory:        3         Parcel count:       13
        Death Recipients:        2      OpenSSL Sockets:        0
                WebViews:        0 
    
     Dalvik
             isLargeHeap:    false 
    
     SQL
             MEMORY_USED:        0
      PAGECACHE_OVERFLOW:        0          MALLOC_SIZE:        0
    

    2.1.3 adb shell cat /proc/meminfo

    读取/proc/meminfo文件,可以获取系统整体内存使用情况:

    >adb shell cat /proc/meminfo
    MemTotal:        5826364 kB   //内存总大小
    MemFree:           85532 kB   //空闲内存总大小
    MemAvailable:    3540368 kB
    Buffers:          127840 kB
    Cached:          3373548 kB
    SwapCached:        10952 kB   
    Active:          2928196 kB
    Inactive:        1346956 kB
    Active(anon):     427512 kB
    Inactive(anon):   432012 kB
    Active(file):    2500684 kB
    Inactive(file):   914944 kB
    Unevictable:       81280 kB
    Mlocked:           81280 kB
    SwapTotal:       2097148 kB   //交换空间总大小
    SwapFree:        1449580 kB   //交换空间空闲大小
    

    2.1.4 procrank

    procrank命令可以获取系统中所有进程的VSS、RSS、PSS、USS的大小:

    >adb shell procrank
    PID Vss      Rss     Pss     Uss    cmdline
    380 2195880K 210712K 111133K 67400K system_server
    146 1561656K 63080  47628K  42036K zygote
    868 1587984K 69500K  25414K  18480K com.android.launcher
    145 2122596K 73552K  23428K  10972K zygote64
    

    2.2 Android Studio Profiler

    Android studio自带的profiler工具可以选择MEMORY查看当前内存使用情况,如下所示:
    memory-profiler-callouts_2x.png
    其中列出了所选进程的内存使用情况,这里的java heap、native heap都是指虚拟内存大小:
    memory-profiler-counts_2x.png

    2.3 代码获取

    2.3.1 手机总内存情况MemoryInfo

    MemoryInfo内包含的内存信息如下:

    public static class MemoryInfo implements Parcelable {
    
        public long availMem;
        public long totalMem;
        public long threshold;
        public boolean lowMemory;
    }
    

    获取MemoryInfo的方式如下:

    // Activity中已经实现了获取activityManager,自定义类中通过这种方式获取ActivityManager:
    ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
    ActivityManager.MemoryInfo memoryInfo = new ActivityManager.MemoryInfo();
    am.getMemoryInfo(memoryInfo);
    
    Log.v(TAG,"手机总内存:" + memoryInfo.totalMem);
    Log.v(TAG,"手机当前可用物理内存:" + memoryInfo.availMem);
    Log.v(TAG,"Android系统认为低内存状态的阈值:" + memoryInfo.threshold);
    

    2.3.2 读取/proc/meminfo文件

    读取/proc/meminfo文件,同样可获取系统总内存情况,可获取的信息见2.1.3;代码如下:

    public static long getRamSwapFree() {
        // // 系统内存信息文件
        String str1 = "/proc/meminfo"; 
        String str2;
        String[] arrayOfString;
        long swapFree = 0;
        InputStreamReader localFileReader = null;
        BufferedReader localBufferedReader = null;
        try {
            localFileReader = new InputStreamReader(new FileInputStream(str1), Charset.forName("utf-8"));
            localBufferedReader = new BufferedReader(localFileReader, 8192);
            while ((str2 = localBufferedReader.readLine()) != null) {
                arrayOfString = str2.split("\s+");
                if (arrayOfString.length >= 2) {
                    String memType = arrayOfString[0];
                    // 下面是个例子,类似地可获取其他更多信息
                    if (TextUtils.equals(arrayOfString[0], "SwapFree:")) {
                        swapFree = Integer.parseInt(arrayOfString[1]);
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (null != localBufferedReader) {
                try {
                    localBufferedReader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
    
            if (null != localFileReader) {
                try {
                    localFileReader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return swapFree / (1024);
    }
    

    2.3.3 获取当前进程PSS物理内存

    Debug.MemoryInfo memoryInfo = new Debug.MemoryInfo();
    Debug.getMemoryInfo(memoryInfo);
    Log.v(TAG,"总PSS:"+memoryInfo.getTotalPss());
    Log.v(TAG,"Java PSS:" + memoryInfo.dalvikPss);
    Log.v(TAG,"Native PSS:" + memoryInfo.nativePss);
    Log.v(TAG,"总RSS:"+memoryInfo.getTotalRss());
    
    // 安卓6.0以上还可以使用如下方式:
    Log.v(TAG,"安卓6.0以上,total-pss:"+memoryInfo.getMemoryStat("summary.total-pss"));
    Log.v(TAG,"安卓6.0以上,java-heap:"+memoryInfo.getMemoryStat("summary.java-heap"));
    Log.v(TAG,"安卓6.0以上,native-heap:"+memoryInfo.getMemoryStat("summary.native-heap"));
    Log.v(TAG,"安卓6.0以上,code:"+memoryInfo.getMemoryStat("summary.code"));
    Log.v(TAG,"安卓6.0以上,graphics:"+memoryInfo.getMemoryStat("summary.graphics"));
    

    2.3.4 获取当前进程VSS虚拟内存

    (1)获取java堆VSS

    long javaMaxHeapSize = Runtime.getRuntime().maxMemory();
    long javaTotalHeapSize = Runtime.getRuntime().totalMemory();
    long javaFreeHeapSize = Runtime.getRuntime().freeMemory();
    Log.v(TAG,"java堆最大内存大小:"+javaMaxHeapSize);
    Log.v(TAG,"当前java堆内存大小:"+javaTotalHeapSize);
    Log.v(TAG,"当前java堆空闲内存大小:"+javaFreeHeapSize);
    
    • maxMemory是虚拟机启动过程中就确定的,默认值根据RAM大小设定(192M (2G RAM)、192M (4G RAM)、256M (6G RAM),但是也和具体的机型配置相关),如果开启了largeHeap就是512M)。
    • totalMemory是当前堆大小,最多能增加到maxMemory。
      (2)获取native堆VSS
    long nativeHeap = Debug.getNativeHeapSize();
    long nativeAllocHeap = Debug.getNativeHeapAllocatedSize();
    long nativeFreeSize = Debug.getNativeHeapFreeSize();
    Log.v(TAG,"当前native堆内存大小:"+nativeHeap);
    Log.v(TAG,"当前native堆已分配内存大小:"+nativeAllocHeap);
    Log.v(TAG,"当前native堆空闲内存大小:"+nativeFreeSize);
    
    • native heap的增长并不受dalvik vm heapsize的限制,只要虚拟内存有剩余空间,应用程序可以一直在native heap上申请空间,见参考
      ps:native层虚拟内存或物理内存不足时,都会发生OOM。

    三、低内存状态

    3.1 LMK进程

    很多时候,kswapd 不能为系统释放足够的内存。在这种情况下,系统会使用 onTrimMemory() 通知应用内存不足,应该减少其分配量。如果这还不够,内核会开始终止进程以释放内存。它会使用低内存终止守护进程 (LMK) 来执行此操作。

    LMK 使用一个名为 oom_adj_score 的“内存不足”分值来确定正在运行的进程的优先级,以此决定要终止的进程。最高得分的进程最先被终止。后台应用最先被终止,系统进程最后被终止。下表列出了从高到低的 LMK 评分类别。评分最高的类别,即第一行中的项目将最先被终止(设备制造商可以更改 LMK 的行为。):
    lmk-process-order.svg
    以下是上表中各种类别的说明:

    • 后台应用:之前运行过且当前不处于活动状态的应用。LMK 将首先从具有最高 oom_adj_score 的应用开始终止后台应用。
    • 上一个应用:最近用过的后台应用。上一个应用比后台应用具有更高的优先级(得分更低),因为相比某个后台应用,用户更有可能切换到上一个应用。
    • 主屏幕应用:这是启动器应用。终止该应用会使壁纸消失。
    • 服务:服务由应用启动,可能包括同步或上传到云端。
    • 可觉察的应用:用户可通过某种方式察觉到的非前台应用,例如运行一个显示小界面的搜索进程或听音乐。
    • 前台应用:当前正在使用的应用。终止前台应用看起来就像是应用崩溃了,可能会向用户提示设备出了问题。
    • 持久性(服务):这些是设备的核心服务,例如电话和 WLAN。
    • 系统:系统进程。这些进程被终止后,手机可能看起来即将重新启动。
    • 原生:系统使用的极低级别的进程(例如,kswapd)。

    3.2 onTrimMemory()

    上面介绍过,当内存不足时,系统会回调onTrimMemory()来通知应用程序内存不足,onTrimMemory()的定义如下:

    public interface ComponentCallbacks2 extends ComponentCallbacks {
        void onTrimMemory(@TrimMemoryLevel int level);
    }
    

    对于Application、Activity、Fragment、Service、ContentProvider,源码中已经继承了ComponentCallbacks2接口,如果开发者需要自定义onTrimMemory的更多行为(例如收到内存不足回调时,进行一些内存释放工作),直接重写父类的方法即可;

    如果开发者需要在自定义的类中接收ComponentCallbacks2系统回调,可按如下方式注册:

    public class ComponentCallbacksDemo implements ComponentCallbacks2{
        
        public ComponentCallbacksDemo() {
            // 注册ComponentCallbacks回调
            getApplicationContext().registerComponentCallbacks(this);
        }
        
        @Override
        public void onTrimMemory(int level) {
            if (level >= TRIM_MEMORY_UI_HIDDEN) {
                // todo do some free memory work
            }
        }
        
        @Override
        public void onConfigurationChanged(@NonNull Configuration newConfig) {}
        
        @Override
        public void onLowMemory() {}
    }
    

    当onTrimMemory(int level)回调时,level表示内存等级,level有以下几种类型:

    /**
     * Level for {@link #onTrimMemory(int)}: the process is nearing the end
     * of the background LRU list, and if more memory isn't found soon it will
     * be killed.
     */
    static final int TRIM_MEMORY_COMPLETE = 80;
    
    /**
     * Level for {@link #onTrimMemory(int)}: the process is around the middle
     * of the background LRU list; freeing memory can help the system keep
     * other processes running later in the list for better overall performance.
     */
    static final int TRIM_MEMORY_MODERATE = 60;
    
    /**
     * Level for {@link #onTrimMemory(int)}: the process has gone on to the
     * LRU list.  This is a good opportunity to clean up resources that can
     * efficiently and quickly be re-built if the user returns to the app.
     */
    static final int TRIM_MEMORY_BACKGROUND = 40;
    
    /**
     * Level for {@link #onTrimMemory(int)}: the process had been showing
     * a user interface, and is no longer doing so.  Large allocations with
     * the UI should be released at this point to allow memory to be better
     * managed.
     */
    static final int TRIM_MEMORY_UI_HIDDEN = 20;
    
    /**
     * Level for {@link #onTrimMemory(int)}: the process is not an expendable
     * background process, but the device is running extremely low on memory
     * and is about to not be able to keep any background processes running.
     * Your running process should free up as many non-critical resources as it
     * can to allow that memory to be used elsewhere.  The next thing that
     * will happen after this is {@link #onLowMemory()} called to report that
     * nothing at all can be kept in the background, a situation that can start
     * to notably impact the user.
     */
    static final int TRIM_MEMORY_RUNNING_CRITICAL = 15;
    
    /**
     * Level for {@link #onTrimMemory(int)}: the process is not an expendable
     * background process, but the device is running low on memory.
     * Your running process should free up unneeded resources to allow that
     * memory to be used elsewhere.
     */
    static final int TRIM_MEMORY_RUNNING_LOW = 10;
    
    /**
     * Level for {@link #onTrimMemory(int)}: the process is not an expendable
     * background process, but the device is running moderately low on memory.
     * Your running process may want to release some unneeded resources for
     * use elsewhere.
     */
    static final int TRIM_MEMORY_RUNNING_MODERATE = 5;
    

    3.3 onLowMemory()

    onTrimMemory是API 14及以上可使用,对于低系统可使用onLowMemory(),其等同于ComponentCallbacks2的当onTrimMemory(TRIM_MEMORY_COMPLETE}回调,对于高系统建议实现nTrimMemory()即可。

    public interface ComponentCallbacks {
        void onConfigurationChanged(@NonNull Configuration newConfig);
        void onLowMemory();
    }
    

    The End

    欢迎关注我,一起解锁更多技能:BC的掘金主页~💐 BC的CSDN主页~💐💐
    个人信息汇总.png

    内存管理概览:https://developer.android.com/topic/performance/memory-overview
    内存分配:https://developer.android.com/topic/performance/memory-management
    Android Studio Profiler:https://developer.android.com/studio/profile/memory-profiler
    dalvik-heap:https://cs.android.com/android/platform/superproject/+/android-11.0.0_r1:frameworks/native/build/phone-xhdpi-2048-dalvik-heap.mk
    https://blog.csdn.net/whbing1471/article/details/105523704
    Android系统内存页面:https://developer.android.com/topic/performance/memory-management

    展开全文
  • android 获取cpu使用率, 内存 实时数据

    热门讨论 2012-07-20 17:47:36
    可以获取系统的内存和可用内存,cpu的实时使用
  • 下面大橙子就教你一招扩大手机虚拟内存,老牛也能拉新车。首先普及一下名词:运行内存是RAM,运行app所使用的内存;存储内存是ROM,就是存放app数据、影音资料的内存。大家说的6G+128G,就是6G RAM+128G ROM。RAMROM...
  • Android系统手动创建虚拟内存swap

    千次阅读 2019-10-07 10:08:01
    首先要使用的工具是android的sdk中的adb.exe 然后用cmd打开adb shell,这里注意android手机打开调试模式与电脑进行usb连接 输入以下命令: mount -o remount,rw /cache; //在cache目录下生成swap.img文件,具体...
  • android修改虚拟内存方法

    千次阅读 2013-07-29 14:19:45
    如果有理有源码的话, 修改:out/target/product/xxx/system/build...dalvik.vm.heapstartsize=12m --------------->应用启动,立马分屏12M内存 dalvik.vm.heapgrowthlimit=72m --------------->整个应用,
  • NULL 博文链接:https://longyi-java.iteye.com/blog/975995
  • CPU 指令对内存的要求 对于 CPU 执行指令来说有三种重要的寄存器: PC 寄存器(Program Counter Register:程序计数器),我们也称作指令地址寄存器(Instruction Address Register)。顾名思义,他就是用来存储下...
  • 怎么给安卓手机增加虚拟内存?

    千次阅读 2021-06-08 17:32:12
    其实,我们只有通过增加虚拟内存方法,也就是说,在内存卡(TF卡)中增加一个区域供运行内存使用。不过这比较消耗内存卡的寿命,所以,建议用户适当使用。下面学习啦小编给大家分享下具体的方法,希望对大家有帮助!...
  • 其中在Android虚拟机中采用的是根节点搜索算法枚举根节点判断是否是垃圾,虚拟机会从GC Roots开始遍历,如果一个节点找不到一条到达GC Roots的路线,也就是没和GC Roots 相连,那么就证明该引用无效,可以被回收,...
  • Android内存管理

    千次阅读 2022-02-24 17:33:43
    Android或Java中,我们一般不用担心内存管理,这是因为Java虚拟机(JVM:Java Virtual Machine)存在垃圾回收机制(GC:Garbage Collection),垃圾回收器会对内存进行管理。相比于其它语言(例如C语言),会要求...
  • 安卓手机怎么设置增加虚拟内存

    千次阅读 2021-06-03 12:59:00
    有许多使用安卓的小伙伴说自己内存一直是不够用,其实我们通过设置它的虚拟内存来解决,下面是学习啦小编为大家介绍安卓手机设置增加虚拟内存方法,欢迎大家阅读。安卓手机设置增加虚拟内存方法1这里只讲解一个最...
  • Android内存管理机制官方详解文档

    千次阅读 2020-12-09 12:40:54
    Android 运行时 (ART) 和 Dalvik 虚拟机使用分页和内存映射来管理内存。这意味着应用修改的任何内存,无论修改的方式是分配新对象还是轻触内存映射的页面,都会一直驻留在 RAM 中,并且无法换出。要从应用中释放内存...
  • Android中的内存泄漏和内存溢出

    千次阅读 2021-12-09 15:45:20
    Android系统为每一个应用程序申请的内存是有限的(为什么如此设计,在dalvik虚拟机章节已经说明),一般为64M或者128M等,国内手机产商修改rom后,也会有所不同。 我们可以在清单文件中配置android:largeheap=“true”...
  • android 内存使用详情查询的几种方法

    千次阅读 2018-07-18 11:02:22
    android /proc/ 目录下为我们提供了操作系统几乎所有的状态信息,当然也包含系统的内存使用信息,下面列举了一些对应内存使用情况的目录信息: /proc/meminfo 机器的内存使用情况 /proc/pid/maps pid 为进程号,...
  • 虚拟机栈:线程创建之初,Java虚拟机会为每一个线程开辟一块虚拟机栈空间,存储线程方法调用的局部变量,计算中间量,参数等,是线程私有的内存区域。 本地方法栈:线程私有的用于native方法引用的内存栈空间。 程序...
  • Android内存占用情况分析

    千次阅读 2020-09-05 21:49:31
    Android内存占用情况分析    本文主要讲解用系统自带工具分析...查看手机剩余内存方法有很多,这个方法比较准确。 Total RAM为手机的总内存; Free RAM为剩余内存:其中cached pss为后台缓冲进程,当系
  • 本文实例讲述了Android SQLite数据库操作方法。分享给大家供大家参考,具体如下: SQLite and Android SQLite简介 SQLite是一个非常流行的嵌入式数据库,它支持SQL语言,并且只利用很少的内存就有很好的性能。此外,...
  • Android Studio 模拟器卡慢、占内存解决方法 Android Studio 模拟器卡慢、占内存解决方法使用Android virtual device来进行调试运行时会比较慢,性能也不是很好。原生模拟器比较吃电脑配置,常常几分钟都跑不完一...
  • 有三种情况:一、不增加安卓手机的虚拟运行内存RAM的运行情况:不必过度在意android系统的运存RAM的大小,安卓系统会帮你管理。Android不是Windows,剩余内存大不是就会快。我也是android用户,以前总用什么es任务...
  • Android性能:内存篇之内存回收

    千次阅读 2019-08-02 17:36:40
    Android性能:内存篇之内存回收 内存回收 一般来说,程序使用内存的方式遵循先向操作系统申请一块内存使用内存使用完毕之后释放内存归还给操作系统。然而在传统的C/C++等要求显式释放内存的编程语言中,记得在...
  • 对手机本地图片或网络图片,进行缓存.虚拟内存等存取实例代码忆调试通过.
  • Android 内存管理详解

    千次阅读 2021-02-20 18:29:04
    和你一起终身学习,这里是程序员Android经典好文推荐,通过阅读本文,您将收获以下知识点:一、Android 垃圾回收机制(GC)二、共享内存三、内存的申请与回收四、内存限制五、不同Ap...
  • 查看Android系统内存使用方法

    千次阅读 2017-09-07 15:10:24
    方式一、使用dumpsys meminfo命令。我们可以在adb shell 中运行dumpsys meminfo命令来得到进程的...比如"adb shell dumpsys meminfocom.teleca.robin.test"将得到com.teleca.robin.test进程使用内存的信息:Appli...
  • Android Studio 内存不足

    万次阅读 2021-07-15 16:49:34
    android studio 4.2.2 编译的项目的时候,出现的内存不足问题,实际上android studio会有引导设置内存大小,可能都不太在意在哪个地方,设置完就完事了,在不提示的情况下可能找不到在哪设置,在此记录一笔。...
  • Android 在 Hardware Composer v1.3 中添加了对虚拟显示屏的平台支持(该支持功能可由 Miracast 使用)。虚拟显示屏的合成方式与物理显示屏类似:在 prepare() 中描述输入层,由 SurfaceFlinger 执行 GPU 合成,然后将...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 44,540
精华内容 17,816
关键字:

虚拟内存使用方法android

友情链接: Primer.rar