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()函数,该函数会进行常规的撤销工作,更新一些统计量。
应用程序操作的对象时映射到物理内存之上的虚拟内存,而处理器直接操作的是物理内存。故应用程序访问一个虚拟地址时,需要将虚拟地址转换为物理地址,然后处理器才能解析地址访问请求,这个转换工作通过查询页表完成。
Linux使用三级页表完成地址转换。
-
顶级页表:页全局目录(PGD),指向二级页目录;
-
二级页表:中间页目录(PMD),指向PTE中的表项;
-
最后一级:页表(PTE),指向物理页面。
多数体系结构,搜索页表工作由硬件完成。每个进程都有自己的页表(线程会共享页表)。为了加快搜索,实现了翻译后缓冲器(TLB),作为将虚拟地址映射到物理地址的硬件缓存。还有写时拷贝方式共享页表,当fork()时,父子进程共享页表,只有当子进程或父进程试图修改特定页表项时,内核才创建该页表项的新拷贝,之后父子进程不再共享该页表项。可见,利用共享页表可以消除fork()操作中页表拷贝所带来的消耗。
进程与内存
所有进程都必须占用一定数量的内存,这些内存用来存放从磁盘载入的程序代码,或存放来自用户输入的数据等。内存可以提前静态分配和统一回收,也可以按需动态分配和回收。
对于普通进程对应的内存空间包含5种不同的数据区:
进程内存空间
Linux采用虚拟内存管理技术,每个进程都有各自独立的进程地址空间(即4G的线性虚拟空间),无法直接访问物理内存。这样起到保护操作系统,并且让用户程序可使用比实际物理内存更大的地址空间。
-
4G进程地址空间被划分两部分,内核空间和用户空间。用户空间从0到3G,内核空间从3G到4G;
-
用户进程通常情况只能访问用户空间的虚拟地址,不能访问内核空间虚拟地址。只有用户进程进行系统调用(代表用户进程在内核态执行)等情况可访问到内核空间;
-
用户空间对应进程,所以当进程切换,用户空间也会跟着变化;
-
内核空间是由内核负责映射,不会跟着进程变化;内核空间地址有自己对应的页表,用户进程各自有不同额页表。
进程分配内存,陷入内核态分别由brk和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。
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
来源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。
1.使用root大师root
2.安装Swapper2,设定大小位置,自动启动
3.设定自动启动,红米->安全中心->授权管理->自动启动应用管理->手工添加->找到Swapper2
4.查看效果cmd-> adb shell -> busybox free
查看Swap
Swap: 262140 0 262140
5.此Swapper2已去广告,并增加选项
对于 CPU 执行指令来说有三种重要的寄存器:
PC 寄存器(Program Counter Register:程序计数器),我们也称作指令地址寄存器(Instruction Address Register)。顾名思义,他就是用来存储下一条指令的地址的。
指令寄存器(Instruction Register): 存储当前正在执行的指令。
条件寄存器(Status Register),用里面的一个一个标记位(Flag),存放 CPU 进行算术或者逻辑计算的结果。
原因:实现内存隔离,进程A的虚拟地址和进程B的虚拟地址不同,这样就防止了进程A将数据信息写入进程B
1.每个进程的4G内存空间只是虚拟内存空间,每次访问内存空间的某个地址,都需要把地址翻译为实际物理内存地址
2.所有进程共享同一物理内存,每个进程只把自己目前需要的虚拟内存空间映射并存储到物理内存上。
3.进程要知道哪些内存地址上的数据在物理内存上,哪些不在,还有在物理内存上的哪里,需要用页表来记录
4.页表的每一个表项分两部分,...
非连续分配管理方式
内存管理有块式管理,页式管理,段式和段页式管理。现在常用段页式管理。
块式管理:把主存分为一大块、一大块的,当所需的程序片断不在主存时就分配一块主存空间,把程序片断load入主存,就算所需的程序片度只有几个字节也只能把这一块分配给它。这样会造成很大的浪费,平均浪费了50%的内存空间,但是易于管理。
页式管理:把主存分为一页一页的,每一页的空间要比一块一块的空间小很多,显然这种方法的空间利用率要比块式管理高很多。
段式管理:把主存分为一段一段的,每一段的空间又要比一
虚拟内存如果在程序被挂起或被换出前仅仅使用了一部分进程快,那么为该进程给内存中装入太多的块显然会带来巨大的浪费。而虚拟内存借助磁盘和内存交换,仅仅装入这小部分块来更好地使用内存,然后,如果程序转移到或访问到不在内存中的某个快中的指令或数据时,就会引发一个中断,告诉操作系统读取需要的块。
我们知道进程中的所有内存访问都是逻辑地址,这些逻辑地址在运行时动态的被转换成物理地址,而这意味着一个进程
02-04 13:15:03.661 1070 1087 W libc : pthread_create failed: couldn't allocate 1069056-bytes mapped space: Out of memory
02-04 13:15:03.661