理解 Java I

說明

在討論 Java I/O 之前要先討論以下內容:

  • 緩衝區操作
  • 內核空間用戶空間
  • 虛擬內存
  • 文件 I/O , 流 I/O
  • UNIX I/O 模型

理解了以上內容會對 I/O 有比較清晰的認識 。


緩衝區操作

緩衝區是所有類型 I/O 的基礎 , I/O 就是把數據從緩衝區中移進或者移出。


理解 Java I/O

I/O 過程是將數據在用戶空間進程緩衝區和內核緩衝區之間進行移動 , 數據的來源是外部的 I/O 設備。當進程請求 I/O 操作時 , 會執行一個系統調用,將控制權移交給系統內核。比如 C/C++ 語言的底層函數 open() , read() , write() , close() , 要做的就是執行系統調用。當內核被調用時,它要找到進程所需的數據,並把數據傳送到用戶空間內指定的緩衝區。內核試圖對數據進行高速緩存或者預讀取,因此進程所需要的數據可能已經在內核空間中了,此時只需要把這些數據拷貝到用戶空間中即可。如果數據不在內核空間中,那麼內核空間要去讀取數據,用戶空間進程被掛起。

數據從內核空間到用戶空間需要進行一次內存拷貝,無法直接將數據從I/O設備傳送到用戶空間。因為,硬件設備通常不能直接訪問用戶空間;像磁盤這種基於塊存儲的硬件設備操作的是固定大小的數據塊,而用戶進程請求的可能是任意大小的或非對齊的數據塊。內核負責了對從I/O設備獲取的數據進行處理。


內核空間、用戶空間

用戶空間是常規進程所在區域,內核空間是操作系統以及一些驅動所在區域。應用程序在用戶模式下運行,操作系統在內核模式下運行。

每個用戶模式進程都有各自專用的虛擬地址空間,在內核模式運行下的所有代碼都稱為“系統空間”的單個虛擬地址空間。用戶模式進程的虛擬地址空間稱為“用戶空間”。用戶模式下運行的代碼可以訪問用戶空間,但是不能訪問系統空間。內核模式下運行的代碼可以訪問系統空間和用戶空間。


虛擬內存

從用戶空間到內核空間 I/O 過程中會存在一次內存拷貝操作,利用虛擬內存技術可以避免這一次內存拷貝。

虛擬內存維基百科:虛擬內存是計算機系統內存管理的一種技術。它使得應用程序認為它擁有連續可用的內存(一個連續完整的地址空間),而實際上,它通常是被分隔成多個物理內存碎片,還有部分暫時存儲在外部磁盤存儲器上,在需要時進行數據交換。與沒有使用虛擬內存技術的操作系統相比,使用這種技術的操作系統使得大型程序的編寫變得更容易,對物理內存的使用也更有效率。

注意:虛擬內存不只是“用磁盤空間來擴展物理內存”的意思,這只是擴充內存級別已使其包含硬盤驅動器而已。把內存擴展到磁盤只是使用虛擬內存技術的一個結果,它的作用也可以通過覆蓋或者把處於不活躍狀態的程序以及他們的數據全部交換到磁盤上的方式來實現。對虛擬內存的定義是基於對地址空間的重定義的,即把地址空間定義為“連續的虛擬內存地址”,以藉此“欺騙”程序,使他們以為自己正在使用一大塊“連續”地址。


理解 Java I/O


理解 Java I/O

目前我大概的理解是,內核空間虛擬和用戶空間虛擬映射的相同的物理內存區域,因為每一個進程的用戶空間是獨立的,內核空間可以操作任意用戶空間。內核把數據存入這片內存區域後對用戶進程來說也是可見的,這樣就避免了內存拷貝。(不過我覺得這種理解應該是存在問題的,虛擬內存技術也不是一兩句話能說清楚的,還需要以後學習研究)。


文件 I/O 、流 I/O

I/O 從廣義上分為兩大類, 文件 I/O , 流 I/O 。文件 I/O 屬於文件系統的範疇,文件系統與磁盤是迥然不同。磁盤只是數據存儲的地方,磁盤是硬件設備並不理解文件的概念。文件系統是更高層次的抽象,是安排、解釋磁盤數據的一種獨特方式。文件系統定義了文件名、路徑、文件屬性等抽象概念。流 I/O 原理模仿了通道,I/O 字節流必須順序存取 ,例如 : 控制檯設備 , 打印機端口 , 網絡連接。網絡通信過程就是 Stream I/O , 主要學習研究這方面 。


UNIX I/O 模型

  • 阻塞 I/O (bloking I/O)
  • 非阻塞 I/O(non-blocking I/O)
  • 多路複用 I/O (multiplexing I/O)
  • 信號驅動 I/O (signal-driven I/O)
  • 異步 I/O (asynchronous I/O)


阻塞 I/O 模型


理解 Java I/O

第①步應用程序觸發操作系統讀取數據;

第②步控制器移交給內核,如果有數據可讀就進行讀取,沒有數據可讀就等待;

第③步讀取到數據 , 將數據從內核空間緩衝區拷貝到用戶空間緩衝區;

第④步數據拷貝完成內核通知應用程序讀取數據成功;

在這4步完成之前, 應用程序進程將一直處於阻塞狀態。


非阻塞 I/O 模型


理解 Java I/O

非阻塞模式做的改進是 , 在第④步沒有完成之前,輪詢的執行第 ① 步 , 此時引用程序進程不會阻塞,在沒有收到成功指示的時候,進程可以去做別的事情,當收到成功指示後再去處理讀取到的數據即可,不需要一直阻塞等待。


多路複用 I/O 模型

多路複用 I/O 就是經常說的 select , poll , epoll 有寫地方也稱這種 I/O 方式為 event driven I/O 。多路複用 I/O 的好處就是一個進程可以處理多個網絡連接 I/O,它的工作原理就是 select/poll/epoll 函數會不斷的查詢所監測的 socket 文件描述符中是否有 socket 準備好讀寫了,如果有,那麼系統就會通知用戶進程。


理解 Java I/O

select 不會像 阻塞I/O 那樣長時間阻塞直到有數據可讀, select 遍歷所有的 socket 返回其中處於可讀狀態的。然後應用程序進程就可以對這些 socket 進行 I/O 操作 , 由於這些 socket 中已經有數據了 ,所以此時只需要進行內存拷貝,將數據從內核空間拷貝到用戶空間中就完成了 I/O 操作。select 最大的缺陷是單個進程所打開的 socket 描述符是有一定限制的,它由 FD_SETSIZE 設置,默認是 1024 。對於需要成千上萬個 TCP 連接的大型服務器來說太少了。epoll 並沒有這個限制 , 它所支持的 FD 上限是操作系統的最大文件句柄數,例如在內存 1G 的機器上大約是 10萬個句柄。select/poll 的另一個致命缺點,當擁有一個很大的 socket 集合時,由於網絡延時或者鏈路空閒,任意時刻只有少部分的 socket 是“活躍”的,但是 select/poll 每次調用都會線性的掃描全部 socket 集合,導致了效率呈線性下降。epoll 不會存在這個問題 , 它只會對 “活躍” 的 socket 進行操作。

阻塞模式 ,和非阻塞模式一次都只能處理一個 I/O 操作 。多路複用模型可以一次處理多個 I/O 操作。event driven 的思想體現在 , 可以選擇處於不同狀態的 socket , 比如 accept , connect , read , write , 更具不同的狀態進行相應的處理。


信號驅動 I/O


理解 Java I/O

Signal Driven I/O 的工作原理就是用戶進程首先和 kernel 之間建立信號的通知機制,即用戶進程告訴 kernel,如果 kernel 中數據準備好了,就通過 SIGIO 信號通知我。然後用戶空間的進程就會調用 read 系統調用將準備好的數據從 kernel 拷貝到用戶空間。

但是這種 I/O 模型存在一個非常重大的缺陷問題:SIGIO 這種信號對於每個進程來說只有一個!如果使該信號對進程中的兩個描述符(這兩個文件描述符都等待著 I/O 操作)都起作用,那麼進程在接到此信號後就無法判別是哪一個文件描述符準備好了。所以

Signal Driven I/O 模型在現實中用的非常少。


異步 I/O


理解 Java I/O


阻塞式 I/O Java 服務端通信模型

Java 是運行在 JVM 之上 , JVM 運行在操作系統之上,JVM 是一個用戶進程 。 Java 應用程序並非是真的受著 I/O 的束縛。操作系統並非不能快速的傳遞數據 。是因為 JVM 在 I/O 方面效率欠佳。操作系統與 Java 基於流的 I/O 模型有寫不匹配。操作系統要移動的是大塊的數據(緩衝區) ,而 JVM 的 I/O 類喜歡操作一小塊數據 — 單一字節、幾行文本。結果操作系統送來整塊緩衝區的數據,Java I/O 流數據類再花大量時間把他們拆成小塊,往往拷貝一小塊就要往返幾層對象。 JDK 在 1.4 之前是隻支持阻塞式 I/O 的,Java 的網絡編程也只能是基於阻塞式 I/O 的模式工作,為了避開 I/O 時線程阻塞的問題,只能採用多線程處理連接請求。


理解 Java I/O

每當有一個客戶端連接,服務端都要分配一個新的線程來處理這個客戶端請求,隨著客戶端的增多服務端線程也線性增長,內存開銷增大,CPU 上下文切換性能開銷大。線程是 JVM 非常寶貴的系統資源,當線程數非常多以後,系統性能急劇下降。這種模式在高併發,大訪問量的場景下舉步維艱。


理解 Java I/O


分享到:


相關文章: