關於java記憶體管理的基礎知識

來源:互聯網
上載者:User

平常工作中,發現有蠻多日常細節與記憶體管理有關,一直想要停下來總結總結,未果。這兩天和一朋友溝通時,虛擬位址與物理地址的mapping方式這個問題,讓平常一直考慮的關於top、mmap、ringbuffer、DirectByteBuffer等細節點在腦海中翻騰,竟然一時語塞。所以今天在家寫了點測試代碼,讓自己把思路理順,整理出來,希望這些基礎知識對大家有用。

1.硬體和實體記憶體實體記憶體概要

大家都知道,實體記憶體就是RAM。處理器通過記憶體匯流排串連到實體記憶體,匯流排位元(比如32位或者48位)決定了可定址的實體記憶體大小。這裡提到48位這個值,是提醒不要與CPU的寄存器頻寬混淆。X86_64的寄存器頻寬是64,但是物理地址位元可能是48。(實體位址延伸後,物理地址位元也可能大於寄存器頻寬)。

現在匯流排位元很大的情況下,只有物理地址受限了(物理地址主要受限於插槽的數量和成本)。實體記憶體分頁定址,每頁4K。對於32位的地址,第0頁從0x00000000到0x00001000。可以看到,只需要前20位用來定址物理頁,而後12位用來標示頁內地址。

思考一:這樣記憶體分頁使用有什麼優點呢?在本文最後講到ringbuffer時會分析這個問題。

實體記憶體和磁碟的互惠交易

在linux協調下,實體記憶體和磁碟間有“最惠國待遇條約”:

1)  實體記憶體充裕時:

linux會把一些實體記憶體用於io的buffer及cache,提升系統運行效率。

Linux下的sar -r命令結果中,總體可用的實體記憶體應該為:kbmemfree+kbbuffers+kbcached。

思考二:這裡使用的實體記憶體,它所mapping到的虛存會歸屬什麼進程呢?後面會有討論。

2)  實體記憶體不夠時:

linux會把實體記憶體的一部分資料放入磁碟swap區儲存,以騰出記憶體給程式使用。

Vmstat命令結果中,swap下的si是每秒從磁碟讀到記憶體的資料量,so是從記憶體寫到磁碟的資料量。

建立Swap時(mkswap命令)可以用swap分區,也可以用普通檔案。我實驗了一下,用swap分區方式,在swapon /dev/hdc7後,used、free、buffer、cache記憶體都有增長;而用檔案方式,這四個量都不變。

思考三:這裡說的把檔案對應到實體記憶體,與下文提到的MappedByteBuffer映射到虛擬記憶體情境是不一樣的。

 

 

2.進程和虛擬記憶體現在都是64位OS,所以進程的虛擬定址空間基本沒有限制了;但是它mapping到的實體記憶體還是受限的。虛擬記憶體一對多映射

進程的虛擬位址空間中的地區可被映射到實體記憶體、檔案或任何其他可定址儲存。這裡的地區也使用分頁機制,當一個程式嘗試使用虛擬位址訪問記憶體時,作業系統連同硬體會將該分頁的虛擬位址映射到物理位置,這個位置可以是物理RAM、一個檔案或分頁檔(交換分區)。

下面是從IBM網站找來的概要圖,pagefile是windows的說法,對應linux下的swap。

圖一:虛擬記憶體映射概要

中的進程可以是使用者進程,也可以是核心進程(比如init fork出所有的進程)。使用者進程的虛擬位址空間又有使用者空間和核心空間(os空間)。每個進程都擁有自己的虛擬位址空間(邏輯地址地區)其大小由該系統上的地址大小規定。比如32位windows的單進程可定址空間是4G,但是實體位址延伸後可能有64G。

思考四:我們開發中用的MappedByteBuffer其實說的通俗一點就是Map把檔案的內容映像到電腦虛擬記憶體的一塊地區,這樣就可以直接操作記憶體當中的資料,而無需每次都通過I/O去物理硬碟讀取檔案,所以效率上有很大的提升。MappedByteBuffer主要使用情境有:需要用檔案分享權限設定來實現處理序間通訊(使用同一個檔案inode);需要寫記憶體同時自動持久化到檔案。


虛擬記憶體多對一映射

思考五:在圖一中,虛擬記憶體地址可以指向同一個實體記憶體地址嗎?一些嵌入式OS中,程式的確直接使用全部的實體記憶體;但是windows和linux是具有虛擬記憶體的作業系統,虛擬記憶體允許多個進程共用實體記憶體(比如OS只佔用一段實體記憶體,但是每個進程都有核心地址空間,映射到同一段實體記憶體)。

儘管每個進程都有其自己的地址空間,但程式通常無法使用所有這些空間。地址空間被劃分為核心空間和使用者空間。大部分作業系統將每個進程地址空間的一部分映射到一個通用的核心記憶體地區。被映射來供核心使用的地址空間部分稱為核心空間,其餘部分稱為使用者空間,可供使用者應用程式使用。

在思考二中,實體記憶體被用於buffer與cache,大都是核心空間的行為。預設情況下,32 位 Windows 擁有 2GB 使用者空間和 2GB 核心空間;而linux分別是3G和1G。

核心是主要的作業系統程式,包含用於串連電腦硬體、發送器以及提供連網和虛擬記憶體等服務的邏輯。作為電腦啟動序列的一部分,作業系統核心運行並初始化硬體。

如果使用者程式需要來自作業系統的服務,它可以執行一種稱為系統調用的操作與核心程式互動。系統調用通常是讀取和寫入檔案、連網和啟動新進程等操作所必需的。Mmap系統調用實現時

3 Java進程使用的記憶體

在 Linux 和 Windows 上,進程是一個由受作業系統控制的資源(比如檔案和通訊端資訊)、一個典型的虛擬位址空間(在某些架構上不止一個)和至少一個執行線程構成的集合。

Java是單進程應用,和普通進程沒有本質區別。

有了上面的分析,可以很容易明白為什麼我們用top看到的java進程消耗的記憶體有時候會大於-Xmx 與-XX:MaxPermSize的和。java進程消費的記憶體包括JVM對應的實體記憶體和java應用消費的JVM之外的實體記憶體,還包括核心空間地址,我們一般情況下不能用top來判斷多少是JVM消耗的、多少是JVM外的記憶體。

Java進程使用的記憶體一般有如下這些:

1)  java堆和永久代。

2)  線程堆棧。-Xss可以調整,棧深度不夠時拋出StackOverflowException,無記憶體可以分配於新線程建立時拋出OutOfMemoryError。

3)  JIT編譯、JNI代碼、GC。

4)  Socket緩衝區。每個Socket串連的Receive緩衝區約37KB,Send緩衝區約25KB,在串連數多的情況下也是很可觀的。這個對應到核心地址空間。

5)  DirectMemory。平常開發用的一些架構(比如CometD),會有大量的NIO操作使用到DirectByteBuffer,它通過native庫直接分配堆外記憶體,這裡使用的空間也在虛擬記憶體位址範圍內,受進程可訪問空間的限制,也可能導致OutofMemoryError。

寫了一個簡單的程式來測試DirectByteBuffer對top和jstat的結果的影響:

1)  DirectByteBuffer在堆中的引用清空後,gc,也可以釋放堆外實體記憶體。

2)  程式啟動時,-Xms的空間只是被分配地址空間,top不計入使用的記憶體。

3)  使用過記憶體後,即使堆記憶體GC,還是會計入使用的記憶體。這個可能與新生代使用“複製演算法”而不是“標記-整理演算法”來實現GC有關係。

4 分頁機制與ringbuffer

Ringbuffer也使用了mmap技術,但是這裡我們要討論的是思考一中的問題:它借鑒記憶體分頁技術,可以用來做什嗎?

前面的4K分頁技術,可以前20位用來定址物理頁,而後12位用來標示頁內地址;其實可以再演化,比如用前10位表示第幾個4M的檔案,後22位表示是4M檔案中的第幾個。這樣可以在Ringbuff中設定不同的slot大小,用來解決記憶體片段、快取、檔案轉記憶體等問題。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.