Android內(nèi)存占用分析

思考的問(wèn)題:
1、為什么/proc/meminfo中的內(nèi)存總大小比物理內(nèi)存小?
2、怎么看Android還剩多少可用內(nèi)存比較準(zhǔn)確郭计?
3、怎么看Kernel的內(nèi)存占用比較準(zhǔn)確椒振?
4昭伸、是哪些因素影響了Lost RAM的大小澎迎?
5庐杨、怎么看一個(gè)進(jìn)程的內(nèi)存占用比較合適?

本文以Android P為例夹供,對(duì)應(yīng)kernel版本為4.14

1灵份、 MemTotal

MemTotal 即 /proc/meminfo 中的第一行的值, 可以認(rèn)為是系統(tǒng)可供分配的內(nèi)存總大小, 通常大小會(huì)比實(shí)際物理內(nèi)存小, 這個(gè)是為什么呢? 少的部分被誰(shuí)占用了呢?

1.1 memblock

首先需要了解一下memblock.

在伙伴系統(tǒng)(buddy system)初始化完成前,Linux使用memblock來(lái)管理內(nèi)存哮洽,memblock管理的內(nèi)存分為兩部分: memory類(lèi)型和reserved類(lèi)型填渠。 對(duì)應(yīng)的描述變量分別是memblock.memorymemblock.reserved

memblock中兩種類(lèi)型的內(nèi)存申請(qǐng)/添加函數(shù)如下:

//memory 類(lèi)型的memblock申請(qǐng)/添加
int __init_memblock memblock_add(phys_addr_t base, phys_addr_t size)
int __init_memblock memblock_add_node(phys_addr_t base, phys_addr_t size, int nid)

//reserved 類(lèi)型的mblock申請(qǐng)/添加
int __init_memblock memblock_reserve(phys_addr_t base, phys_addr_t size)
1.1.1 物理內(nèi)存分布

memblock是如何知道物理內(nèi)存的分布的呢?

kernel啟動(dòng)的過(guò)程中鸟辅,從lk/uboot知道了DTB的加載地址, 在如下的調(diào)用流程中解析DTB下的memroy節(jié)點(diǎn)氛什,將節(jié)點(diǎn)下的物理內(nèi)存區(qū)間使用memblock_add()添加給memblock維護(hù)。
由于lk/uboot中可能使用內(nèi)存匪凉,lk/uboot也可以修改DTB枪眉,所以這里memory節(jié)點(diǎn)下可能有多個(gè)物理內(nèi)存區(qū)間。 多個(gè)物理區(qū)間中不連續(xù)的部分就是已經(jīng)被lk/uboot占用的部分再层。

setup_machine_fdt(__fdt_pointer)   //__fdt_pointer是傳遞的DTB加載地址
  |-->early_init_dt_scan()
    |-->early_init_dt_scan_memory() //解析memory node,遍歷其下的所有單元
      |-->early_init_dt_add_memory_arch()
        |-->memblock_add(base, size) //每個(gè)單元的起始地址和大小添加到memblock的memory類(lèi)型中
    //dts中的初始描述,0x40000000是起始物理地址
    //lk/uboot可以修改DTB中的節(jié)點(diǎn),這里的reg單元可能有多個(gè)
    memory {
        device_type = "memory";
        reg = <0 0x40000000 0 0x20000000>;
    };   

可以通過(guò) /sys/kernel/debug/memblock/ 下的節(jié)點(diǎn)查看兩種內(nèi)存的物理空間分布:

lsg@eebbk:~$adb shell cat /sys/kernel/debug/memblock/memory
lsg@eebbk:~$adb shell cat /sys/kernel/debug/memblock/reserved

memblock構(gòu)建了內(nèi)存的物理空間分布, 之后在伙伴系統(tǒng)(buddy system)初始化的過(guò)程中,構(gòu)建了物理內(nèi)存分布到虛擬內(nèi)存空間的映射.
memblock管理的memory類(lèi)型的頁(yè)框都添加到各ZONE中, totalram_pages統(tǒng)計(jì)了它的頁(yè)框數(shù)目. 詳細(xì)代碼見(jiàn) free_all_bootmem()

1.2 隱藏的內(nèi)存占用

這里說(shuō)幾個(gè)概念:

  • 物理內(nèi)存: 即DRAM物理內(nèi)存大小,比如物理內(nèi)存為2GRAM,則物理內(nèi)存大小為2097152K
  • memblock管理的內(nèi)存: memblock管理的內(nèi)存, 包含伙伴系統(tǒng)管理的內(nèi)存和reserved兩部分,其頁(yè)框數(shù)為 get_num_physpages()
    在下面的例子中, 可管理內(nèi)存大小為 2045952K
  • 伙伴系統(tǒng)管理的內(nèi)存:伙伴系統(tǒng)管理的內(nèi)存, 初始時(shí)對(duì)應(yīng)memblock中的memory類(lèi)型,其頁(yè)框數(shù)在kernel中對(duì)應(yīng)全局變量 totalram_pages
    在下面的例子中, 可分配內(nèi)存大小為1983136K

關(guān)系:
物理內(nèi)存 > memblock管理的內(nèi)存 > 伙伴系統(tǒng)管理的內(nèi)存
物理內(nèi)存 = memblock管理的內(nèi)存 + 預(yù)申請(qǐng)內(nèi)存
memblock管理的內(nèi)存 = 伙伴系統(tǒng)管理的內(nèi)存 + reserverd內(nèi)存(memblock的reserved type)

預(yù)申請(qǐng)內(nèi)存:
預(yù)申請(qǐng)內(nèi)存是指在memblock初始化前已經(jīng)申請(qǐng)的內(nèi)存贸铜,呈現(xiàn)給memblock的是這部分物理內(nèi)存不存在堡纬,比如lk/uboot/bootloader用到的內(nèi)存。
不同平臺(tái)這部分的占用大小不盡相同萨脑,有的可能為0.

Q1:
memblock是怎么知道有些內(nèi)存已經(jīng)被占用的?
A1:
lk/uboot/bootloade修改DTB中memory節(jié)點(diǎn)下的空間區(qū)段(reg), kernel中讀取這些空間區(qū)段(reg),空間區(qū)段之間的內(nèi)存空間就是被預(yù)申請(qǐng)已經(jīng)占用了的.

//kernel log 中的輸出, 代碼實(shí)現(xiàn)在 mem_init_print_info()
//關(guān)系:2097152K(物理2GB) = 2045952K(memblock管理的內(nèi)存) + 51200K(預(yù)申請(qǐng)內(nèi)存)
//關(guān)系:2045952K(memblock管理的內(nèi)存) = 1982176K + 63776K + 0K
//1982176K 是此時(shí) totalram_pages*4K 的大小, 也即memblcok管理的`memory type` 部分
//63776K 是memblcok管理的`reserved type` 部分
[    0.000000] -(0)[0:swapper]Memory: 1982176K/2045952K available (12924K kernel code, 1384K rwdata, 4392K rodata, 960K init, 5936K bss, 63776K reserved, 0K cma-reserved)

///proc/meminfo的輸出
//1983136 是此時(shí) totalram_pages*4K 的大小
lsg@eebbk:~$adb shell cat /proc/meminfo
MemTotal:        1983136 kB

Q2:
為什么MemTotal的大小比實(shí)際物理內(nèi)存小?
A2:
這個(gè)部分比實(shí)際的物理內(nèi)存小,就是少了上面說(shuō)的兩個(gè)部分:預(yù)申請(qǐng)內(nèi)存和kernel reserved內(nèi)存.
預(yù)申請(qǐng)內(nèi)存: 這部分的大小可以查看/d/memblock/memory相對(duì)物理內(nèi)存空閑的部分.
reserved內(nèi)存: 這部分大小可以查看/d/memblock/reserved的大小,或者kernel log中的大小(上例中63776K)

Q3:
為什么開(kāi)機(jī)時(shí) totalram_pages的大小(上例中1982176K) 和 totalram_pages的大小(上例中1983136K) 開(kāi)機(jī)后 不一致呢?
A3:
這是因?yàn)閙emblock管理的reserved type中部分內(nèi)存在初始化完畢后釋放了,添加到了伙伴系統(tǒng)(buddy system)中. 詳細(xì)代碼見(jiàn)free_initmem()
比如上面的log, 1982176K + 960K = 1983136K

<6>[ 2.258901] -(2)[1:swapper/0]Freeing unused kernel memory: 960K

Q4:
kernel代碼部分的占用在哪個(gè)部分體現(xiàn)?
A4:
這部分包含 kernel code+rwdata+rodata+init+bss , 在 kernel log中已經(jīng)輸出. 這部分的物理占用計(jì)算在reserved部分.詳細(xì)代碼見(jiàn) arm64_memblock_init().
另外kernel code物理區(qū)域也會(huì)vmap到虛擬地址空間隐轩,其vmap的區(qū)間可以看 /proc/vmallocinfo中帶有paging_init的行饺饭。

1.3 reserved包含哪些

前面說(shuō)到渤早,memblock管理的內(nèi)存, 包含伙伴系統(tǒng)管理的內(nèi)存和reserved兩部分。
那reserverd部分的內(nèi)存占用又包含哪些呢瘫俊?詳細(xì)分解來(lái)看鹊杖,至少包含以下這些部分:

1、代碼
包含 kernel code+rwdata+rodata+init+bss 等扛芽,都計(jì)入到reserved部分骂蓖。
分配路徑:

arm64_memblock_init()

2、struct page
我們知道整個(gè)物理內(nèi)存被分配為若干個(gè)頁(yè)框(page frame)川尖,一般大小位4K登下,一個(gè)頁(yè)框?qū)?yīng)一個(gè)struct page結(jié)構(gòu)。struct page的內(nèi)存占用就是在reserved部分叮喳,物理內(nèi)存越大被芳,這個(gè)區(qū)域就越大。比如2GB RAM馍悟,可能需要32MB大小的struct page畔濒。
分配路徑:

__earlyonly_bootmem_alloc()

3、percpu
為所有已定義的per-cpu變量分配副本空間锣咒,靜態(tài)定義的per-cpu變量越多侵状,這個(gè)區(qū)域越大。
分配路徑:

setup_per_cpu_areas() --> pcpu_embed_first_chunk() --> pcpu_dfl_fc_alloc()

4毅整、devicetree
解析DTB消耗的內(nèi)存
分配路徑:

unflatten_device_tree() --> early_init_dt_alloc_memory_arch()

5趣兄、dts中reserved節(jié)點(diǎn)
dts中通過(guò)reserved_memory節(jié)點(diǎn)申請(qǐng)的reserved內(nèi)存
分配路徑:

early_init_fdt_scan_reserved_mem() --> early_init_dt_reserve_memory_arch()

2、 從Linux角度

/proc/meminfo 是從Linux的角度統(tǒng)計(jì)系統(tǒng)的內(nèi)存占用情況.

2.1 /proc/meminfo

具體代碼在:

//fs/proc/meminfo.c
static int meminfo_proc_show(struct seq_file *m, void *v)

1悼嫉、MemTotal
當(dāng)前系統(tǒng)可使用的內(nèi)存大小诽俯,對(duì)應(yīng)全局變量MemTotal。 詳細(xì)見(jiàn)上面第一節(jié)的敘述承粤。

2暴区、MemFree
當(dāng)前系統(tǒng)空閑的內(nèi)存大小,對(duì)應(yīng)所有處于NR_FREE_PAGES狀態(tài)的頁(yè)框辛臊。

3仙粱、MemAvailable
大致等于: MemFree + Active(file) + Inactive(file) + SReclaimable
此外還考慮了內(nèi)存壓力水位(watermark)的情況,計(jì)算比較復(fù)雜彻舰,詳細(xì)見(jiàn) si_mem_available().
這只是理論上系統(tǒng)可用的內(nèi)存伐割,即理論上可回收的內(nèi)存候味,但是實(shí)際上能用的達(dá)不到這么多。

4隔心、Buffers
塊設(shè)備(block device)操作所占用的page cache大小白群。
塊設(shè)備的緩沖區(qū)大小,詳細(xì)見(jiàn)nr_blockdev_pages()

5硬霍、Cached
普通文件操作所占用的page cache大小帜慢。這里只是普通文件操作時(shí)的page cache,其實(shí)page cache還有swap cache 和 上面的Buffers唯卖。
計(jì)算方法:

Cached = global_node_page_state(NR_FILE_PAGES) - SwapCached - Buffers

6粱玲、Active/Inactive
Active = Active(anon) + Active(file)
Inactive = Inactive(anon) + Inactive(file)
內(nèi)存中的頁(yè)分為匿名頁(yè)(anon)和文件頁(yè)(file)。

  • 匿名頁(yè)(anon):特征是其內(nèi)容與文件無(wú)關(guān)拜轨,比如malloc申請(qǐng)的內(nèi)存抽减,回收方法是交換到swap區(qū)。
  • 文件頁(yè)(file):特征是其內(nèi)容與文件相關(guān)橄碾,比如程序文件卵沉、數(shù)據(jù)文件所對(duì)應(yīng)的內(nèi)存頁(yè),回收方法是回寫(xiě)到磁盤(pán)或清空法牲。
    在內(nèi)存回收中史汗,采用的算法是LRU(Least Recently Used),LRU算法又將匿名頁(yè)(anon)和文件頁(yè)(file)都分為活躍(Active)和不活躍(Inactive)皆串。內(nèi)存回收時(shí)淹办,首先回收的是不活躍頁(yè)(Inactive)。

7恶复、Unevictable/Mlocked
Unevictable 對(duì)應(yīng)LRU_UNEVICTABLE怜森, 是LRU中不能被回收的頁(yè)。Mlocked 對(duì)應(yīng)NR_MLOCK的頁(yè)谤牡。

8副硅、SwapTotal/SwapFree
SwapTotal 對(duì)應(yīng) Swap 區(qū)的總大小。SwapFree 對(duì)應(yīng) Swap 區(qū)的剩余大小翅萤。

9恐疲、Dirty/Writeback
Dirty: 對(duì)應(yīng)NR_FILE_DIRTY的頁(yè),需要寫(xiě)入磁盤(pán)的內(nèi)存區(qū)大小
Writeback: 對(duì)應(yīng)NR_WRITEBACK的頁(yè)套么,正在被寫(xiě)回磁盤(pán)的大小

10培己、AnonPages/Mapped
AnonPages: 對(duì)應(yīng)NR_ANON_MAPPED的頁(yè),已映射的匿名頁(yè)(anon)大小
Mapped: 對(duì)應(yīng)NR_FILE_MAPPED的頁(yè)胚泌,已映射的文件頁(yè)的大小省咨,Mapped 是 Cached 的一部分

11、Shmem
對(duì)應(yīng)NR_SHMEM的頁(yè)玷室,是 tmpfs 和 devtmpfs 所使用的內(nèi)存零蓉。

12笤受、Slab/SReclaimable/SUnreclaim
Slab = SReclaimable + SUnreclaim
使用slab/slub/slob機(jī)制申請(qǐng)的內(nèi)存大小,又分為可回收(NR_SLAB_RECLAIMABLE)和不可回收(NR_SLAB_UNRECLAIMABLE)兩部分敌蜂。
可以通過(guò) /proc/slabinfo 節(jié)點(diǎn)查看slab的內(nèi)存信息箩兽。

13、KernelStack
對(duì)應(yīng) NR_KERNEL_STACK_KB的頁(yè)章喉, 是所有task的內(nèi)核棧的內(nèi)存大小汗贫。

14、PageTables
對(duì)應(yīng) NR_PAGETABLE的頁(yè)囊陡,是頁(yè)表(page table)的占用大小芳绩,頁(yè)表(page table)的作用就是完成內(nèi)存虛擬地址到物理地址的轉(zhuǎn)換掀亥。
還有一個(gè)相關(guān)概念是頁(yè)框(page frame)撞反,頁(yè)框(page frame)是內(nèi)存管理的最小單位,就是物理頁(yè)搪花。每一個(gè)物理頁(yè)都用一個(gè)對(duì)應(yīng)的struct page結(jié)構(gòu)體描述遏片,struct page占用的內(nèi)存在reserved內(nèi)存中。

15撮竿、VmallocTotal
整個(gè)vmalloc的地址區(qū)間的大小吮便,對(duì)應(yīng) (VMALLOC_END - VMALLOC_START),vmalloc的真實(shí)占用可以查看/proc/vmallocinfo幢踏。詳細(xì)見(jiàn)下一節(jié)髓需。

2.2 /proc/vmallocinfo

vmalloc 用于在內(nèi)核中分配虛擬地址空間連續(xù)的內(nèi)存,/proc/vmallocinfo 展示了整個(gè)vmalloc區(qū)間(VMALLOC_END - VMALLOC_START)中已經(jīng)分配的虛擬地址空間信息房蝉,每一行表示一段區(qū)間的信息僚匆。

單個(gè)區(qū)間的信息展示代碼在:

//mm/vmalloc.c
static int s_show(struct seq_file *m, void *p)
{
    struct vmap_area *va;
    struct vm_struct *v;
    
    va = list_entry(p, struct vmap_area, list);
   
    if (!(va->flags & VM_VM_AREA)) {
        seq_printf(m, "0x%pK-0x%pK %7ld %s\n",
            (void *)va->va_start, (void *)va->va_end,
            va->va_end - va->va_start,
            //kernel 4.14上,這里考慮了VM_LAZY_FREE 的flag, 5.x版本又去掉了這一部分
            va->flags & VM_LAZY_FREE ? "unpurged vm_area" : "vm_map_ram");

        return 0;
    }

    v = va->vm;
    ......
}

/proc/vmallocinfo中每行輸出的最后一個(gè)字段表示這段區(qū)間的類(lèi)型搭幻,kernel 4.14上顯示的類(lèi)型有:

  • ioremap(對(duì)應(yīng)VM_IOREMAP)
  • vmalloc(對(duì)應(yīng)VM_ALLOC)
  • vmap(對(duì)應(yīng)VM_MAP)
  • user(對(duì)應(yīng)VM_USER)
  • vm_map_ram(根據(jù)vmap_area.flag)
  • unpurged vm_area(根據(jù)vmap_area.flag)

一段vmalloc區(qū)間是否已經(jīng)在物理上分配對(duì)應(yīng)的大小咧擂,要看具體的類(lèi)型。

對(duì)于Android P來(lái)說(shuō)檀蹋,除ioremap,map_lowmem,vm_map_ram之外的類(lèi)型都認(rèn)為是有物理占用的松申,用VmallocUsed表示Android統(tǒng)計(jì)的Vmalloc內(nèi)存占用。

但是如果按排除這幾個(gè)類(lèi)型來(lái)統(tǒng)計(jì)俯逾,會(huì)有一定的偏差贸桶,在Android P(kernel 為4.14),存在的誤差主要表現(xiàn)在:

1桌肴、kernel code

kernel code的占用皇筛,包括kernel code+rwdata+rodata+init+bss,都計(jì)入了memblock reserved部分识脆。

但是kernel code也會(huì)通過(guò)vmap映射到虛擬地址空間(見(jiàn) setup_arch() --> paging_init() --> map_kernel())设联,這部分地址空間可以看含有paging_init關(guān)鍵字的行善已。

由于這部分默認(rèn)的關(guān)鍵字為vmap, 所以是統(tǒng)計(jì)在 VmallocUsed中的。

2离例、binder

Binder的內(nèi)存分配是用戶(hù)空間調(diào)用mmap(), 到kernel中binder_mmap()的流程中為用戶(hù)空間映射分配內(nèi)存换团,但是只映射了VMA區(qū)間大小的( 比如BINDER_VM_SIZE)虛擬地址空間,并沒(méi)有分配物理頁(yè)宫蛆,是等到binder傳輸需要使用內(nèi)存時(shí)艘包,才在binder_update_page_range()中申請(qǐng)物理頁(yè)。

比如 一個(gè)binder client 調(diào)用binder_mmap()耀盗,其VMA區(qū)間為1024K想虎,但是可能目前實(shí)際只用到了4K。但1024K都被計(jì)入到VmallocUsed部分叛拷。

74bb4b1000-74bb5af000 r--p 00000000 00:13 8297                           /dev/binder
Size:               1016 kB
KernelPageSize:        4 kB
MMUPageSize:           4 kB
Rss:                   4 kB
Pss:                   4 kB

概括來(lái)說(shuō)舌厨,binder占用的內(nèi)存,在進(jìn)程PSS中已經(jīng)統(tǒng)計(jì)忿薇,又統(tǒng)計(jì)在了kernel中裙椭,而且統(tǒng)計(jì)的是整個(gè)VMA區(qū)間而非實(shí)際物理占用,這造成了統(tǒng)計(jì)上的誤差署浩。

Android P上binder的內(nèi)存信息歸為vmalloc揉燃,這就會(huì)統(tǒng)計(jì)到 VmallocUsed中,而在Android P之前版本上筋栋,內(nèi)存信息類(lèi)型為ioremap 炊汤。

3、unpurged vm_area

s_show()中的代碼弊攘,在kernel 4.14上抢腐,對(duì)于va->flags含有VM_VM_AREAVM_LAZY_FREE位的,類(lèi)型標(biāo)記為unpurged vm_area肴颊,而只有VM_VM_AREA的標(biāo)記為vm_map_ram氓栈。

看內(nèi)核的提交記錄,VM_LAZY_FREE是 在2017年7月為解決ioremap地址跳躍的問(wèn)題所加上的婿着,本意是對(duì)此類(lèi)型做特殊標(biāo)記授瘦。但不應(yīng)該認(rèn)為這種類(lèi)型已經(jīng)分配了物理內(nèi)存,它仍然只是一段虛擬地址區(qū)間竟宋。

在2019年9月又去掉了這部分代碼提完,最新的5.3內(nèi)核版本上已經(jīng)沒(méi)有了VM_LAZY_FREE的標(biāo)記。

綜上來(lái)看丘侠,unpurged vm_area標(biāo)記的區(qū)間徒欣,Android不應(yīng)該認(rèn)為是物理內(nèi)存占用,不應(yīng)統(tǒng)計(jì)在 VmallocUsed中蜗字。

/proc/vmallocinfo的輸出示例:

//區(qū)間起始地址-區(qū)間結(jié)束地址 
//kernel code的map
0x0000000000000000-0x0000000000000000 8912896 paging_init+0x104/0x6c0 phys=0x0000000080080000 vmap
//binder
0x0000000000000000-0x0000000000000000 1044480 binder_alloc_mmap_handler+0x48/0x1cc vmalloc
//unpurged vm_area
0x0000000000000000-0x0000000000000000 1044480 unpurged vm_area
//進(jìn)程棧打肝,實(shí)際只分配了4頁(yè)脂新,但地址空間有5頁(yè)
0x0000000000000000-0x0000000000000000   20480 _do_fork+0xdc/0x3ac pages=4 vmalloc

2.3 free命令

free 命令的代碼見(jiàn):

//external/toybox/toys/other/free.c
void free_main(void)
{
  struct sysinfo in; 
  //系統(tǒng)接口,獲取內(nèi)存信息粗梭,實(shí)質(zhì)上與 /proc/meminfo 的內(nèi)容相對(duì)應(yīng)
  sysinfo(&in);

  xprintf("\t\ttotal        used        free      shared     buffers\n"
    "Mem:%17s%12s%12s%12s%12s\n-/+ buffers/cache:%15s%12s\n"
    "Swap:%16s%12s%12s\n", convert(in.totalram),
    convert(in.totalram-in.freeram), convert(in.freeram), convert(in.sharedram),
    convert(in.bufferram), convert(in.totalram - in.freeram - in.bufferram),
    convert(in.freeram + in.bufferram), convert(in.totalswap),
    convert(in.totalswap - in.freeswap), convert(in.freeswap));
}

其展示的內(nèi)容示例如下:

H100:/ # free -k
        total        used        free      shared     buffers
Mem:   1983136     1604832      378304        2240       71416
-/+ buffers/cache: 1533416      449720
Swap:  1048572           0     1048572

3争便、從Android角度

dumpsys meminfo 是從Android的角度統(tǒng)計(jì)系統(tǒng)的內(nèi)存占用情況。

可以dumpsys meminfo -h查看支持的參數(shù)断医,這里說(shuō)明不接參數(shù)的情況滞乙。

對(duì)應(yīng)的代碼在:

//frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
private final void dumpApplicationMemoryUsage()

展示的信息分為幾個(gè)部分:

  • 進(jìn)程PSS情況
    • Total PSS by process
    • Total PSS by OOM adjustment
    • Total PSS by category
  • 整體情況
    • Total RAM
    • Free RAM
    • Used RAM
    • Lost RAM
    • ZRAM

3.1 整體情況

先看整體的內(nèi)存占用情況,整體的內(nèi)存占用情況在輸出結(jié)果的最后鉴嗤,示例如下:

Total RAM: 1,983,136K (status moderate)
 Free RAM: 1,016,150K (   75,626K cached pss +   562,384K cached kernel +   378,140K free)
 Used RAM: 1,108,343K (  758,523K used pss +   349,820K kernel)
 Lost RAM:  -141,361K
     ZRAM:         4K physical used for         0K in swap (1,048,572K total swap)
   Tuning: 128 (large 256), oom   322,560K, restore limit   107,520K (high-end-gfx)

約定幾個(gè)變量的值:

  • totalPss: 是展示信息中Total PSS by process:下所有進(jìn)程的內(nèi)存占用之和.
  • cachedPss:是展示信息中 Total PSS by OOM adjustment:下的Cached部分進(jìn)程的內(nèi)存大小之和斩启。

使用了如下的調(diào)用關(guān)系主要讀取 /proc/meminfo的信息,將對(duì)應(yīng)字段放到 MemInfoReader.mInfos[]數(shù)組中醉锅,展示的信息就是MemInfoReader.mInfos[]中信息的組合兔簇。

MemInfoReader.readMemInfo()
    //frameworks/base/core/java/android/os/Debug.java
  --> Debug.getMemInfo()
    //frameworks/base/core/jni/android_os_Debug.cpp
    --> android_os_Debug_getMemInfo()

以下涉及/proc/meminfo中字段的直接用其顯示的字符串代指。

3.1.1 Total RAM

對(duì)應(yīng) MemTotal

3.1.2 Free RAM

包括 cached pss , cached kernel, free 三個(gè)部分荣挨。

  • cached pss 對(duì)應(yīng)變量cachedPss的值男韧。
    這部分進(jìn)程占用的內(nèi)存并沒(méi)有被釋放朴摊,而由于他們都已切換到后臺(tái)默垄,且adj較低,系統(tǒng)認(rèn)為可以釋放掉這部分內(nèi)存甚纲。所以對(duì)于這部分進(jìn)程口锭,系統(tǒng)最好有機(jī)制能及時(shí)清理掉從而釋放內(nèi)存。
  • cached kernel 對(duì)應(yīng) Buffers + Cached + SReclaimable - Mapped
    這部分的內(nèi)存由于理論上是可以被Kernel回收的介杆,所以這里也計(jì)算在free中鹃操,但是這是一個(gè)理論上的值,實(shí)際上很難做到全部回收春哨。
  • feee 對(duì)應(yīng) MemFree
3.1.2 Used RAM

包括 used pss , kernel兩個(gè)部分荆隘。

  • used pss 對(duì)應(yīng)兩個(gè)變量的差值,即totalPss - cachedPss赴背。

  • kernel 對(duì)應(yīng) Shmem + SUnreclaim + VmallocUsed + PageTables + KernelStack
    其中VmallocUsed 是統(tǒng)計(jì)/proc/vmallocinfo中除ioremap,map_lowmem,vm_map_ram之外的和椰拒,詳細(xì)見(jiàn)Debug.get_allocated_vmalloc_memory()
    這部分即是對(duì)kernel的內(nèi)存占用的一個(gè)統(tǒng)計(jì),如果要統(tǒng)計(jì)kernel的內(nèi)存占用凰荚,這個(gè)稍微準(zhǔn)確一些燃观。

3.1.3 ZRAM
  • 第一個(gè)數(shù) 用變量zramtotal來(lái)代替,表示zram實(shí)際占用的物理內(nèi)存便瑟,是從/sys/block/zram0/mm_stat中統(tǒng)計(jì)而來(lái)
  • 第二個(gè)數(shù) 對(duì)應(yīng) SwapTotal - SwapFree , 是已經(jīng)在swap區(qū)的內(nèi)存大小
  • 第三個(gè)數(shù) 對(duì)應(yīng) SwapTotal缆毁, 是整個(gè)swap區(qū)的大小
3.1.4 Lost RAM

對(duì)應(yīng)MemTotal - (totalPss - totalSwapPss) - MemFree - (cached kernel) - (kernel) - zramtotal

前面已經(jīng)說(shuō)過(guò)到涂,totalPss對(duì)應(yīng)展示信息下的 cached pss + used pss脊框。
totalSwapPss是統(tǒng)計(jì)所有進(jìn)程所有VMA中SwapPss的頁(yè)颁督,含義是已經(jīng)換入到swap區(qū)的頁(yè),則(totalPss - totalSwapPss)就表示未進(jìn)入交換區(qū)的PSS浇雹。

Lost RAM也與 (Total RAM) - (Free RAM) - (Used RAM) - (zramtotal) + (SwapTotal - SwapFree)的值大致相對(duì)應(yīng)适篙,詳細(xì)見(jiàn)代碼中的計(jì)算。
Lost RAM 可以理解為內(nèi)存統(tǒng)計(jì)的誤差箫爷,是某些部分被重復(fù)計(jì)算或沒(méi)有計(jì)算嚷节。可以參考這里虎锚。

就我的理解硫痰,kernel USED部分中VmallocUsed就存在統(tǒng)計(jì)不一致的問(wèn)題,詳細(xì)見(jiàn)下一節(jié)窜护。

思考問(wèn)題:
更詳細(xì)的影響Lost RAM的因素有哪一些呢效斑?如何讓Lost RAM的誤差更小呢?留待后續(xù)進(jìn)一步追查柱徙。

3.1.5 kernel的統(tǒng)計(jì)誤差

上面說(shuō)到kernel的內(nèi)存占用包含Shmem + SUnreclaim + VmallocUsed + PageTables + KernelStack這幾個(gè)部分缓屠,而VmallocUsed 是統(tǒng)計(jì)/proc/vmallocinfo輸出中不含有ioremap,map_lowmem,vm_map_ram關(guān)鍵字的內(nèi)存區(qū)間之和,詳細(xì)見(jiàn)Debug.get_allocated_vmalloc_memory()护侮。

//frameworks/base/core/jni/android_os_Debug.cpp
static long get_allocated_vmalloc_memory() {
}

VmallocUsed在這里會(huì)有一些統(tǒng)計(jì)上的誤差敌完,這個(gè)誤差就造成了kernel的統(tǒng)計(jì)誤差。

VmallocUsed對(duì)/proc/vmallocinfo的統(tǒng)計(jì)誤差主要表現(xiàn)在以下方面羊初,詳細(xì)看[2.2節(jié)](#2.2 /proc/vmallocinfo)滨溉。

  • Kernel code 的重復(fù)統(tǒng)計(jì)

    此處應(yīng)該是 Android 的一個(gè)統(tǒng)計(jì)bug, kernel code的內(nèi)存占用长赞,既統(tǒng)計(jì)在了memblock reserved部分晦攒,又統(tǒng)計(jì)在了VmallocUsed部分。

  • Binder的占用誤差

    binder占用的內(nèi)存得哆,在進(jìn)程PSS中已經(jīng)統(tǒng)計(jì)脯颜,又統(tǒng)計(jì)在了kernel中,而且統(tǒng)計(jì)的是整個(gè)VMA區(qū)間而非實(shí)際物理占用贩据,這造成了統(tǒng)計(jì)上的誤差栋操。

3.2 進(jìn)程PSS情況

是對(duì)系統(tǒng)所有用戶(hù)態(tài)進(jìn)程PSS情況的統(tǒng)計(jì),統(tǒng)計(jì)的基礎(chǔ)是以pid獲取每個(gè)進(jìn)程的PSS占用情況乐设,調(diào)用到的方法是Debug.getMemoryInfo()讼庇,最終是統(tǒng)計(jì)/proc/pid/smaps下的數(shù)據(jù)。各類(lèi)型的內(nèi)存占用信息保存在 Debug.MemoryInfo 對(duì)象中近尚。

詳細(xì)的單個(gè)進(jìn)程的內(nèi)存占用統(tǒng)計(jì)可以查看 第四章蠕啄。

//frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
    Debug.MemoryInfo mi = new Debug.MemoryInfo();
    ......
    //調(diào)用到JNI中的 android_os_Debug_getDirtyPagesPid() --> read_mapinfo()
    //具體就是解析 /proc/pid/smaps 中的內(nèi)存區(qū)間,按類(lèi)型匯總信息
    Debug.getMemoryInfo(pid, mi);

一個(gè)進(jìn)程的PSS就是這些信息的相加:

//frameworks/base/core/java/android/os/Debug.java
public int getTotalPss() {
    return dalvikPss + nativePss + otherPss + getTotalSwappedOutPss();
}

public int getTotalSwappedOutPss() {
    return dalvikSwappedOutPss + nativeSwappedOutPss + otherSwappedOutPss;
}

1、Total PSS by process

Total PSS by process下是由大到小顯示所有進(jìn)程的PSS情況歼跟。

2和媳、Total PSS by OOM ADJ

Total PSS by OOM adjustment下是以進(jìn)程的OOM ADJ值(/proc/pid/oom_score_adj, 范圍在[-1000,1000])的區(qū)間分類(lèi)來(lái)統(tǒng)計(jì)進(jìn)程的PSS。ADJ的區(qū)間與類(lèi)別對(duì)應(yīng)如下:

對(duì)于Cached的進(jìn)程哈街,其ADJ很小(<900)留瞳,這部分進(jìn)程的內(nèi)存占用計(jì)算到了Free RAM中,即系統(tǒng)認(rèn)為這部分進(jìn)程是可以回收的骚秦。

static final int[] DUMP_MEM_OOM_ADJ = new int[] {
        ProcessList.NATIVE_ADJ,            //ADJ在此區(qū)間的為 “Native”
        ProcessList.SYSTEM_ADJ,            //ADJ在此區(qū)間的為 “System” 
        ProcessList.PERSISTENT_PROC_ADJ,   //ADJ在此區(qū)間的為 “Persistent"
        ProcessList.PERSISTENT_SERVICE_ADJ,//ADJ在此區(qū)間的為 “Persistent Service"
        ProcessList.FOREGROUND_APP_ADJ,    //ADJ在此區(qū)間的為 “Foreground"
        ProcessList.VISIBLE_APP_ADJ,       //ADJ在此區(qū)間的為 “Visible"
        ProcessList.PERCEPTIBLE_APP_ADJ,   //ADJ在此區(qū)間的為 “Perceptible"
        ProcessList.BACKUP_APP_ADJ,        //ADJ在此區(qū)間的為 “Backup"
        ProcessList.HEAVY_WEIGHT_APP_ADJ,  //ADJ在此區(qū)間的為 “Heavy Weight"
        ProcessList.SERVICE_ADJ,           //ADJ在此區(qū)間的為 “A Services"
        ProcessList.HOME_APP_ADJ,          //ADJ在此區(qū)間的為 “Home"
        ProcessList.PREVIOUS_APP_ADJ,      //ADJ在此區(qū)間的為 “Previous"
        ProcessList.SERVICE_B_ADJ,         //ADJ在此區(qū)間的為 “B Services"
        ProcessList.CACHED_APP_MIN_ADJ     //ADJ大于此值的為 “Cached"
};

3她倘、Total PSS by category

Total PSS by category下是以?xún)?nèi)存占用的類(lèi)型分類(lèi)來(lái)統(tǒng)計(jì)進(jìn)程的PSS。

4作箍、從進(jìn)程角度看

這里說(shuō)的是用戶(hù)態(tài)進(jìn)程的內(nèi)存占用硬梁,其實(shí)內(nèi)核線(xiàn)程也有內(nèi)存占用,只是Linux/Android并未進(jìn)行統(tǒng)計(jì)胞得,內(nèi)核線(xiàn)程的內(nèi)存占用分散在Kernel的各類(lèi)占用里荧止,比如KernelStack,Slab等 。

幾個(gè)概念:
查看一個(gè)進(jìn)程的內(nèi)存占用阶剑,需要弄清楚以下幾個(gè)概念:

  • VSS (Virtual Set Size)
    虛擬耗用內(nèi)存(包含共享庫(kù)占用的內(nèi)存跃巡,以及分配但未使用內(nèi)存)
  • RSS (Resident Set Size)
    實(shí)際使用物理內(nèi)存(包含共享庫(kù)占用的全部?jī)?nèi)存)
  • PSS (Proportional Set Size)
    實(shí)際使用的物理內(nèi)存(比例分配共享庫(kù)占用的內(nèi)存)
  • USS (Unique Set Size)
    進(jìn)程獨(dú)自占用的物理內(nèi)存(不包含共享庫(kù)占用的內(nèi)存)

一般來(lái)說(shuō)內(nèi)存占用大小有如下規(guī)律:VSS >= RSS >= PSS >= USS

PSS和USS反應(yīng)進(jìn)程的內(nèi)存占用比較有意義,PSS是按進(jìn)程數(shù)比例分配共享庫(kù)內(nèi)存牧愁,而USS是不包括共享庫(kù)內(nèi)存素邪,當(dāng)一個(gè)進(jìn)程被銷(xiāo)毀后,RSS是真實(shí)返回給系統(tǒng)的物理內(nèi)存递宅。
具體可以參考這里

4.1 dumpsys meminfo pid

dumpsys meminfo pid 是顯示指定pid進(jìn)程的PSS內(nèi)存占用詳細(xì)信息娘香,這里說(shuō)明不接任何參數(shù)的情況。

獲取指定pid進(jìn)程的內(nèi)存占用信息是通過(guò)Debug.getMemoryInfo(pid, mi)讀取整理/proc/pid/smaps下的數(shù)據(jù)办龄,而展示這些信息是在 ActivityThread.dumpMemInfoTable()中。

4.1.1 /proc/pid/smaps

/proc/pid/maps 展示指定pid進(jìn)程下的虛擬地址空間分布淋昭,而/proc/pid/smaps則是對(duì)每一虛擬地址區(qū)間(VMA)更詳細(xì)的展示俐填。

具體代碼在:

//fs/proc/task_mmu.c
static int show_smap(struct seq_file *m, void *v, int is_pid)

使用如下的調(diào)用關(guān)系遍歷一個(gè)VMA的所有頁(yè)框(page frame)對(duì)應(yīng)的struct page,統(tǒng)計(jì)的信息填充到 struct mem_size_stats結(jié)構(gòu)體翔忽。

walk_page_vma(vma, &smaps_walk) --> __walk_page_range() --> walk_pgd_range()

以下面例子說(shuō)明一個(gè)VMA展示字段的含義:

70d98c8000-70d98f0000 r-xp 00000000 fc:00 493                            /system/lib64/vndk-sp-28/libhwbinder.so  //VMA名稱(chēng)英融,這個(gè)關(guān)鍵字決定了這段VMA的類(lèi)型
Size:                160 kB    //該VMA占用的虛擬地址空間大小
Rss:                 132 kB    //實(shí)際占用的物理頁(yè)
Pss:                   3 kB    //獨(dú)占頁(yè)+按比例分配的共享頁(yè)
Shared_Clean:        132 kB    //共享頁(yè)(比如共享庫(kù)使用到的頁(yè))中符合 PageDirty(page) 的頁(yè)
Shared_Dirty:          0 kB    //共享頁(yè)(比如共享庫(kù)使用到的頁(yè))中不符合 PageDirty(page) 的頁(yè)
Private_Clean:         0 kB    //獨(dú)占頁(yè)中符合 PageDirty(page) 的頁(yè)
Private_Dirty:         0 kB    //獨(dú)占頁(yè)中不符合 PageDirty(page) 的頁(yè)
Referenced:          132 kB    //符合PageReferenced(page)的頁(yè)
Anonymous:             0 kB    //符合PageAnon(page)的頁(yè)
AnonHugePages:         0 kB
Shared_Hugetlb:        0 kB
Private_Hugetlb:       0 kB
Swap:                  0 kB    //swap的頁(yè)
SwapPss:               0 kB    //swap的頁(yè)按比例分配
KernelPageSize:        4 kB   
MMUPageSize:           4 kB
Locked:                0 kB

/proc/pid/smaps展示的所有VMA的詳細(xì)信息是 dumpsys meminfo pid 顯示的基礎(chǔ)。

4.1.2 dumpMemInfoTable

將PSS的占用分為三大類(lèi):

  • nativePss
    是native部分的PSS,對(duì)應(yīng)Debug.MemoryInfo.nativePss歇式,對(duì)應(yīng)HEAP_NATIVE類(lèi)型.
    /proc/pid/smaps中包含"[heap]","[anon:libc_malloc]"關(guān)鍵字的VMA都計(jì)作nativePss
  • dalvikPss
    是dalvik部分的PSS驶悟,對(duì)應(yīng)Debug.MemoryInfo.dalvikPss,對(duì)應(yīng)HEAP_DALVIK類(lèi)型.
    /proc/pid/smaps中包含"/dev/ashmem/"開(kāi)頭部分關(guān)鍵字的VMA計(jì)作dalvikPss材失,具體見(jiàn)read_mapinfo()痕鳍。
  • otherPss
    是除native和dalvik部分之外的PSS,對(duì)應(yīng)Debug.MemoryInfo.otherPss,對(duì)應(yīng)HEAP_UNKNOWN類(lèi)型笼呆,也對(duì)應(yīng)OTHER_DALVIK_OTHEROTHER_OTHER_MEMTRACK的17個(gè)子類(lèi)型.
    這17個(gè)子類(lèi)型都對(duì)應(yīng)/proc/pid/smaps中VMA名稱(chēng)的關(guān)鍵字熊响,具體見(jiàn)read_mapinfo()

每一類(lèi)型的Pss诗赌,又細(xì)分為下面這些部分汗茄,都是根據(jù)/proc/smaps下各VMA中頁(yè)類(lèi)型統(tǒng)計(jì)而來(lái)。

  • xxxPrivateDirty(Private_Dirty)
  • xxxSharedDirty(Shared_Dirty)
  • xxxPrivateClean(Private_Clean)
  • xxxSharedClean(Shared_Clean)
  • xxxSwappedOut(Swap)
  • xxxSwappedOutPss(SwapPss)

比如getTotalSwappedOutPss()就等于dalvikSwappedOutPss + nativeSwappedOutPss + otherSwappedOutPss铭若。

dumpsys meminfo pid 默認(rèn)的輸出主要有下面兩個(gè)部分:
1洪碳、PSS Summary

  • Native Heap
    對(duì)nativePss的展示,還包括堆的情況: Heap Size, Heap Alloc, Heap Free.
    調(diào)用了 mallinfo() --> je_mallinfo()獲取navite 堆的信息叼屠。
  • Dalvik Heap
    對(duì)dalvikPss的展示偶宫,還包括堆的情況: Heap Size, Heap Alloc, Heap Free.
    調(diào)用了Runtime.totalMemory()Runtime.freeMemory()來(lái)獲取dalvik 堆 Heap SizeHeap Free的信息。
  • Other
    對(duì)otherPss下17個(gè)子類(lèi)型Pss的展示环鲤,比如 Dalvik Other 對(duì)應(yīng)類(lèi)型OTHER_DALVIK_OTHER纯趋, 對(duì)于字段全位0的每一展示出來(lái)。
  • Total
    對(duì)上面所有部分的相加冷离。

2吵冒、App Summary

  • Java Heap:
    dalvikPss 和 OTHER_ART 類(lèi)型的PSS中PrivateDirty 部分之和。
  • Native Heap:
    nativePss 中 PrivateDirty 部分西剥。
  • Code:
    OTHER_SO,OTHER_JAR,OTHER_APK,OTHER_TTF,OTHER_DEX,OTHER_OAT類(lèi)型的PSS中PrivateDirty 部分之和痹栖。
  • Stack:
    OTHER_STACK 類(lèi)型的PSS中的 PrivateDirty 部分。
  • Graphics:
    OTHER_GL_DEV,OTHER_GRAPHICS,OTHER_GL類(lèi)型的PSS中PrivateDirty 部分之和瞭空。
  • Private Other:
    見(jiàn) Debug.getSummaryPrivateOther()
  • System:
    見(jiàn) Debug.getSummarySystem()

4.2 procrank

涉及的代碼如下:

system/extras/procrank/
system/extras/libpagemap/

procrank 主要展示所有用戶(hù)態(tài)進(jìn)程的VSS/RSS/PSS/USS情況揪阿,用到的信息是 /proc/pid/maps 下的進(jìn)程地址區(qū)間, 以及 使用/proc/pid/pagemap來(lái)得到物理頁(yè)使用情況咆畏。

int main(int argc, char *argv[]) {
    ....
    std::vector<proc_info> procs(num_procs);
    for (i = 0; i < num_procs; i++) {
        ....
        //pm_process_t *proc;
        //解析/proc/pid/maps下的進(jìn)程地址區(qū)間信息南捂,保存到proc->map[]數(shù)組中
        error = pm_process_create(ker, pids[i], &proc);
        ....
        //將 proc->map[]下的VMA信息轉(zhuǎn)換為 procs[i].usage 信息
        error = pm_process_usage_flags(proc, &procs[i].usage, flags_mask, required_flags);
        ....
    }
    //展示的VSS,RSS,PSS,USS 分別用到了 proc.usage.vss,proc.usage.rss,proc.usage.pss,proc.usage.uss
}
4.2.1 /proc/pid/maps

/proc/pid/maps 展示指定pid進(jìn)程下的虛擬地址空間分布,每一行對(duì)應(yīng)一段虛擬地址空間旧找,在Kernel 中對(duì)應(yīng)一個(gè) struct vm_area_struct結(jié)構(gòu)溺健,稱(chēng)為一個(gè)VMA(virtual memory area)

一行信息包括該VMA的起始地址钮蛛,結(jié)束地址鞭缭,權(quán)限,偏移量魏颓,路徑等岭辣。示例如下:

70d965c000-70d9662000 r--p 000fa000 fc:00 523                            /system/lib64/libc.so
7fc89fe000-7fc8a1f000 rw-p 00000000 00:00 0                              [stack]
4.2.2 /proc/pid/pagemap

/proc/pid/maps得到所有的VMA之后,還需要知道VMA對(duì)應(yīng)的物理內(nèi)存情況甸饱,因?yàn)檫M(jìn)程分配一個(gè)VMA后沦童,只是得到了一段虛擬地址空間,只有在真正使用內(nèi)存時(shí)才會(huì)分配對(duì)應(yīng)的物理內(nèi)存。

/proc/pid/pagemap 就是用來(lái)查詢(xún)一個(gè)VMA的物理內(nèi)存情況搞动。 seek到 /proc/pid/pagemap虛擬地址區(qū)間的起始位置躏精,就能讀到該段線(xiàn)性地址區(qū)間的物理頁(yè)情況,每一個(gè)64位int描述了一個(gè)物理頁(yè)的情況鹦肿。詳細(xì)見(jiàn)pagemap文檔矗烛。

pagemap具體實(shí)現(xiàn)代碼在:

//proc/task_mmu.c
static ssize_t pagemap_read(struct file *file, char __user *buf,
size_t count, loff_t *ppos)

procrank 流程中關(guān)鍵的轉(zhuǎn)換 pagemap 就是在 pm_map_usage_flags()中,從其中可以看出VSS/RSS/PSS/USS的具體差異箩溃。

//map:描述一個(gè)VMA
int pm_map_usage_flags(pm_map_t *map, pm_memusage_t *usage_out,
                        uint64_t flags_mask, uint64_t required_flags) {
    //使用 /proc/pid/pagemap 得到該VMA映射的物理頁(yè)的信息瞭吃,
    //每一個(gè)物理頁(yè)的情況是一個(gè)uint64_t,保存在pagemap[]中涣旨,len是該VMA所占的頁(yè)數(shù)
    error = pm_map_pagemap(map, &pagemap, &len);
    ....
    //遍歷該VMA下的每一物理頁(yè)(pagemap[i])
    for (i = 0; i < len; i++) {
        //只要分配了地址空間都計(jì)算在VSS中歪架,無(wú)論物理上是否分配(物理頁(yè)不存在)
        usage.vss += map->proc->ker->pagesize;
        
        //如果該物理頁(yè)不存在或者該物理頁(yè)沒(méi)有被swap,則繼續(xù)
        if (!PM_PAGEMAP_PRESENT(pagemap[i]) && !PM_PAGEMAP_SWAPPED(pagemap[i]))
              continue;
       ....
        //通過(guò)該頁(yè)P(yáng)FN得到該物理頁(yè)被映射的次數(shù)count霹陡,通過(guò)/proc/pid/kpagecount節(jié)點(diǎn)
        error = pm_kernel_count(map->proc->ker, PM_PAGEMAP_PFN(pagemap[i]),&count);
       ....
        //只要映射過(guò)和蚪,該頁(yè)就計(jì)算在RSS中
        usage.rss += (count >= 1) ? map->proc->ker->pagesize : (0);
        //按映射的次數(shù)比例分配該頁(yè)的大小,計(jì)算在PSS中
        usage.pss += (count >= 1) ? (map->proc->ker->pagesize / count) : (0);
        //只有被映射過(guò)一次烹棉,才計(jì)算在USS中攒霹,即映射多次的頁(yè)(比如共享庫(kù))比計(jì)算在USS中
        usage.uss += (count == 1) ? (map->proc->ker->pagesize) : (0);
        ....
    }
}

留個(gè)問(wèn)題:
為什么 dumpsys meminfo pid 顯示的進(jìn)程PSS大小,比procrank展示的進(jìn)程PSS大小多浆洗?
是多統(tǒng)計(jì)了swap?

5催束、總結(jié)

回應(yīng)一下文本開(kāi)頭所提的問(wèn)題。
1伏社、為什么/proc/meminfo中的內(nèi)存總大小比物理內(nèi)存小?
/proc/meminfo中的MemTotal相比物理內(nèi)存少了兩個(gè)部分:

  • kernel進(jìn)入前預(yù)申請(qǐng)的部分抠刺,此時(shí)memblock還未初始化。這部分的大小可以統(tǒng)計(jì) /d/memblock/memory相對(duì)物理內(nèi)存少的部分摘昌。
  • kernel reserved的部分速妖。這部分內(nèi)存的大小可以統(tǒng)計(jì)/d/memblock/reserved中的各部分之和。

2第焰、怎么看Android還剩多少可用內(nèi)存比較準(zhǔn)確买优?
查看dumpsys meminfoFree RAM 的部分相對(duì)準(zhǔn)確, 而/proc/meminfo中的MemAvailable只是一個(gè)理論上通過(guò)回收能達(dá)到的最大值挺举,實(shí)際上很難達(dá)到。

3烘跺、怎么看Kernel的內(nèi)存占用比較準(zhǔn)確湘纵?
查看dumpsys meminfoUsed RAM下的kernel部分的大小,它也是/proc/meminfo中下面部分的和: Shmem + SUnreclaim + VmallocUsed + PageTables + KernelStack滤淳。
但是這部分的統(tǒng)計(jì)比實(shí)際占用要多梧喷,主要在VmallocUsed的統(tǒng)計(jì),詳細(xì)看 3.1.5節(jié)

4、是哪些因素影響了Lost RAM的大衅痰小汇歹?
有一個(gè)因素是Android 對(duì) kernel的內(nèi)存占用統(tǒng)計(jì)存在偏差皆怕,這有體現(xiàn)在對(duì)/proc/vmalloc中內(nèi)存區(qū)間的統(tǒng)計(jì)讨惩。

5、怎么看一個(gè)進(jìn)程的內(nèi)存占用比較合適床估?
一個(gè)進(jìn)程的內(nèi)存占用有VSS/RSS/PSS/USS之分弯囊,USS是進(jìn)程的獨(dú)占物理內(nèi)存大小痰哨,PSS則是USS加上按比例分配的共享庫(kù)內(nèi)存,相對(duì)來(lái)說(shuō)PSS更加合適匾嘱。
通過(guò)dumpsys meminfo pid查看指定進(jìn)程的PSS情況斤斧。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市霎烙,隨后出現(xiàn)的幾起案子撬讽,更是在濱河造成了極大的恐慌,老刑警劉巖悬垃,帶你破解...
    沈念sama閱讀 207,248評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件游昼,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡盗忱,警方通過(guò)查閱死者的電腦和手機(jī)酱床,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)趟佃,“玉大人扇谣,你說(shuō)我怎么就攤上這事∠姓眩” “怎么了罐寨?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,443評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)序矩。 經(jīng)常有香客問(wèn)我鸯绿,道長(zhǎng),這世上最難降的妖魔是什么簸淀? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,475評(píng)論 1 279
  • 正文 為了忘掉前任瓶蝴,我火速辦了婚禮,結(jié)果婚禮上租幕,老公的妹妹穿的比我還像新娘舷手。我一直安慰自己,他們只是感情好劲绪,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,458評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布男窟。 她就那樣靜靜地躺著盆赤,像睡著了一般。 火紅的嫁衣襯著肌膚如雪歉眷。 梳的紋絲不亂的頭發(fā)上牺六,一...
    開(kāi)封第一講書(shū)人閱讀 49,185評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音汗捡,去河邊找鬼淑际。 笑死,一個(gè)胖子當(dāng)著我的面吹牛凉唐,可吹牛的內(nèi)容都是我干的庸追。 我是一名探鬼主播,決...
    沈念sama閱讀 38,451評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼台囱,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼淡溯!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起簿训,我...
    開(kāi)封第一講書(shū)人閱讀 37,112評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤咱娶,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后强品,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體膘侮,經(jīng)...
    沈念sama閱讀 43,609評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,083評(píng)論 2 325
  • 正文 我和宋清朗相戀三年的榛,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了琼了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,163評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡夫晌,死狀恐怖雕薪,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情晓淀,我是刑警寧澤所袁,帶...
    沈念sama閱讀 33,803評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站凶掰,受9級(jí)特大地震影響燥爷,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜懦窘,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,357評(píng)論 3 307
  • 文/蒙蒙 一前翎、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧畅涂,春花似錦鱼填、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,357評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至苇经,卻和暖如春赘理,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背扇单。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,590評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工商模, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蜘澜。 一個(gè)月前我還...
    沈念sama閱讀 45,636評(píng)論 2 355
  • 正文 我出身青樓施流,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親鄙信。 傳聞我的和親對(duì)象是個(gè)殘疾皇子瞪醋,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,925評(píng)論 2 344