亚洲一级簧片_性 毛片_国产乱子视频_久久影城_强伦女教师视频_成人精品久久

電腦怎么看多大內(nèi)存

發(fā)布時(shí)間: 2023-04-16 08:04 閱讀: 文章來(lái)源:轉(zhuǎn)載

1 內(nèi)存條、總線與DMA

計(jì)算機(jī)組成中內(nèi)存或者叫主存是非常重要的部件。內(nèi)存因?yàn)榈匚惶匾院虲PU直接相連,通過(guò)數(shù)據(jù)總線進(jìn)行數(shù)據(jù)傳輸,并通過(guò)地址總線來(lái)進(jìn)行物理地址的尋址。

除了數(shù)據(jù)總線、地址總線還有控制總線、IO總線等。IO總線是用來(lái)連接各種外設(shè)的,例如USB全稱(chēng)就是通用串行總線。再比如PCIE是目前最常見(jiàn)的IO總線之一。這里放一張B站硬件茶談的一張圖。

圖1-1 硬件圖

圖中CPU和左側(cè)內(nèi)存條直接連,并通過(guò)PCIE總線與下方的PCIE插槽連接,在PCIE插槽上可以插顯卡,網(wǎng)卡,聲卡,硬盤(pán)等等。PCIE帶寬是共享的,如果某個(gè)設(shè)備用了x1路帶寬,則能用的就少一路,因?yàn)楸举|(zhì)上每一路都是串行的。南橋和CPU之間也有PCIE通道,主要是提供給一些帶寬占用很低的外設(shè)。

南橋芯片位于主板上,一般在右下角,有個(gè)被動(dòng)散熱下面壓著。南橋中有個(gè)很重要的設(shè)備就是DMA控制器,或者叫DMAC。DMA直接內(nèi)存訪問(wèn),意思就是DMAC能夠直接訪問(wèn)內(nèi)存。即一般進(jìn)行IO的時(shí)候,cpu會(huì)把總線完全交給DMAC(DMAC和CPU會(huì)分時(shí)掌控總線),DMAC訪問(wèn)設(shè)備如磁盤(pán),將數(shù)據(jù)讀到內(nèi)存中,因?yàn)榇藭r(shí)接管了總線,所以可以寫(xiě)內(nèi)存。在這個(gè)過(guò)程中CPU可以進(jìn)行其他的任務(wù)。這也是異步IO、非阻塞IO等理論的基礎(chǔ)。

計(jì)算機(jī)常考題:

圖1-2-1 題目1

圖1-2-2 題目2

2 操作系統(tǒng)內(nèi)存管理與分類(lèi)

2.1 虛擬內(nèi)存(邏輯內(nèi)存)

win32程序從程序上能操作的邏輯地址空間有4G這么大(雖然實(shí)際可能用不了那么多),4G的邏輯地址需要全部映射到物理內(nèi)存上。映射的最小單位如果是字節(jié)的話,映射表將會(huì)非常大,且效率低下。提出page概念,即最小的映射單位是一個(gè)page,一頁(yè)一般是4K這樣的大小,我的機(jī)器是這樣的,所以下面程序demo中頁(yè)大小都是4K。

顯然邏輯空間可能比實(shí)際要大,但是只要程序沒(méi)有用那么多內(nèi)存,就不需要去映射那么多page,且就算用了那么多內(nèi)存,也可以映射到磁盤(pán)上。

邏輯頁(yè)是抽象的,需要映射到物理的頁(yè)上,才能完成對(duì)內(nèi)存的操作。我們把邏輯頁(yè)叫頁(yè)(page)物理頁(yè)叫幀(page frame)。頁(yè)號(hào)-幀號(hào)的映射表叫頁(yè)表(page table)。

圖2-1 頁(yè)表映射

因?yàn)槊總€(gè)程序看到的邏輯地址空間都很大,所以程序變多了之后,程序使用的內(nèi)存大于了物理內(nèi)存,此時(shí)一般通過(guò)將部分"不著急使用"的頁(yè)映射到磁盤(pán)的方式來(lái)解決。所以頁(yè)表中映射項(xiàng)可能是磁盤(pán)。

圖2-2 頁(yè)表映射

同時(shí)每個(gè)進(jìn)程都有自己的專(zhuān)屬頁(yè)表,如下:

圖2-3 多進(jìn)程的頁(yè)表

一種實(shí)際情況,4G邏輯地址有32bit地址空間,假設(shè)pageSize=4K偏移量占12bit,因而頁(yè)表的邏輯頁(yè)號(hào)有20bit。再假設(shè)實(shí)際內(nèi)存條只有256M 28bit地址空間 12bit偏移量 16bit頁(yè)號(hào)。

邏輯地址0x 00001 1a3,去映射的時(shí)候00001就是邏輯頁(yè)號(hào),去查頁(yè)表發(fā)現(xiàn)映射到真實(shí)頁(yè)幀號(hào)00f3,然后偏移量不變還是1a3,最終就找到這個(gè)物理內(nèi)存內(nèi)容了。

圖2-4 頁(yè)表的映射過(guò)程

這個(gè)過(guò)程中,可能會(huì)出現(xiàn)映射的幀號(hào)是disk,即映射到了磁盤(pán)上。此時(shí)會(huì)觸發(fā)缺頁(yè)異常,進(jìn)入內(nèi)核態(tài),內(nèi)核從磁盤(pán)中讀取缺的這頁(yè)內(nèi)容,將其加載到物理內(nèi)存中。但是物理內(nèi)存的幀有可能所有幀都滿了,此時(shí)就需要逐出不太"重要"的幀。

逐出的過(guò)程需要判斷當(dāng)前物理頁(yè)(幀)是否是臟的(臟:與磁盤(pán)中內(nèi)容不一致,即從磁盤(pán)加載到物理內(nèi)存后被改過(guò)就是臟的),如果是臟的還需要更新磁盤(pán)中的內(nèi)容保證一致。

逐出后就騰出了位置給從磁盤(pán)中讀到的這頁(yè)的數(shù)據(jù),然后需要更新頁(yè)表的這一項(xiàng)的映射關(guān)系,將磁盤(pán)改為幀號(hào),然后重新進(jìn)行查頁(yè)表這一步。

邏輯層的作用:極大的降低了內(nèi)存碎片;借助磁盤(pán)可以實(shí)現(xiàn)"無(wú)限的內(nèi)存";各個(gè)進(jìn)程間內(nèi)存的安全性等。

一個(gè)地址中“住”的是一字節(jié)(8bit)的數(shù)據(jù)。

2.2 快表TLB、多級(jí)頁(yè)表

上面提到了邏輯-物理頁(yè)的映射,這就是頁(yè)表,但是上面的頁(yè)表其實(shí)除了簡(jiǎn)單的頁(yè)號(hào)映射,還存儲(chǔ)了其他一些屬性:是否有效,讀寫(xiě)權(quán)限,修改位,訪問(wèn)位(淘汰算法和TLB中用),是否是臟(被修改過(guò)就是臟的,因?yàn)樗陀脖P(pán)上的數(shù)據(jù)不一致),是否允許被高速緩存等等。

頁(yè)表存于主存中,每個(gè)進(jìn)程都有自己的頁(yè)表。

上面可以看到基于頁(yè)表的尋址,需要兩次訪問(wèn)主存(頁(yè)表是存在主存的),效率低下。為了提高速度,引入了快表,快表是頁(yè)表項(xiàng)的緩存,將最近一次的映射項(xiàng)存入快表,因?yàn)榭臻g有限所以需要逐出最老的那一項(xiàng)。快表的設(shè)計(jì)是基于經(jīng)驗(yàn):程序經(jīng)常訪問(wèn)的page一般就那幾個(gè),不會(huì)經(jīng)常頻繁的更換特別多的頁(yè)。

快表可能存于硬件MMU中(也可能是軟件TLB),一般只有8-256條,每個(gè)進(jìn)程都有自己的快表。

另一個(gè)值得討論的話題是頁(yè)表占用空間太大,上面例子中(32位程序256M機(jī)器pageSize4K)頁(yè)號(hào)有20bit即2百萬(wàn)個(gè),所以需要有1百萬(wàn)條,每條大小如果只算邏輯頁(yè)號(hào)(20bit)和物理頁(yè)號(hào)(16bit)的話:

36bit * 2^20 = 4.5MB

如果有64個(gè)這樣的程序在運(yùn)行...后果可想而知。

一種很好的解決方法是多級(jí)頁(yè)表,第一級(jí)頁(yè)表用于尋找第二級(jí)頁(yè)表的編號(hào)。<20bit-16bit>的單級(jí)映射可以改成<10bit-10bit>和<10bit-6bit>兩級(jí)映射。此時(shí)占用內(nèi)存為

20bit * 2^10 + 16bit * 2^20 = 2M

2.3 分段

嚴(yán)格意義的分段是,每一段的虛擬地址都是從0開(kāi)始。然后頁(yè)表是段號(hào)+頁(yè)號(hào)來(lái)映射幀號(hào)的。但是這種形式已經(jīng)被廢棄了,只有x86 32位的intel的cpu還保留了這種段頁(yè)結(jié)合的方式,即嚴(yán)格意義的分段已經(jīng)用的很少。

那為什么還經(jīng)常聽(tīng)到段的概念?現(xiàn)在所說(shuō)的段一般是程序在邏輯層面保留的概念,對(duì)邏輯地址有個(gè)粗略的劃分,便于程序編寫(xiě),但是并不影響os的內(nèi)存管理(還是分頁(yè)管理)。

以32位程序?yàn)槔谶壿嬁臻g中最高的0xc0000000 - 0xffffffff這1G的內(nèi)存是給內(nèi)核留出的,這部分是所有進(jìn)程共享的。剩余3G內(nèi)存從低到高分別是Text、Data、Heap、Lib、Stack。64位程序則遠(yuǎn)大于這里的值。

Heap是從低往高增長(zhǎng),Stack是從高往低增長(zhǎng),且有個(gè)最大限制。Data存儲(chǔ)靜態(tài)變量Text存儲(chǔ)程序二進(jìn)制碼,Lib存儲(chǔ)庫(kù)函數(shù)需要占用的內(nèi)存,多個(gè)程序如果都使用了相同的庫(kù),內(nèi)存是共用的(共享內(nèi)存)。各個(gè)部分的留有隨機(jī)的一段偏移量,可以保護(hù)程序,這也使得每次重新執(zhí)行程序的時(shí)候變量所在的內(nèi)存地址總是不同的。

圖2-5 32位系統(tǒng)下內(nèi)存地址的組成

分段是邏輯空間上的,不影響分頁(yè)的內(nèi)存管理方式,后面進(jìn)行分頁(yè),映射到物理內(nèi)存上各部分跨多個(gè)頁(yè)其實(shí)并不連續(xù)。

2.4 cache

cpu的三級(jí)緩存扮演著緩存主存數(shù)據(jù)的作用,而cache在內(nèi)存管理中的位置是怎樣的呢?

PIPT,物理級(jí)cache,cpu分析完映射關(guān)系,先到cache找有沒(méi)有該物理地址的cache。這樣會(huì)非常的慢,但是所有進(jìn)程可以共享cache。

VIVT,邏輯級(jí)cache,cpu直接通過(guò)邏輯地址找cache,miss后再查T(mén)LB頁(yè)表這些。這樣很快,但是邏輯地址只能對(duì)當(dāng)期進(jìn)程使用,其他進(jìn)程完全不能復(fù)用,尤其是庫(kù)函數(shù)這種共享的不能利用好cache。

VIPT,將兩者結(jié)合,用邏輯地址查找cache,cache中數(shù)據(jù)部分前面添加一個(gè)對(duì)應(yīng)物理地址的tag。這樣拿到這個(gè)tag后到tlb、頁(yè)表中查看下這個(gè)對(duì)應(yīng)關(guān)系是否正確,如果正確就直接讀cache。這樣速度和共享性都是折中的。

以上三種方式各有優(yōu)劣,在不同的cpu中可能使用的不一樣。

2.5 內(nèi)存地址大小

很多人想當(dāng)然的會(huì)認(rèn)為32位系統(tǒng)的虛擬地址是32位,這是沒(méi)錯(cuò)的,但是64位系統(tǒng)下真正的可用的虛擬地址卻不到64位。

#include int main(){int x = 10;printf("%p",&x)}

圖2-6 C語(yǔ)言打印地址

明顯看到是48位,雖然這個(gè)指針大小是8byte,但是只有48bit是有效的地址位,前面是多個(gè)0。通過(guò)cat /proc/cpuinfo最后幾行能看到物理地址和虛擬地址的大小,這主要是cpu單方面定制的,我的這臺(tái)機(jī)器是13年買(mǎi)的intel 酷睿i5 3230的CPU。當(dāng)然我的系統(tǒng)內(nèi)存只有2G,其實(shí)物理地址不會(huì)有43位,只是cpu最多支持43位物理地址。

圖2-7 cpuinfo中的虛擬地址和物理地址

小細(xì)節(jié):棧是僅次于內(nèi)核的高位地址,參考圖2-5. 所以看到前面這個(gè)地址基本能推算出分給內(nèi)核的虛擬空間應(yīng)該是0xffff ffff ffff - 0x8000 0000 0000。

2.6 內(nèi)存分類(lèi)

在生活中我們經(jīng)常看到各種內(nèi)存的種類(lèi),比如在linux調(diào)用free -h的時(shí)候可以看到圖2-6的分類(lèi)。

在linux中通過(guò)free -h可以看到當(dāng)前系統(tǒng)的內(nèi)存情況:

圖2-8 free指令下的內(nèi)存分類(lèi)

mem是物理內(nèi)存,swap是交換分區(qū),是用來(lái)將內(nèi)存暫時(shí)放到磁盤(pán)上的。

total總內(nèi)存大小,used用戶(hù)使用的內(nèi)存大小,free空閑的內(nèi)存大小,shared共享內(nèi)存大小,buff/cache文件緩存大小,available可用內(nèi)存大小是free和buff/cache加起來(lái)。

total = used(含shared) + free+ buff/cache

這里需要理解buff/cache,他們?cè)诶弦恍┑膬?nèi)核中是分開(kāi)顯示的分別是buffer cache和page cache,都是對(duì)磁盤(pán)的緩存。其中buffer cache是硬件層面,對(duì)磁盤(pán)塊中的數(shù)據(jù)進(jìn)行緩存,緩存的單位當(dāng)然也是塊。而page cache是文件系統(tǒng)層面,對(duì)文件進(jìn)行緩存,緩存單位就是頁(yè)。buffer cache的提出非常的早,兩者并存時(shí)會(huì)遇到重復(fù)緩存了相同的內(nèi)容的情況。

較新的內(nèi)核已經(jīng)將兩者合并,或者說(shuō)將buffer cache合到了page cache。雖然也還是能緩存磁盤(pán)塊,但是存儲(chǔ)單位也是頁(yè)了。并且buffer使用前會(huì)先檢查page cahce是否已經(jīng)緩存了對(duì)應(yīng)內(nèi)容,如果是則直接指過(guò)去。在機(jī)器維度查看內(nèi)存的時(shí)候也能發(fā)現(xiàn)BufferCache都是0,因?yàn)槎己系搅藀ageCache,有Buffer的都是很老的內(nèi)核的機(jī)器。

buff/cache占用大,會(huì)不會(huì)影響后續(xù)程序申請(qǐng)內(nèi)存?

不會(huì),一旦用戶(hù)程序需要申請(qǐng)內(nèi)存,buff/cache就會(huì)釋放掉一部分。換句話說(shuō)buff/cache是在內(nèi)存比較空閑的時(shí)候,盡量利用一下來(lái)加速文件讀寫(xiě)的。如果有大哥需要用內(nèi)存,是會(huì)拱手讓出的。

如果想進(jìn)一步了解兩者的演化,這篇文章從內(nèi)核源碼的角度展示了,幾個(gè)理成本版本下buff cache 和 page cache的變化。

在windows任務(wù)管理器中又可以看到下圖的幾種狀態(tài)的內(nèi)存叫法,而在Jprofile查看jvm內(nèi)存的時(shí)候也有圖2-8的一些叫法。

圖2-9 windows任務(wù)管理器內(nèi)存分類(lèi)

圖2-10 jprofile內(nèi)存分類(lèi)

已提交的意思是已經(jīng)向操作系統(tǒng)申請(qǐng)了這么多的內(nèi)存,操作系統(tǒng)可以已經(jīng)給了這么多內(nèi)存了,但是也可能沒(méi)有給那么多。貼一張微軟自己的解釋如圖

圖2-11 幾種內(nèi)存的解釋

提交的內(nèi)存因?yàn)槭翘摂M內(nèi)存,并不一定系統(tǒng)會(huì)立刻給這么多,所以可能提交遠(yuǎn)超過(guò)物理內(nèi)存上限的大小。我之前看過(guò)一個(gè)視頻,小哥用malloc申請(qǐng)了130000+GB的內(nèi)存程序才退出,而如果在malloc后給申請(qǐng)的地址填寫(xiě)值,事情就不那么順利了。感興趣可以去看下這個(gè)視頻。當(dāng)然不了解C語(yǔ)言也沒(méi)關(guān)系我在本文后半段會(huì)用java的Unsafe同樣申請(qǐng)超過(guò)物理上限的內(nèi)存大小做demo。

3 內(nèi)存相關(guān)的系統(tǒng)調(diào)用

3.1 內(nèi)核態(tài)和用戶(hù)態(tài)

內(nèi)核態(tài)、用戶(hù)態(tài)、內(nèi)核空間、用戶(hù)空間,是經(jīng)常說(shuō)起的概念。因?yàn)椴僮飨到y(tǒng)不允許用戶(hù)直接操作硬件,所以需要用戶(hù)程序通知內(nèi)核,內(nèi)核幫你下達(dá)指令給硬件。在進(jìn)行讀文件的時(shí)候,就需要用到磁盤(pán)這個(gè)設(shè)備,所以需要進(jìn)入內(nèi)核態(tài),將文件內(nèi)容讀到內(nèi)核buffer,然后拷貝到用戶(hù)buffer并從內(nèi)核態(tài)切換為用戶(hù)態(tài),程序才能真正拿到數(shù)據(jù)。

用戶(hù)態(tài)進(jìn)內(nèi)核態(tài),一般有三種觸發(fā)條件,中斷、異常和系統(tǒng)調(diào)用,中斷和異常有時(shí)候界限比較模糊,例如缺頁(yè)中斷也有地方叫缺頁(yè)異常。這里我們引出了系統(tǒng)調(diào)用,大多數(shù)需要主動(dòng)操作或讀寫(xiě)硬件的都是通過(guò)系統(tǒng)調(diào)用。例如讀寫(xiě)文件的open/read/write是系統(tǒng)調(diào)用,網(wǎng)絡(luò)傳輸常見(jiàn)的select/poll/epoll也是系統(tǒng)調(diào)用,申請(qǐng)內(nèi)存的malloc底層也是通過(guò)brk或mmap這倆系統(tǒng)調(diào)用實(shí)現(xiàn)的。

系統(tǒng)調(diào)用伴隨了很多設(shè)計(jì)的優(yōu)化,例如通過(guò)epoll等系統(tǒng)調(diào)用實(shí)現(xiàn)的IO多路復(fù)用提高了網(wǎng)絡(luò)包的處理效率,mmap、sendfile等系統(tǒng)調(diào)用實(shí)現(xiàn)的零拷貝,減少了用戶(hù)空間和內(nèi)核空間之間的數(shù)據(jù)拷貝和上下文切換次數(shù)等等。在java的NIO中有大量的函數(shù)是直接封裝了系統(tǒng)調(diào)用。

3.2 brk

malloc小于128K(閾值可修改)的內(nèi)存時(shí),用的是brk申請(qǐng)內(nèi)存。C語(yǔ)言中sbrk(可函數(shù))是brk(系統(tǒng)調(diào)用)的簡(jiǎn)單封裝,下面代碼打印的值可以看出first因?yàn)樯暾?qǐng)了0大小,所以和second指針位置相同。而third則表示的是second的尾部地址。可以看到虛擬地址是連續(xù)分配的,brk其實(shí)就是向上擴(kuò)展heap的上界,配合查看圖2-5。

#include #include int main(){void *first = sbrk(0);void *second = sbrk(1);void *third = sbrk(0);printf("%p\n",first);printf("%p\n",second);printf("%p\n",third);}

圖3-1 brk代碼輸出

如果此時(shí)在 third+1地址處去初始化一個(gè)int值,是可以成功的,并不報(bào)錯(cuò)。

#include #include int main(){void *first = sbrk(0);void *second = sbrk(1);void *third = sbrk(0);int *p = (int *)third+1;*p = 1;}

這是因?yàn)轫?yè)大小是4K,sbrk(1)其實(shí)也是申請(qǐng)一頁(yè),所以third+1位置也是安全的。如果我們將second這行改為4096,那就是另一個(gè)故事了,會(huì)觸發(fā)段錯(cuò)誤。

void *second = sbrk(4096);

圖3-2 brk代碼輸出2

3.3 mmap

malloc大于128K的內(nèi)存時(shí),用的是mmap。

// addr傳NULL則不關(guān)心起始地址,關(guān)心地址的話應(yīng)傳個(gè)4k的倍數(shù),不然也會(huì)歸到4k倍數(shù)的起始地址。void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);//釋放內(nèi)存munmapint munmap(void *addr, size_t length);

mmap用法有兩種,一種是將文件映射到內(nèi)存,另一種空文件映射,也就是把fd傳入-1,就會(huì)從映射區(qū)申請(qǐng)到一塊內(nèi)存。malloc就是調(diào)用的第二種實(shí)現(xiàn)。

#include #include #include int main(){int* a =(int *) mmap(NULL, 100 * 4096, PROT_READ| PROT_WRITE, MAP_PRIVATE| MAP_ANONYMOUS, -1, 0);int* b =a;for(int i=0;i<100;i++){b = (void *)a + (i*4096);*b =1;}while(1){sleep(1);}}

這里提交400K內(nèi)存的申請(qǐng),并且在每頁(yè)中都進(jìn)行內(nèi)存的使用。可以看到不映射文件的話觸發(fā)的是minflt次數(shù)是100次。

圖3-3 進(jìn)程的內(nèi)存minflt

這里是mmap內(nèi)存的惰性加載,一開(kāi)始mmap100頁(yè)時(shí)其實(shí)都沒(méi)有分配給進(jìn)程,在用到的時(shí)候開(kāi)始真正拿到內(nèi)存,此時(shí)觸發(fā)minflt缺頁(yè),因?yàn)椴皇怯成涞奈募挥脧拇疟P(pán)中調(diào)內(nèi)存,所以是小錯(cuò)誤。但是仍是消耗性能的。

如果mmap是映射的磁盤(pán)文件,也會(huì)惰性加載,在初次加載或者頁(yè)被逐出后再加載的時(shí)候,也會(huì)缺頁(yè),這個(gè)時(shí)候就不是小錯(cuò)誤minflt了,而是majflt。例如下面使用mmap來(lái)讀文件。

#include #include #include #include #include #include int main(){sleep(4);int fd = open("./1.txt", O_RDONLY, S_IRUSR|S_IWUSR);struct stat sb;if(fstat(fd, &sb) == -1){perror("cannot get file size\n");}printf("file size is %ld\n",sb.st_size);char *file_in_memory = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);for(int i=0;i

下圖是線程監(jiān)聽(tīng)的結(jié)果,為了方便觀察我在開(kāi)始讀之前sleep 4s。可以看到紅框第一行,有一次majflt,這是第一次去讀文件,直接觸發(fā)了缺頁(yè)異常,且指向磁盤(pán)。是最耗時(shí)的錯(cuò)誤。

圖3-3-2 進(jìn)程mmap讀文件引發(fā)majflt

read和mmap都可以讀文件,前者有狀態(tài)轉(zhuǎn)換和多次拷貝,但是后者有缺頁(yè)中斷。在單純讀磁盤(pán)文件場(chǎng)景,兩者其實(shí)沒(méi)法在孰優(yōu)孰劣上有定論。

3.4 共享內(nèi)存

共享內(nèi)存是進(jìn)程間通信的一種方式,(管道 信號(hào) 信號(hào)量 套接字也是進(jìn)程通信的方式)。共享內(nèi)存的例子比比皆是,windows下最明顯,比如這個(gè)上傳文件的對(duì)話框就是共享內(nèi)存里的,同一時(shí)間windows下不會(huì)彈出兩個(gè)該對(duì)話框。再比如動(dòng)態(tài)鏈接庫(kù),也是共享內(nèi)存中的,多個(gè)進(jìn)程可以共享,兩個(gè)進(jìn)程mmap相同文件的方式可以實(shí)現(xiàn)共享內(nèi)存,shmget則是更廣泛的共享內(nèi)存的系統(tǒng)調(diào)用。

圖3-4 共享內(nèi)存的典型例子

共享內(nèi)存原理就是兩個(gè)進(jìn)程中頁(yè),映射到了相同的幀。代碼這里不寫(xiě)了,直接參考geeks這篇的代碼。

4 java中的內(nèi)存

4.1 java內(nèi)存概述

jvm內(nèi)存結(jié)構(gòu)主要如圖4-1.本文不想對(duì)“常考”的知識(shí)點(diǎn)再次進(jìn)行講解,網(wǎng)上有大量的文章來(lái)講內(nèi)存結(jié)構(gòu)各自的用途和GC相關(guān)的內(nèi)容,這里我就不展開(kāi)講了。下面幾節(jié)會(huì)講一些比較"冷門(mén)"的知識(shí)。

圖4-1 java的內(nèi)存五區(qū)

4.2 對(duì)象頭與指針壓縮

在另一篇講計(jì)算java對(duì)象大小的文章中提到,java對(duì)象是由對(duì)象頭,對(duì)象內(nèi)容組成,并且是8字節(jié)對(duì)齊的。其中對(duì)象頭有以下三部分組成:

  • Mark Word(64bits) 當(dāng)前對(duì)象一些運(yùn)行時(shí)數(shù)據(jù)如鎖
  • Klass Word(開(kāi)壓縮32bits,不開(kāi)64bits) 類(lèi)型指針,指向類(lèi)元數(shù)據(jù)Klass地址
  • array length(32bits) 數(shù)組對(duì)象才有

我們這里來(lái)看下Klass,有沒(méi)有想過(guò)我們反射的時(shí)候操作的都是Class對(duì)象而不是這里的Klass,兩者關(guān)系是:

Klass是C++對(duì)象InstanceKlass,里面有個(gè)_java_mirror字段指向?qū)?yīng)的Class對(duì)象。

圖4-2 java對(duì)象頭指向metaspace

這里還提到了指針壓縮,64位系統(tǒng),如果jvm堆內(nèi)存小于32GB是可以開(kāi)啟指針壓縮的,此時(shí)Klass指針只需要4個(gè)字節(jié),同時(shí)對(duì)象指針也只需要4個(gè)字節(jié)。這里會(huì)衍生出兩個(gè)問(wèn)題:

第一個(gè)就是4字節(jié)最多表示2^32個(gè)地址,每個(gè)地址里住的是一個(gè)字節(jié),所以只能表示4GB,怎么還說(shuō)32G下都能壓縮呢?

因?yàn)椋荷厦嫣岬綄?duì)象都是8字節(jié)對(duì)齊,所以每個(gè)地址里住的是8字節(jié),所以可以表示32GB,實(shí)際地址移3位。

第二個(gè)問(wèn)題就是普通對(duì)象指針壓縮Compressed Object Pointers (“CompressedOops”),壓縮的是java堆上的對(duì)象的指針(引用)大小,而對(duì)象頭指向的是Klass,這是個(gè)C++的結(jié)構(gòu),這個(gè)指針也壓縮了嗎?

是的,CompressOops和CompressKlass是相伴而生,默認(rèn)同時(shí)開(kāi)啟的,Klass這部分需要連續(xù)的<4G的內(nèi)存,因?yàn)槭荂++結(jié)構(gòu),沒(méi)有8字節(jié)對(duì)齊限制,所以4字節(jié)只能在4G內(nèi)存上尋址,默認(rèn)大小是1G。

4.3 metaspace

metaspace存儲(chǔ)的是類(lèi)的元數(shù)據(jù)信息,上面提到的Klass就是在metaspace中的,一般開(kāi)啟壓縮的metaspace有CompressClassSpace和NonClassSpace兩部分組成,其中前者內(nèi)存占用較少,是后者的5-100分之一,前者又叫壓縮類(lèi)空間,實(shí)際上這部分內(nèi)存本身并沒(méi)有壓縮,只是對(duì)象頭中記錄的指向這里的指針進(jìn)行了壓縮。

圖4-3 metaspace兩部分:非類(lèi)區(qū)和壓縮類(lèi)空間

壓縮類(lèi)空間中Klass是c++的對(duì)象有著很多元數(shù)據(jù)字段,vtable是記錄虛方法指針,itable是接口方法指針。Non-class中則記錄了更詳細(xì)的元數(shù)據(jù)信息。開(kāi)啟指針壓縮后,如果設(shè)置MaxMetaspaceSize參數(shù)實(shí)際上是限定的Non-class部分的大小,而不包括壓縮類(lèi)空間。通過(guò)Jprofile中也能發(fā)現(xiàn)Metaspace只包括Non-class部分,那為什么我上來(lái)說(shuō)Metaspace有兩部分呢,主要是從概念上講兩者都是元數(shù)據(jù),在國(guó)外很多文章中也都?xì)w為了Metaspace。這里只需要注意這個(gè)小細(xì)節(jié)就可以了。設(shè)置MaxMetaspaceSize參數(shù)也可以對(duì)壓縮類(lèi)空間起到間接的限制,因?yàn)榍懊嬲f(shuō)了Non-class部分是class部分的n倍。

圖4-4 指針壓縮開(kāi)啟時(shí) 非堆

將壓縮類(lèi)空間和非類(lèi)空間分開(kāi)的原因之一,就是壓縮類(lèi)空間是對(duì)象關(guān)聯(lián)的,只有4G上限,而將更多其他元數(shù)據(jù)剝離出去后,元空間可以遠(yuǎn)超過(guò)4G。而如果不開(kāi)啟指針壓縮,其實(shí)兩者就沒(méi)必要分開(kāi)了。關(guān)閉指針壓縮后,-XX:-UseCompressedOops 兩部分會(huì)合為一個(gè)。統(tǒng)稱(chēng)Metaspace

圖4-5 指針壓縮關(guān)閉時(shí)非堆

Q1:元空間內(nèi)存什么時(shí)候分配?

一個(gè)新的類(lèi)在需要被加載的時(shí)候,會(huì)使用ClassLoader在元空間申請(qǐng)內(nèi)存,并存儲(chǔ)類(lèi)的元數(shù)據(jù)信息。

Q2:元空間什么時(shí)候釋放內(nèi)存?

元空間的內(nèi)存是ClassLoader持有的,所以說(shuō)只有對(duì)應(yīng)的ClassLoader卸載掉的時(shí)候才會(huì)釋放。ClassLoader又是需要他所加載的類(lèi)都消失的時(shí)候才能消失。一般是伴隨在一次GC的過(guò)程中進(jìn)行這個(gè)釋放。另外元空間如果超過(guò)了上限也會(huì)導(dǎo)致OOM。

Q3:metaspace溢出會(huì)不會(huì)導(dǎo)致OOM?

當(dāng)然會(huì)導(dǎo)致OOM,所以metaspace限制大小的配置,需要根據(jù)程序謹(jǐn)慎定制。一般通過(guò)不斷創(chuàng)建新的類(lèi),如加載新類(lèi)(如hsf配置中下發(fā)groovy文件就會(huì)動(dòng)態(tài)的加載新的class),或者動(dòng)態(tài)代理類(lèi)(spring中的增強(qiáng)類(lèi)都是動(dòng)態(tài)代理類(lèi))都會(huì)導(dǎo)致metaspace的增長(zhǎng)。

cglibcglib3.2.4
//設(shè)置metaspace大小:-XX:MaxMetaspaceSize=200mpublic class T {public static void main(String[] args) {while (true) {Enhancer enhancer = new Enhancer();enhancer.setSuperclass(Object.class);enhancer.setUseCache(false);enhancer.setCallback((FixedValue)()->":)");enhancer.create();}}}

監(jiān)視會(huì)發(fā)現(xiàn)壓縮類(lèi)空間和非類(lèi)空間都在增大,后者在200M上有道紅線,在2分鐘左右溢出,程序掛掉,這個(gè)程序中壓縮類(lèi)空間大概是分類(lèi)的六分之一。

圖4-5a 壓縮類(lèi)空間

圖4-5b 非類(lèi)空間

4.4 堆外內(nèi)存

上面的CodeCache和Metaspace毫無(wú)疑問(wèn)是jvm管理下的堆外空間。但是除了這些常規(guī)的堆外空間,jvm還可以使用一些native方法,直接申請(qǐng)堆外內(nèi)存。

例如做這么個(gè)demo,我們?cè)O(shè)置一個(gè)簡(jiǎn)單的java程序的堆大小是10M,此時(shí)用jprofile查看內(nèi)存堆提交了10M實(shí)際使用9M多,堆外提交了12M實(shí)際使用11M左右。所以算下來(lái)是20M+。直接查看進(jìn)程內(nèi)存會(huì)略大于這個(gè)值,因?yàn)檫@個(gè)20M是虛擬機(jī)內(nèi)部的內(nèi)存,本身運(yùn)行還是需要一些額外內(nèi)存的,進(jìn)程提交的內(nèi)存有90M,實(shí)際使用內(nèi)存47M

圖4-6 進(jìn)程的提交內(nèi)存和實(shí)際內(nèi)存

接下來(lái)我們使用Unsafe申請(qǐng)1G堆外內(nèi)存(也可以用NIO中的ByteBuffer.allocateDirect())

public static void main(String[] args) throws InterruptedException, IllegalAccessException, NoSuchFieldException {Field f = Unsafe.class.getDeclaredField("theUnsafe");f.setAccessible(true);Unsafe us = (Unsafe) f.get(null);long addr = us.allocateMemory(1024 * 1024 * 1024);System.out.println("Hello World");System.out.println(addr);while(true){Thread.sleep(1000L);}}

可以看到提交的內(nèi)存1G多,實(shí)際使用內(nèi)存也是47M。

圖4-7 進(jìn)程的提交內(nèi)存和實(shí)際內(nèi)存2

我甚至可以調(diào)整申請(qǐng)65G的內(nèi)存,要知道我的電腦也只有64G的內(nèi)存,但這仍不會(huì)報(bào)錯(cuò),可以看到提交的內(nèi)存已經(jīng)超過(guò)了物理內(nèi)存上限,但是得益于前面講的虛擬內(nèi)存的管理模式,使得應(yīng)用申請(qǐng)了超過(guò)物理大小的內(nèi)存,而如果真的使用起來(lái)的話,會(huì)有頁(yè)置換來(lái)協(xié)調(diào)。

圖4-8 進(jìn)程可以提交超過(guò)現(xiàn)實(shí)存在的內(nèi)存

上面的提交內(nèi)存很大但是實(shí)際使用內(nèi)存卻并不大:

圖4-9 任務(wù)管理器此時(shí)的狀態(tài)

Unsafe是很危險(xiǎn)的一個(gè)類(lèi),不建議使用。但是可以幫助我們理解有些框架是如何工作的。比如前一陣子看的Ehcache就提供了堆外緩存就是用類(lèi)似Unsafe申請(qǐng)的。堆外緩存需要自己實(shí)現(xiàn)序列化,因?yàn)閁nsafe設(shè)置內(nèi)存只能設(shè)置01字節(jié)碼不能設(shè)置為java對(duì)象。

堆外緩存的好處:緩存一般是短時(shí)間不需要清理的,如果在堆上則肯定會(huì)進(jìn)入老年代,占用固定的一大塊空間,使得觸發(fā)full GC的門(mén)檻降低了,很容易到了那個(gè)門(mén)限值。而且GC過(guò)程中還要去遍歷這些對(duì)象,效率較低。

堆外內(nèi)存的壞處:序列化需要自己實(shí)現(xiàn),清理也需要自己實(shí)現(xiàn),訪問(wèn)速度比heap要慢。

???展開(kāi)全文
相關(guān)文章
主站蜘蛛池模板: 91av电影在线观看 | 日韩一二 | 成人做爰视频www网站小优视频 | 久久精品免费看 | 久久国产精品久久久久久久久久 | 一区二视频 | 蜜桃视频一区二区 | 国产精品第一国产精品 | 伊人精品久久 | 在线一区二区视频 | 日韩一级片在线播放 | 亚洲 欧美 视频 | 国产激情综合五月久久 | 成人高清视频在线观看 | 精品国产成人 | 色综合精品久久久久久久 | 成人影院网站ww555久久精品 | 九九亚洲精品 | 欧美日韩在线视频免费观看 | 日韩a在线| 精品一区二区三区久久 | 欧美一区在线视频 | 久久黄色 | 黄色av网站在线观看 | 欧美日本综合 | 国产麻豆精品一区二区 | 久久久久久久 | 偷拍自拍亚洲色图 | 女人一级黄色片 | 日韩综合在线视频 | 黄色一级视频在线播放 | 国产情侣一区二区三区 | 欧美 亚洲 视频 | 99久免费精品视频在线观78 | 中文字幕国产 | 日本中文字幕在线观看 | 欧美大片黄色 | 在线视频 亚洲 | 黄色在线网 | 亚洲成人一区二区 | 毛片免费观看视频 |