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

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

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

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

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

