java虛擬機器:Java記憶體地區及對象

來源:互聯網
上載者:User

標籤:1.4   控制代碼   效能   重試   包含   檔案   .com   code   之間   

  說到Java記憶體地區,可能很多人第一反應是“堆棧”。首先堆棧不是一個概念,而是兩個概念,堆和棧是兩塊不同的記憶體地區,簡單理解的話,堆是用來存放對象而棧是用來執行程式的

 

圖中綠色部分就是所有線程之間共用的記憶體地區,而白色部分則是線程運行時專屬的資料區域,從這個分類角度來看一下這幾個資料區。

1、線程專屬的記憶體地區

(1)PROGRAM COUNTER REGISTER,程式計數器

這塊記憶體地區很小,它是當前線程所執行的位元組碼的行號指標,位元組碼解譯器通過改變這個計數器的值來選取下一條需要執行的位元組碼指令。Java方法這個計數器才有值,如果執行的是一個Native方法,那這個計數器是空的。

(2)JAVA STACK,虛擬機器棧

生命週期和線程相同。每個方法執行的同時都會建立一個棧幀,用於儲存局部變數表、運算元棧、動態連結、方法出口等資訊,每一個方法從調用直至執行完畢的過程,就對應著一個棧幀在虛擬機器中入棧到出棧的過程。棧的大小和具體JVM的實現有關,通常在256K~756K之間。

(3)NATIVE METHOD STACK,方法棧

和虛擬機器棧起的作用一樣,只不過方法棧為虛擬機器使用到的Native方法服務。虛擬機器規範並沒有對這個地區有什麼強制規定,因此我們使用的HotSpot虛擬機器,就乾脆沒有這塊地區了,它和虛擬機器棧是一起的。

2、線程間共用的記憶體地區

(1)HEAP,堆

大多數應用,堆都是Java虛擬機器所管理的記憶體中最大的一塊,它在虛擬機器啟動時建立,此記憶體唯一的目的就是存放對象執行個體。由於現在垃圾收集器採用的基本都是分代收集演算法,所以堆還可以細分為新生代和老年代,再細緻一點還有Eden區、From Survivior區、To Survivor區,這個後面都會講到的。

(2)METHOD AREA,方法區

這塊地區用於儲存虛擬機器載入的類資訊、常量、靜態變數、即時編譯器編譯後的代碼等資料,虛擬機器規範是把這塊地區描述為堆的一個邏輯部分的,但實際它應該是要和堆區分開的。從上面提到的分代收集演算法的角度看,HotSpot中,方法區≈永久代。不過JDK 7之後,我們使用的HotSpot應該就沒有永久代這個概念了,會採用Native Memory來實現方法區的規划了。

(3)RUNTIME CONSTANT POOL,運行時常量池

上面的圖中沒有畫出來,因為它是方法區的一部分。Class檔案中除了有類的版本資訊、欄位、方法、介面等描述資訊外,還有一項資訊就是常量池,用於存放編譯期間產生的各種字面量和符號引用,這部分內容將在類載入後進入方法區的運行時常量池中,另外翻譯出來的直接引用也會儲存在這個地區中。這個地區另外一個特點就是動態性,Java並不要求常量就一定要在編譯期間才能產生,運行期間也可以在這個地區放入新的內容,String.intern()方法就是這個特性的應用。

3、直接記憶體

想想還是把這塊加上。直接記憶體並不是虛擬機器運行時資料區的一部分,也不是Java虛擬機器規範中定義的記憶體地區。但是這部分記憶體也被頻繁地使用,而且也可能導致記憶體溢出問題。JDK1.4中新增加了NIO,引入了一種基於通道與緩衝區的I/O方式,它可以使用Native函數庫直接分配堆外記憶體,然後通過一個儲存在Java堆中的DirectByteBuffer對象作為這塊記憶體的引用進行操作。這樣能在一些情境中顯著提高效能,因為避免了在Java堆和Native堆中來回複製資料。顯然,本機直接記憶體的分配不會受到Java堆大小的限制,但是,既然是記憶體,肯定還是會受到本機總記憶體(包括RAM、SWAP區)大小以及處理器定址空間的限制。

 

對象建立

Java是一門物件導向的語言,Java程式運行過程中無時無刻都有對象被建立出來。在語言層面上,建立對象(複製、還原序列化)就是一個new關鍵字而已,但是虛擬機器層面上卻不是如此。看一下在虛擬機器層面上建立對象的步驟:

1、虛擬機器遇到一條new指令,首先去檢查這個指令的參數能否在常量池中定位到一個類的符號引用,並且檢查這個符號引用代表的類是否已經被載入、解析和初始化。如果沒有,那麼必須先執行類的初始化過程。

2、類載入檢查通過後,虛擬機器為新生對象分配記憶體。對象所需記憶體大小在類載入完成後便可以完全確定,為對象分配空間無非就是從Java堆中劃分出一塊確定大小的記憶體而已。這個地方會有兩個問題:

(1)如果記憶體是規整的,那麼虛擬機器將採用的是指標碰撞法來為對象分配記憶體。意思是所有用過的記憶體在一邊,閒置記憶體在另外一邊,中間放著一個指標作為分界點的指標,分配記憶體就僅僅是把指標向空閑那邊挪動一段與對象大小相等的距離罷了。如果垃圾收集器選擇的是Serial、ParNew這種基於壓縮演算法的,虛擬機器採用這種分配方式。

(2)如果記憶體不是規整的,已使用的記憶體和未使用的記憶體相互交錯,那麼虛擬機器將採用的是空閑列表法來為對象分配記憶體。意思是虛擬機器維護了一個列表,記錄上哪些記憶體塊是可用的,再分配的時候從列表中找到一塊足夠大的空間劃分給對象執行個體,並更新列表上的內容。如果垃圾收集器選擇的是CMS這種基於標記-清除演算法的,虛擬機器採用這種分配方式。

另外一個問題及時保證new對象時候的執行緒安全性。因為可能出現虛擬機器正在給對象A分配記憶體,指標還沒有來得及修改,對象B又同時使用了原來的指標來分配記憶體的情況。虛擬機器採用了CAS配上失敗重試的方式保證更新更新操作的原子性和TLAB兩種方式來解決這個問題。

3、記憶體配置結束,虛擬機器將分配到的記憶體空間都初始化為零值(不包括對象頭)。這一步保證了對象的執行個體欄位在Java代碼中可以不用賦初始值就可以直接使用,程式能訪問到這些欄位的資料類型所對應的零值。

4、對對象進行必要的設定,例如這個對象是哪個類的執行個體、如何才能找到類的中繼資料資訊、對象的雜湊碼、對象的GC分代年齡等資訊,這些資訊存放在對象的對象頭中。

5、執行<init>方法,把對象按照程式員的意願進行初始化,這樣一個真正可用的對象才算完全產生出來。

以上這部分內容,如果有下載OpenJDK的原始碼的話,可以通過參考hotspot/src/share/vm/interpreter/bytecodeInterpreter.cpp檔案,從1939行開始。1939行的代碼是CASE(_new):{...},意思是當代碼中遇見new這個關鍵字,虛擬機器做的事情。實際虛擬機器可能並不是執行的這段代碼,但是通過這段代碼來瞭解new對象的時候虛擬機器的運作過程基本上是沒問題的。

對象定位方式

建立對象是為了使用對象,Java程式需要通過棧上的reference(引用)資料來操作堆上的具體對象。比如我們寫了一句

Object obj = new Object()

而new Object()之後其實有兩部分內容,一部分是類資料(比如代表類的Class對象)、一部分是執行個體資料

由於reference在Java虛擬機器規範中只是一個指向對象new Object()的引用obj,並沒有規定obj應該通過何種方式去定位、訪問堆中對象的具體位置,所以對象訪問方式也是取決於虛擬機器而定的。主流方式有兩種:

1、控制代碼訪問。Java堆中劃分出一塊控制代碼池,obj指向的是對象的控制代碼地址,控制代碼中則包含了類資料的地址和執行個體資料的地址

2、指標訪問。對象中儲存所有的執行個體資料和類資料的地址,obj指向的是這個對象

HotSpot虛擬機器採用的是後者,不過前者的對象訪問方式也是十分常見的。

 

java虛擬機器:Java記憶體地區及對象

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.