Linux 的虛擬內(nèi)存、物理內(nèi)存、磁盤
為什么要有虛擬內(nèi)存的概念
- 進(jìn)程創(chuàng)建時,會分配4G的虛擬內(nèi)存,如果分配物理內(nèi)存的話,物理內(nèi)存很快就會分配完。
- 由于指令都是直接訪問物理內(nèi)存的,那么我這個進(jìn)程就可以修改其他進(jìn)程的數(shù)據(jù),甚至?xí)薷膬?nèi)核地址空間的數(shù)據(jù),這是我們不想看到的。
- 因?yàn)閮?nèi)存時隨機(jī)分配的,所以程序運(yùn)行的地址也是不正確的。
什么是頁表
進(jìn)程需要知道哪些地址空間上的數(shù)據(jù)在物理內(nèi)存上,哪些不在(可能這部分存儲在磁盤上),還有在物理內(nèi)存上的哪里,這就需要通過頁表來記錄
頁表的每一個表項(xiàng)分兩部分,第一部分記錄此頁是否在物理內(nèi)存上,第二部分記錄物理內(nèi)存頁的地址(如果在的話)

虛擬內(nèi)存、頁表、物理內(nèi)存、磁盤的工作方式
在進(jìn)程創(chuàng)建時,內(nèi)核會分配4G虛擬內(nèi)存,當(dāng)進(jìn)程沒有開始運(yùn)行時,是不會將磁盤上的程序數(shù)據(jù)和代碼等拷貝到物理內(nèi)存中,只是建立好虛擬內(nèi)存和磁盤之間的映射關(guān)系。
- 每次我要訪問地址空間上的某一個地址,都需要把地址翻譯為實(shí)際物理內(nèi)存地址
所有進(jìn)程共享這整一塊物理內(nèi)存,每個進(jìn)程只把自己目前需要的虛擬地址空間映射到物理內(nèi)存上 - 當(dāng)進(jìn)程訪問某個虛擬地址的時候,就會先去看頁表,如果發(fā)現(xiàn)對應(yīng)的數(shù)據(jù)不在物理內(nèi)存上,就會發(fā)生缺頁異常
- 缺頁異常的處理過程,操作系統(tǒng)立即阻塞該進(jìn)程,并將硬盤里對應(yīng)的頁換入內(nèi)存,然后使該進(jìn)程就緒,如果內(nèi)存已經(jīng)滿了,沒有空地方了,那就找一個頁覆蓋,至于具體覆蓋的哪個頁,就需要看操作系統(tǒng)的頁面置換算法是怎么設(shè)計(jì)的了。

mmap
在LINUX中我們可以使用mmap用來在進(jìn)程虛擬內(nèi)存地址空間中分配地址空間,創(chuàng)建和物理內(nèi)存的映射關(guān)系。
映射關(guān)系
映射的內(nèi)容
- 文件映射: 磁盤文件映射進(jìn)程的虛擬地址空間,第一次使用時使用文件內(nèi)容初始化物理內(nèi)存。
- 匿名映射:初始化全為0的內(nèi)存空間。
映射是否共享
- 私有映射(MAP_PRIVATE) : 多進(jìn)程間數(shù)據(jù)共享,修改不反應(yīng)到磁盤實(shí)際文件,是一個copy-on-write(寫時復(fù)制)的映射方式。
- 共享映射(MAP_SHARED): 多進(jìn)程間數(shù)據(jù)共享,修改反應(yīng)到磁盤實(shí)際文件中。
總共4種映射關(guān)系
私有文件映射:
多個進(jìn)程使用同樣的物理內(nèi)存頁進(jìn)行初始化,但是各個進(jìn)程對內(nèi)存文件的修改不會共享,也不會反應(yīng)到物理文件中私有匿名映射:
mmap會創(chuàng)建一個新的映射,各個進(jìn)程不共享,這種使用主要用于分配內(nèi)存(malloc分配大內(nèi)存會調(diào)用mmap)。
例如開辟新進(jìn)程時,會為每個進(jìn)程分配虛擬的地址空間,這些虛擬地址映射的物理內(nèi)存空間各個進(jìn)程間讀的時候共享,寫的時候會copy-on-write。共享文件映射:
多個進(jìn)程通過虛擬內(nèi)存技術(shù)共享同樣的物理內(nèi)存空間,對內(nèi)存文件 的修改會反應(yīng)到實(shí)際物理文件中,他也是進(jìn)程間通信(IPC)的一種機(jī)制。共享匿名映射:
這種機(jī)制在進(jìn)行fork的時候不會采用寫時復(fù)制,父子進(jìn)程完全共享同樣的物理內(nèi)存頁,這也就實(shí)現(xiàn)了父子進(jìn)程通信(IPC).
mmap和new/malloc 的關(guān)系
new/malloc 是C++/C語言層面的事情,實(shí)際在類linux的操作系統(tǒng)層面,給用戶提供的申請內(nèi)存的函數(shù)只有brk/sbrk和mmap函數(shù)。
- sbrk 作用就是擴(kuò)展heap的上界,可以傳入一個分配大小,并返回新的brk的地址。
- Malloc函數(shù)當(dāng)申請小內(nèi)存時使用sbrk來分配內(nèi)存,大內(nèi)存則使用mmap申請,如果是這種情況,這時的malloc并沒有申請物理內(nèi)存的占用 。但實(shí)際上大部分malloc的實(shí)現(xiàn)都會在操作系統(tǒng)內(nèi)再維護(hù)一個內(nèi)存池,它會預(yù)先申請一塊較大的連續(xù)內(nèi)存復(fù)用,最終都是走的mmap。

mmap在write和read時會發(fā)生什么
write
- 進(jìn)程(用戶態(tài))將需要寫入的數(shù)據(jù)直接copy到對應(yīng)的mmap地址(內(nèi)存copy)
- 若mmap地址未對應(yīng)物理內(nèi)存,則產(chǎn)生缺頁異常,由內(nèi)核處理
- 若已對應(yīng),則直接copy到對應(yīng)的物理內(nèi)存
- 由操作系統(tǒng)調(diào)用,將臟頁回寫到磁盤(通常是異步的)
read

從圖中可以看出,mmap要比普通的read系統(tǒng)調(diào)用少了一次copy的過程。因?yàn)閞ead調(diào)用,進(jìn)程是無法直接訪問kernel space的,所以在read系統(tǒng)調(diào)用返回前,內(nèi)核需要將數(shù)據(jù)從內(nèi)核復(fù)制到進(jìn)程指定的buffer。但mmap之后,進(jìn)程可以直接訪問mmap的數(shù)據(jù)(page cache)。
mmap 性能總結(jié)
優(yōu)點(diǎn)
對文件的讀取操作跨過了頁緩存,減少了數(shù)據(jù)的拷貝次數(shù),用內(nèi)存讀寫取代I/O讀寫,提高了文件讀取效率。
實(shí)現(xiàn)了用戶空間和內(nèi)核空間的高效交互方式。兩空間的各自修改操作可以直接反映在映射的區(qū)域內(nèi),從而被對方空間及時捕捉。
提供進(jìn)程間共享內(nèi)存及相互通信的方式。不管是父子進(jìn)程還是無親緣關(guān)系的進(jìn)程,都可以將自身用戶空間映射到同一個文件或匿名映射到同一片區(qū)域。從而通過各自對映射區(qū)域的改動,達(dá)到進(jìn)程間通信和進(jìn)程間共享的目的。同時,如果進(jìn)程A和進(jìn)程B都映射了區(qū)域C,當(dāng)A第一次讀取C時通過缺頁從磁盤復(fù)制文件頁到內(nèi)存中;但當(dāng)B再讀C的相同頁面時,雖然也會產(chǎn)生缺頁異常,但是不再需要從磁盤中復(fù)制文件過來,而可直接使用已經(jīng)保存在內(nèi)存中的文件數(shù)據(jù)。
可用于實(shí)現(xiàn)高效的大規(guī)模數(shù)據(jù)傳輸。內(nèi)存空間不足,是制約大數(shù)據(jù)操作的一個方面,解決方案往往是借助硬盤空間協(xié)助操作,補(bǔ)充內(nèi)存的不足。但是進(jìn)一步會造成大量的文件I/O操作,極大影響效率。這個問題可以通過mmap映射很好的解決。換句話說,但凡是需要用磁盤空間代替內(nèi)存的時候,mmap都可以發(fā)揮其功效。
缺點(diǎn)
- 文件如果很小,是小于4096字節(jié)的,比如10字節(jié),由于內(nèi)存的最小粒度是頁,而進(jìn)程虛擬地址空間和內(nèi)存的映射也是以頁為單位。雖然被映射的文件只有10字節(jié),但是對應(yīng)到進(jìn)程虛擬地址區(qū)域的大小需要滿足整頁大小,因此mmap函數(shù)執(zhí)行后,實(shí)際映射到虛擬內(nèi)存區(qū)域的是4096個字節(jié),11~4096的字節(jié)部分用零填充。因此如果連續(xù)mmap小文件,會浪費(fèi)內(nèi)存空間。
- 對變長文件不適合,文件無法完成拓展,因?yàn)閙map到內(nèi)存的時候,你所能夠操作的范圍就確定了。
- 如果更新文件的操作很多,會觸發(fā)大量的臟頁回寫及由此引發(fā)的隨機(jī)IO上。所以在隨機(jī)寫很多的情況下,mmap方式在效率上不一定會比帶緩沖區(qū)的一般寫快。
進(jìn)程中的內(nèi)存
android 系統(tǒng)給app進(jìn)程分配的的內(nèi)存都有上限,如果需要增大內(nèi)存限制,有以下方案:
- 則在AndroidManifest.xml中的application標(biāo)簽加上 android:largeHeap="true"
- 創(chuàng)建子進(jìn)程。創(chuàng)建一個新的進(jìn)程,那么我們就可以把一些對象分配到新進(jìn)程的heap上了,從而達(dá)到一個應(yīng)用程序使用更多的內(nèi)存的目的。
- 使用jni在native heap上申請空間(推薦使用)。nativeheap的增長并不受dalvik vm heapsize的限制。只要RAM有剩余空間,程序員可以一直在native heap上申請空間,當(dāng)然如果 RAM快耗盡,memory killer會殺進(jìn)程釋放RAM。
- 使用顯存。使用 OpenGL textures 等 API , texture memory 不受 dalvik vm heapsize 限制。
內(nèi)存示例(圖中的所占內(nèi)存都是指物理內(nèi)存)
adb shell dumpsys meminfo [包名]


名詞解釋
Pss(proportional[比例的] set size)Total:進(jìn)程實(shí)際占用的物理內(nèi)存大小,但是android內(nèi)存涉及到在系統(tǒng)中同其他進(jìn)程共享一部分庫(可能是so,可能是字體文件等的mmap),所以這里面會考慮這個因素計(jì)算你的進(jìn)程平攤的這部分共享庫的大小,這里的p就意味著統(tǒng)計(jì)了這個平攤后的物理內(nèi)存。這是衡量進(jìn)程占用內(nèi)存的最真實(shí)指標(biāo)。
Private Dirty 和 Private Clean:則是完全該進(jìn)程自己(而不包括和別人共享部分)占用的物理內(nèi)存,clean是指那部分可能被swap的內(nèi)存部分(即前面說到的擁有backup文件,mmap之后一直保持只讀狀態(tài),他們具有被swap out的可能,比如你的so庫文件。),而dirty部分就是除clean之外那些不能被swap的內(nèi)存。Private Dirty通常是你的進(jìn)程內(nèi)存最需要優(yōu)化的地方,因?yàn)樗麄兪谴箢^。
Swapped Dirty:指的并不是被swap-out出去的內(nèi)存,而是android系統(tǒng)中zram機(jī)制壓縮掉的部分Private Dirty部分的不常用物理內(nèi)存,這個重要度等同于Private Dirty,因?yàn)槟男㏄rivate Dirty會被壓縮不能被控制,所以這部分多通常也是Private Dirty 的。
Native Heap:這是C/C++層直接通過malloc分配的內(nèi)存
Dalvik Heap:虛擬機(jī)堆,由java層new出來的對象放在這里
Dalvik other:???
Stack: 棧分配內(nèi)存
Ashmem: 進(jìn)程的匿名共享內(nèi)存 Anonymous Shared Memory ,通常不會很大,和操作系統(tǒng)有關(guān)系
GFX dev :通俗來說是你的顯存,android 顯存和內(nèi)存在同樣的物理設(shè)備上,所以統(tǒng)計(jì)的總內(nèi)存是包括顯存
Other Dev: 除顯卡外所有其他硬件設(shè)備的mmap后的物理內(nèi)存,可能包括聲卡等,通常不多。
.so mmap: 這個就是so庫本身文件mmap占用的物理內(nèi)存,我們隨著游戲進(jìn)度會逐漸的讀取我們的so文件,造成和缺頁的部分就是在物理內(nèi)存產(chǎn)生占用,這部分大就是so庫太大了,但是這部分因?yàn)橛泻芏嗍莚eadonly的mmap,所以有更大的機(jī)會被swap-out出去。
.apk .dex oat .art mmap: 這些都是android 程序文件本身被mmap占用的內(nèi)存,和so的性質(zhì)差不多。
Other mmap: 是所有除了上面的之外其他的所有非匿名方式的mmap
Unkonwn: 所有的匿名mmap
注意點(diǎn):
- PSS中已經(jīng)包含了Private Dirty和Private Clean,但是沒包含swapped dity,所以最終衡量你的進(jìn)程對物理內(nèi)存的占用應(yīng)該是取PSS+Swapped Dirty
JAVA 虛擬機(jī)進(jìn)程中的內(nèi)存結(jié)構(gòu)

-
程序計(jì)數(shù)器(Program Counter Register):
- 是一塊較小的內(nèi)存空間,它可以看作是當(dāng)前線程所執(zhí)行的Java字節(jié)碼的行號指示器。在虛擬機(jī)的概念模型里,字節(jié)碼解釋器工作時就是通過改變這個計(jì)數(shù)器的值來選取下一條需要執(zhí)行的字節(jié)碼指令,分支、循環(huán)、跳轉(zhuǎn)、異常處理、線程恢復(fù)等基礎(chǔ)功能都需要依賴這個計(jì)數(shù)器來完成。
- 由于Java虛擬機(jī)的多線程是通過線程輪流切換并分配處理器執(zhí)行時間的方式來實(shí)現(xiàn)的,在任何一個確定的時刻,一個處理器都只會執(zhí)行一條線程中的指令。因此,為了線程切換后能恢復(fù)到正確的執(zhí)行位置,每條線程都需要有一個獨(dú)立的程序計(jì)數(shù)器,各條線程之間計(jì)數(shù)器互不影響,獨(dú)立存儲,我們稱這類內(nèi)存區(qū)域?yàn)椤熬€程私有”的內(nèi)存。
- 如果線程正在執(zhí)行的是一個Java方法,這個計(jì)數(shù)器記錄的是正在執(zhí)行的虛擬機(jī)字節(jié)碼指令的地址;如果正在執(zhí)行的是Native方法,這個計(jì)數(shù)器值則為空(Undefined)。此內(nèi)存區(qū)域是唯一 一個在Java虛擬機(jī)規(guī)范中沒有規(guī)定OutOfMemoryError情況的區(qū)域。
-
Java虛擬機(jī)棧
- 與程序計(jì)數(shù)器一樣,Java虛擬機(jī)棧(Java Virtual Machine Stacks)也是線程私有的,它的生命周期與線程相同。虛擬機(jī)棧描述的是Java方法執(zhí)行的內(nèi)存模型:每個方法在執(zhí)行的同時都會創(chuàng)建一個棧幀(Stack Frame)用于存儲局部變量表、操作數(shù)棧、動態(tài)鏈接、方法出口等信息。每一個方法從調(diào)用直至執(zhí)行完成的過程,就對應(yīng)著一個棧幀在虛擬機(jī)棧中入棧到出棧的過程。
- 經(jīng)常有人把Java內(nèi)存區(qū)分為堆內(nèi)存(Heap)和棧內(nèi)存(Stack),其中所指的“堆”就是Java堆,而所指的“?!本褪乾F(xiàn)在所講的虛擬機(jī)棧,或者說是虛擬機(jī)棧中局部變量表部分。
- 局部變量表存放了編譯期可知的各種基本數(shù)據(jù)類型(boolean、byte、char、short、int、float、long、double)、對象引用(reference類型,它不等同于對象本身,可能是一個指向?qū)ο笃鹗嫉刂返囊弥羔?,也可能是指向一個代表對象的句柄或其他與此對象相關(guān)位置)和returnAddress類型(指向了一條字節(jié)碼指令的地址)。
- 其中64為長度的long和double類型的數(shù)據(jù)會占用2個局部變量空間(Slot),其余的數(shù)據(jù)類型只占用1個。局部變量表所需的內(nèi)存空間在編譯期間完成分配,當(dāng)進(jìn)入一個方法時,這個方法需要在幀中分配多大的局部變量空間是完全確定的,在方法運(yùn)行期間不會改變局部變量表的大小。
- 在Java虛擬機(jī)規(guī)范中,對這個區(qū)域規(guī)定了兩種異常狀況:如果線程請求的棧深度大于虛擬機(jī)所允許的深度,將拋出StackOverflowError異常;如果虛擬機(jī)??梢詣討B(tài)擴(kuò)展(當(dāng)前大部分的Java虛擬機(jī)都可動態(tài)擴(kuò)展,只不過Java虛擬機(jī)規(guī)范中也允許固定長度的虛擬機(jī)棧),如果擴(kuò)展時無法申請到足夠的內(nèi)存,就會拋出OutOfMemoryError異常。
-
本地方法棧
- 本地方法棧(Native Method Stack)與虛擬機(jī)棧所發(fā)揮的作用是非常相似的,它們之間的區(qū)別不過是虛擬機(jī)棧為虛擬機(jī)執(zhí)行Java方法(也就是字節(jié)碼)服務(wù),而本地方法棧則為虛擬機(jī)使用到的Native方法服務(wù)。在虛擬機(jī)規(guī)范中對本地方法棧中方法使用的語言、使用方式與數(shù)據(jù)結(jié)構(gòu)并沒有強(qiáng)制規(guī)定。HotSpot虛擬機(jī)直接把本地方法棧和虛擬機(jī)棧合二為一。與虛擬機(jī)棧一樣,本地方法棧區(qū)域也會拋出StackOverflowError和OutOfMemoryError異常。
-
Java堆
- 對于大多數(shù)應(yīng)用來說,Java堆(Java Heap)是Java虛擬機(jī)所管理的內(nèi)存中最大的一塊。Java堆是被所有線程共享的一塊內(nèi)存區(qū)域,在虛擬機(jī)啟動時創(chuàng)建。此內(nèi)存區(qū)域的唯一目的就是存放對象實(shí)例,幾乎所有的對象實(shí)例都在這里分配內(nèi)存。這一點(diǎn)在Java虛擬機(jī)規(guī)范中的描述是:所有的對象實(shí)例以及數(shù)組都要在堆上分配,但是隨著JIT編譯器的發(fā)展以及逃逸分析技術(shù)逐漸成熟,棧上分配、標(biāo)量替換優(yōu)化技術(shù)將會導(dǎo)致一些微妙的變化發(fā)生,所有的對象都分配在堆上也漸漸變得不是那么“絕對”了。
- Java堆是垃圾收集器管理的主要區(qū)域,因此很多時候也被稱做“GC堆”(Garbage Collected Heap)。從內(nèi)存回收的角度來看,由于現(xiàn)在收集器基本都采用分代收集算法,所以Java堆中還可以細(xì)分為:新生代和老年代;再細(xì)致一點(diǎn)的,新生代可以有Eden空間、From Survivor空間、To Survivor空間等。從內(nèi)存分配的角度來看,線程共享的Java堆中可能劃分出多個線程私有的分配緩沖區(qū)(Thread Local Allocation Buffer,TLAB)。不過無論如何劃分,都與存放內(nèi)容無關(guān),無論哪個區(qū)域,存儲的都仍然是對象實(shí)例,進(jìn)一步劃分的目的是為了更好地回收內(nèi)存,或者更快地分配內(nèi)存。
- 根據(jù)Java虛擬機(jī)規(guī)范的規(guī)定,Java堆可以處于物理上不連續(xù)的內(nèi)存空間中,只要邏輯上是連續(xù)的即可,就像我們的磁盤空間一樣。在實(shí)現(xiàn)時,既可以實(shí)現(xiàn)成固定大小的,也可以是擴(kuò)展的,不過當(dāng)前主流的虛擬機(jī)都是按照可擴(kuò)展來實(shí)現(xiàn)的(通過-Xmx和-Xms控制)。如果在堆中沒有內(nèi)存完成實(shí)例分配,并且堆也無法再擴(kuò)展時,將會拋出OutOfMemoryError異常。
-
方法區(qū) (永久代)
方法區(qū)(Method Area)與Java堆一樣,是各個線程共享的內(nèi)存區(qū)域,它用于存儲已被虛擬機(jī)加載的類信息、常量、靜態(tài)變量、即時編譯器編譯后的代碼等數(shù)據(jù),即存放靜態(tài)文件,如Java類、方法等。雖然Java虛擬機(jī)規(guī)范把方法區(qū)描述為堆的一個邏輯部分,但是它卻有一個別名叫做Non-Heap(非堆),目的應(yīng)該是與Java堆區(qū)分開來。-
jdk7 以前的實(shí)現(xiàn)如下:
1217276-20190418165715298-666699733.png - jdk7:
在目前已經(jīng)發(fā)布的JDK1.7的HotSpot中,已經(jīng)把原本放在永久代的字符串常量池移到了Java堆中。 -
jdk8:
jdk8版本中則把永久代給完全刪除了,取而代之的是MetaSpace。
運(yùn)行時常量池和靜態(tài)變量都存儲到了堆中,MetaSpace存儲類的元數(shù)據(jù),MetaSpace直接在本地內(nèi)存中(Native memory),這樣類的元數(shù)據(jù)分配只受本地內(nèi)存大小的限制,OOM問題就不存在了。
1217276-20190418171349818-952181838.png
-
-
運(yùn)行時常量池
- 運(yùn)行時常量池(Runtime Constant Pool)是方法區(qū)的一部分。Class文件中除了有類的版本、字段、方法、接口等描述信息外,還有一項(xiàng)信息是常量池(Constant Pool Table),用于存放編譯期生成的各種字面量和符號引用,這部分內(nèi)容將在類加載后進(jìn)入方法區(qū)的運(yùn)行時常量池中存放。
- 運(yùn)行時常量池相對于Class文件常量池的另外一個重要特征是具備動態(tài)性,Java語言并不要求常量一定只有編譯期才能產(chǎn)生,也就是并非預(yù)置入Class文件中常量池的內(nèi)容才能進(jìn)入方法區(qū)運(yùn)行時常量池,運(yùn)行期間也可能將新的常量放入池中,這種特性被開發(fā)人員利用得比較多的便是String類的intern()方法。
- 既然運(yùn)行時常量池是方法區(qū)的一部分,自然受到方法區(qū)內(nèi)存的限制,當(dāng)常量池?zé)o法再申請到內(nèi)存時會拋出OutOfMemoryError異常。
-
直接內(nèi)存(堆外內(nèi)存)
- 直接內(nèi)存(Direct Memory),也叫堆外內(nèi)存,它并不是虛擬機(jī)運(yùn)行時數(shù)據(jù)區(qū)的一部分,也不是Java虛擬機(jī)規(guī)范中定義的內(nèi)存區(qū)域,而是Java虛擬機(jī)的堆以外的內(nèi)存,直接受操作系統(tǒng)管理。但是這部分內(nèi)存也被頻繁地使用,而且也可能導(dǎo)致OutOfMemoryError異常出現(xiàn)。使用堆外內(nèi)存有兩個優(yōu)勢,一是減少了垃圾回收,二是提升復(fù)制速度,如NIO就是采用堆外內(nèi)存??梢允褂梦垂_的Unsafe和NIO包下ByteBuffer來創(chuàng)建堆外內(nèi)存。
- 在JDK1.4中新加入了NIO(New Input/Output)類,引入了一種基于通道(Channel)與緩沖區(qū)(Buffer)的I/O方式,它可以使用Native函數(shù)庫直接分配堆外內(nèi)存,然后通過一個存儲在Java堆中的DirectByteBuffer對象作為這塊內(nèi)存的引用進(jìn)行操作。這樣能在一些場景中顯著提高性能,因?yàn)楸苊饬嗽贘ava堆和Native堆中來回復(fù)制數(shù)據(jù)。
- 顯然,本機(jī)直接內(nèi)存的分配不會受到Java堆大小的限制,但是,既然是內(nèi)存,肯定還是會受到本機(jī)總內(nèi)存(包括RAM以及SWAP區(qū)或者分頁文件)大小以及處理尋址空間的限制。服務(wù)器管理員在配置虛擬機(jī)參數(shù)時,會根據(jù)實(shí)際內(nèi)存設(shè)置-Xmx等參數(shù)信息,但經(jīng)常忽略直接內(nèi)存,使得各個內(nèi)存區(qū)域總和大于物理內(nèi)存限制(包括物理的和操作系統(tǒng)級的限制),從而導(dǎo)致動態(tài)擴(kuò)展時出現(xiàn)OutOfMemoryError異常。
android LowMemoryKiller原理
進(jìn)程的優(yōu)先級
前臺進(jìn)程(Foreground process)
- 包含正在交互的Activity(resumed
- 包含綁定到正在交互的Activity的Service
- 包含正在“前臺”運(yùn)行的Service(服務(wù)已調(diào)用startForeground())
- 包含正執(zhí)行一個生命周期回調(diào)的Service(onCreate()、onStart() 或 onDestroy())
- 包含一個正執(zhí)行其onReceive()方法的BroadcastReceiver
可見進(jìn)程(Visible process)
- 包含不在前臺、但仍對用戶可見的 Activity(已調(diào)用其 onPause() 方法)。例如,如果前臺 Activity 啟動了一個對話框,允許在其后顯示上一Activity,則有可能會發(fā)生這種情況。
- 包含綁定到可見(或前臺)Activity 的 Service。
服務(wù)進(jìn)程(Service process)
- 正在運(yùn)行已使用 startService() 方法啟動的服務(wù)且不屬于上述兩個更高類別進(jìn)程的進(jìn)程。盡管服務(wù)進(jìn)程與用戶所見內(nèi)容沒有直接關(guān)聯(lián),但是它們通常在執(zhí)行一些用戶關(guān)心的操作(例如,在后臺播放音樂或從網(wǎng)絡(luò)下載數(shù)據(jù))。因此,除非內(nèi)存不足以維持所有前臺進(jìn)程和可見進(jìn)程同時運(yùn)行,否則系統(tǒng)會讓服務(wù)進(jìn)程保持運(yùn)行狀態(tài)。
后臺進(jìn)程(Background process)
- 包含目前對用戶不可見的 Activity 的進(jìn)程(已調(diào)用 Activity 的 onStop() 方法)。這些進(jìn)程對用戶體驗(yàn)沒有直接影響,系統(tǒng)可能隨時終止它們,以回收內(nèi)存供前臺進(jìn)程、可見進(jìn)程或服務(wù)進(jìn)程使用。 通常會有很多后臺進(jìn)程在運(yùn)行,因此它們會保存在 LRU (最近最少使用)列表中,以確保包含用戶最近查看的 Activity 的進(jìn)程最后一個被終止。如果某個 Activity 正確實(shí)現(xiàn)了生命周期方法,并保存了其當(dāng)前狀態(tài),則終止其進(jìn)程不會對用戶體驗(yàn)產(chǎn)生明顯影響,因?yàn)楫?dāng)用戶導(dǎo)航回該 Activity 時,Activity會恢復(fù)其所有可見狀態(tài)。 有關(guān)保存和恢復(fù)狀態(tài)、或者異常殺死恢復(fù)可以參考前兩篇 文章。
空進(jìn)程(Empty process)
- 不含任何活動應(yīng)用組件的進(jìn)程。保留這種進(jìn)程的的唯一目的是用作緩存,以縮短下次在其中運(yùn)行組件所需的啟動時間,這就是所謂熱啟動 。為了使系統(tǒng)資源在進(jìn)程緩存和底層內(nèi)核緩存之間保持平衡,系統(tǒng)往往會終止這些進(jìn)程。
Android進(jìn)程的優(yōu)先級是如何更新的
這里是通過了Linux中的一個proc文件體統(tǒng),proc文件系統(tǒng)可以簡單的看多是內(nèi)核空間映射成用戶可以操作的文件系統(tǒng),當(dāng)然不是所有進(jìn)程都有權(quán)利操作,通過proc文件系統(tǒng),用戶空間的進(jìn)程就能夠修改內(nèi)核空間的數(shù)據(jù),比如修改進(jìn)程的優(yōu)先級。
version 5.0之前
AMS進(jìn)程直接修改proc,
version 5.0之后
修改優(yōu)先級的操作被封裝成了一個獨(dú)立的服務(wù)-lmkd,lmkd服務(wù)位于用戶空間,其作用層次同AMS、WMS類似,就是一個普通的系統(tǒng)服務(wù)。AMS 和 lmkd 通過socket通信。
Android 系統(tǒng)是如何回調(diào)低內(nèi)存方法
- kswapd的內(nèi)核線程,當(dāng)linux回收存放分頁的時候,kswapd線程將會遍歷一張shrinker鏈表,并執(zhí)行回調(diào),或者某個app啟動,發(fā)現(xiàn)可用內(nèi)存不足時,則內(nèi)核會阻塞請求分配內(nèi)存的進(jìn)程分配內(nèi)存的過程,并在該進(jìn)程中去執(zhí)行l(wèi)owmemorykiller來釋放內(nèi)存
Android進(jìn)程如何被殺死
- LomemoryKiller屬于一個內(nèi)核驅(qū)動模塊,主要功能是:在系統(tǒng)內(nèi)存不足的時候掃描進(jìn)程隊(duì)列,找到低優(yōu)先級(也許說性價(jià)比低更合適)的進(jìn)程并殺死,以達(dá)到釋放內(nèi)存的目的。
被動掃描。找到低優(yōu)先級的進(jìn)程殺死。
refrence
https://blog.csdn.net/c10WTiybQ1Ye3/article/details/107723950
虛擬內(nèi)存與物理內(nèi)存的聯(lián)系與區(qū)別
[關(guān)于 mmap解析](http://m.itdecent.cn/p/755338d11865)
[Android性能優(yōu)化-內(nèi)存篇](http://m.itdecent.cn/p/829477754c19)
UE高級性能剖析技術(shù)(三)-- Android內(nèi)存分布和優(yōu)化
【騰訊優(yōu)測干貨分享】如何降低App的待機(jī)內(nèi)存(四)——進(jìn)階:內(nèi)存原理

