爲了快速構建項目,使用高性能框架是個人職責,但若不去深究底層的細節會讓我失去對技術的熱愛。
探究的過程是痛苦並激動的,痛苦在於徹底理解甚至要十天半月甚至沒有機會去應用,激動在於技術的相同性,新的框架再也不是我焦慮。
每個底層細節的攻克,就愈加以爲本身對計算機一無所知,這可能就是對知識的敬畏。java
新IO和傳統IO都是用於進行輸入/輸出。
新IO採用了內存映射的方式來處理輸入/輸出,新IO將文件或文件的一段區域映射到內存中,這樣就能夠像訪問內存同樣訪問文件了,經過這種方式比傳統的輸入/輸出要快的多。經過內存映射機制操做文件比使用常規方法和使用FileChannel讀寫高效的多。linux
傳統的文件IO操做中,調用操做系統提供的底層標準IO系統調用函數 read()、write() ,調用此函數的進程(在JAVA中即java進程)由當前的用戶態切換到內核態,而後OS的內核代碼負責將相應的文件數據讀取到內核的IO緩衝區,而後再把數據從內核IO緩衝區拷貝到進程的私有地址空間中去,這樣便完成了一次IO操做。api
爲了減小磁盤的IO操做,同時程序訪問通常都帶有局部性,局部性原理,OS根據局部性原理會在一次 read()系統調用過程當中預讀更多的文件數據緩存在內核IO緩衝區中,當繼續訪問的文件數據在緩衝區中時便直接拷貝數據到進程私有空間,避免了再次的低效率磁盤IO操做。
其過程以下:緩存
爲何要搞一個內核IO緩衝區把本來只需一次拷貝數據的事情搞成須要2次數據拷貝呢?
這麼作是爲了減小磁盤的IO操做,爲了提升性能而考慮的,程序訪問通常都帶有局部性,局部性原理,在這裏主要是指的空間局部性,即咱們訪問了文件的某一段數據,那麼接下去極可能還會訪問接下去的一段數據,因爲磁盤IO操做的速度比直接訪問內存慢了好幾個數量級,因此OS根據局部性原理會在一次 read()系統調用過程當中預讀更多的文件數據緩存在內核IO緩衝區中,當繼續訪問的文件數據在緩衝區中時便直接拷貝數據到進程私有空間,避免了再次的低效率磁盤IO操做。數據結構
講新IO前先講講背景知識,虛擬空間。架構
好久好久之前的存儲管理技術必須將做業所有裝入內存才能執行且做業常駐內存直到運行結束,難以知足較大做業或較多做業進入內存執行。 爲了能讓做業的一部分裝入就能夠運行的存儲管理技術叫作虛擬內存管理技術。
現代操做系統中的進程在使用內存的時候,都不是直接訪問內存物理地址的,進程訪問的都是虛擬內存地址,而後虛擬內存地址再轉化爲內存物理地址。 虛擬內存就是硬盤中的一塊區域,它用來存放內存裏使用頻率不高的頁面文件,讓使用頻率高的頁面文件活動在內存區域中,提升CPU對數據操做的速度。
進程看到的全部地址組成的空間,就是虛擬空間。虛擬空間是某個進程對分配給它的全部物理地址(已經分配的和將會分配的)的從新映射。 在Linux中,這個區域叫作swap,通常大小應設置爲物理內存的2倍。詳情見 https://blog.csdn.net/fengxinlinux/article/details/52071766app
大多數程序執行時,在一個較短的時間內僅能使用程序代碼的一部分,相應的,程序所訪問的存儲空間也侷限於某個區域,這就是程序執行的局部性原理。
基於局部性原理,在程序裝入時能夠將程序的一部分放入內存,而將其他部分放在外存,而後啓動程序(部分裝入)。在程序執行期間,當所訪問的信息不在內存中,再由操做系統將所需的部分調入內存(請求調入)。另外,系統將內存中暫時不用的內容置換到外存上,騰出空間存放將要調入內存的信息(置換功能)。
框架
虛擬地址轉化爲真實地址的時候,不必定會對應內存地址,還可能對應硬盤地址。 內存的一個地址通常對應1byte,硬盤的一個地址通常對應512byte(一個磁盤扇區).
內存和硬盤裏的數據作交換時,也就是把一個內存地址對應的數據拷貝到硬盤裏或者反過來把硬盤數據拷貝到內存裏,想要方便處理操做系統會統一單位(傳說中的頁對齊)。 頁就是一個統一的單位,頁的大小老是磁盤扇區大小的倍數,一般是2次冪,好比1024字節。函數
有了頁這個統一單位,接下來咱們說的虛擬地址、內存地址、磁盤地址都是對應的一個頁。頁式虛擬地址與內存物理地址創建一一對應的頁表(硬件地址變換機構來執行轉換)。將邏輯地址上連續的頁號映射到物理內存中稱爲離散的多個物理塊(頁面),將頁面和物理塊一一對應,體如今頁表。(頁表由頁號和塊號組成)性能
虛擬地址空間能夠大於實際的內存空間,好比實際內存大小是1G,可是虛擬地址空間能夠是4G。這樣在操做系統中的普通應用程序看來,就好像是有4G的可用內存。
虛擬地址空間能夠大於實際內存空間,這是怎麼實現的呢?
好比我實際內存1G,虛擬內存設成了4G,如今往4G的虛擬內存裏放了4G的數據,那麼當前只有1G的數據在真實內存中,另外的3G由於裝不下就只能以文件形式放到硬盤裏,這個存放內存內容的硬盤文件就叫頁面文件。
虛擬內存的空間=物理內存+頁面文件。
各進程的虛擬空間被劃分爲等的頁若干個長度相,頁長1K—4K。進程虛擬地址變爲由頁號P與頁內地址W組成。 同時也把內存分紅與頁面大小相等的區域,稱爲頁面。用戶進程在內存空間除了在每一個頁面內地址連續以外,每一個頁面之間再也不連續。
swap空間就是虛擬內存,在物理內存不足時,有較大的用處。
查看內存空間大小:free -m // m表示顯示的字節單位是m(megabytes)
用命令free查看系統內 Swap 分區大小。
free -m total used free shared buffers cached Mem: 1002 964 38 0 21 410 -/+ buffers/cache: 532 470 Swap: 951 32 929 能夠看到 Swap 只有951M
如何修改百度便可。
vm.swappiness是Linux內核的一個參數,範圍是0~100。它表示實際內存和虛擬內存區域進行數據交換的傾向性大小,數值越大表示傾向性越大,即交換的頁面文件越多,反之亦然。通常默認值爲60。可用'cat /proc/sys/vm/swappiness’查看。
這個值應該設置成多大才能提升Linux的性能呢?
如下摘自 https://blog.csdn.net/liu870915/article/details/51860932
這個固然要由具體的環境來定了。在一臺CentOS機器上,分別把值設爲0,60,100,下面是運行'vmstat -S M 5’的三次數據報告。(vmstat命令是用來查看虛擬內存情況的,參數-S M表示以M爲單位,5表示每5秒鐘產生一次報告。)這裏主要關注bi,bo和wa這三個值,bi表明每秒鐘從硬盤讀入數據的塊數(由於硬盤是塊設備),bo表示每秒鐘寫入硬盤數據的塊數,wa表示CPU等待IO設備就緒的時間。 當值爲100時,wa基本爲50左右的值,這表示50%的CPU時間都在等待IO設備就緒(大好的CPU資源就這樣被浪費了!)如今你明白瓶頸在哪裏了吧?對,就是硬盤。 說明我實驗的這臺機器硬盤IO的處理能力是最影響性能的了。那麼該怎麼解決呢?固然了,換個轉速更快的硬盤固然能夠,還有呢?增長內存有可能也能夠。增長了內存之後,再把swappiness的值設小點,以減小硬盤IO的操做。內存夠大時,不管頁面文件的使用頻率是高仍是低都放在內存裏,無須使用虛擬內存。 可是在這個例子中,swpd的值始終爲0,這表示沒有虛擬文件被使用。這說明內存容量是足夠的,即便再增長內存,做用也不大。最好的辦法就是更換硬盤了。 如何改變swapiness的值?你能夠運行'echo 數值 > /proc/sys/vm/swapiness’ 或者 'sysctl –w vm.swappiness = 數值' 來修改內核中的實時參數。若是想機器在重啓以後仍然保持這個數值的話,就須要在'/etc/sysctl.conf’文件中加上'vm.swappiness = 數值' 這一行。
傳統IO中當對文件進行操做的時候,通常老是先打開文件,而後申請一塊內存用作緩衝區,再將文件數據循環讀入並處理,當文件長度大於緩衝區長度的時候須要屢次讀入。
內存映射文件是將一個文件直接映射到進程的進程空間中(「映射」就是創建一種對應關係,這裏指硬盤上文件的位置與進程邏輯地址空間中一塊相同區域之間一 一對應,這種關係純屬是邏輯上的概念,物理上是不存在的),這樣能夠經過內存指針用讀寫內存的辦法直接存取文件內容。
在內存映射過程當中,並無實際的數據拷貝,文件沒有被載入內存,只是邏輯上放入了內存,具體到代碼,就是創建並初始化了相關的數據結構,這個過程由系統調用mmap()實現,因此映射的效率很高.
經驗代表,內存映射IO容許加載不能直接訪問的潛在巨大文件,在大文件處理方面性能更加優異。它的不足是增長了頁面錯誤的數目(因爲操做系統只將一部分文件加載到內存,若是一個請求頁面沒有在內存中,它將致使頁面錯誤)。
映射文件區域的能力取決於於內存尋址的大小。在32位機器中,你不能一次訪問超過4GB或2 ^ 32(以上的文件),只能分批映射。
mmap()是系統調用,沒有進行數據拷貝,數據拷貝是在缺頁中斷處理時進行的,因爲mmap()將文件直接映射到用戶空間,因此中斷處理函數根據這個映射關係,直接將文件從硬盤拷貝到用戶空間(沒有拷貝到內核空間),只進行了一次數據拷貝 。
從硬盤上將文件讀入內存,都是要通過數據拷貝,而且數據拷貝操做是由文件系統和硬件驅動實現的,理論上來講,拷貝數據的效率是同樣的。
內存映射文件的效率比標準IO高的重要緣由就是由於少了把數據拷貝到OS內核緩衝區這一步,內存映射只拷貝一次效率要比read/write 拷貝兩次高。
虛擬內存是內存映射文件的基礎,內存映射文件的底層仍是依賴虛擬內存。虛擬內存和內存映射文件都是將一部份內容加載到內存,另外一部分放在磁盤上,兩者都是應用程序動態性的基礎,因爲兩者的虛擬性,對於用戶都是透明的.
虛擬內存是硬盤的一部分,是計算機RAM(隨機存取存儲器)與硬盤的數據交換區,由於實際的物理內存可能遠小於進程的地址空間,這就須要把內存中暫時不用到的數據放到硬盤上一個特殊的地方,當請求的數據不在內存中時,系統產生缺頁中斷,內存管理器便將對應的內存頁從新從硬盤調入物理內存。
內存映射文件是由一個文件到一塊內存的映射,使應用程序能夠經過內存指針對磁盤上的文件進行訪問,其過程就如同對加載了文件的內存的訪問,所以內存文件映射很是適合於用來管理大文件。
虛擬內存實現的基礎是分頁機制和局部性原理,架構在物理內存之上,其引入是由於實際的物理內存運行程序所需的空間,即便如今計算機中的物理內存愈來愈大,將全部運行着的程序所有加載到內存中很是不現實。
內存映射文件虛擬性並非因爲局部性,而是使進程虛擬地址空間的某個區域創建映射磁盤文件的所有或部份內容,經過該區域能夠直接對被映射的磁盤文件進行訪問,而沒必要執行文件I/O操做也無需對文件內容進行緩衝處理。
用圖來表示mmap,即爲以下所示。mmap函數會在內存中找一段空白內存,而後將這部份內存與文件的內容對應起來。咱們對內存的全部操做都會直接反應到文件中去。mmap的主要功能就是創建內存與文件這種對應關係。因此才被命名爲memory map。
此圖爲 Linux 中進程的虛擬存儲器,即進程的虛擬地址空間, 32 位操做系統,就有2^32 = 4G的虛擬地址空間,
圖中有一塊區域: 「共享庫的內存映射區域」 ,這段區域就是在內存映射文件的時候將某一段的虛擬地址和文件對象的某一部分創建起映射關係,此時並無拷貝數據到內存中去,而是當進程代碼第一次引用這段代碼內的虛擬地址時,觸發了缺頁異常,這時候OS根據映射關係直接將文件的相關部分數據拷貝到進程的用戶私有空間中去。
自此文件IO的演化理論依據介紹完了,下一篇將會基於java的源碼去看各類實現。
文章內容參考:
《深刻理解計算機系統(原書第三版3)》,《清華大學計算機系列教材:計算機操做系統教程(第4版)》,示例圖片來源於其餘博客。