摘要:運行時數據區域之所以要劃分這么多區域出來是因為這些區域都有自己的用途,以及創建和銷毀的時間。,運行時常量池它是方法區的一部分。直接內存直接內存并不是虛擬機運行時數據區的一部分,也不是虛擬機規范中定義的內存區域。
前言
說到JAVA內存區域,可能很多人第一反應是“堆棧”。
首先,堆棧不是一個概念,而是兩個概念,堆和棧是兩塊不同的內存區域,簡單理解的話,堆是用來存放對象而棧是用來運行程序的。
其次,堆內存和棧內存的這種劃分方式比較粗糙,這種劃分方式只能說明大多數程序員最關注的、與對象內存分配關系最密切的內存區域是這兩塊,Java內存區域劃分實際上遠比這復雜。
對于Java程序員來說,在虛擬機自動內存管理機制的幫助下,不再需要為每一個new操作去配對deleted/free代碼,不容易出現內存泄漏和內存溢出問題。
但是,也正是因為Java把內存控制權交給了虛擬機,一旦出現內存泄漏和內存溢出的問題,就難以排查,因此一個好的Java程序員應該去了解虛擬機的內存區域以及會引起內存泄漏和內存溢出的場景。
之所以要劃分這么多區域出來是因為這些區域都有自己的用途,以及創建和銷毀的時間。有些區域隨著虛擬機進程的啟動而存在,有的區域則依賴用戶線程的啟動和結束而銷毀和建立。
1.線程獨有的內存區域
(1) PROGRAM COUNTER REGISTER,程序計數器
這塊內存區域很小,它是當前線程所執行的字節碼的行號指示器,字節碼解釋器通過改變這個計數器的值來選取下一條需要執行的字節碼指令。Java方法這個計數器才有值,如果執行的是一個Native方法,那這個計數器是空的。
(2) JAVA STACK,虛擬機棧
生命周期和線程周期相同。每個方法執行的同時都會創建一個棧幀,用于存儲局部變量表、操作數棧、動態鏈接、方法出口等信息,每一個方法從調用至執行完畢的過程,就對應一個棧幀在虛擬機棧中入棧到出棧的過程。棧的大小和具體JVM的實現有關,通常在256K~756Kz之間。
(3) NATIVE METHOD STACK,方法棧
和虛擬機棧起的作用一樣,只不過方法棧為虛擬機使用到的Native方法服務。虛擬機規范并沒有對這個區域有什么強制規定,因此我們使用的HotSpot虛擬機,就干脆沒有這塊區域了,它和虛擬機棧是一起的。
2.線程間共享的內存區域
(1) HEAP,堆
大多數應用,堆都是Java虛擬機所管理的內存中最大的一塊,它在虛擬機啟動時創建,此內存唯一目的就是存放對象實例。由于現在垃圾收集器采用的基本都是分代收集算法,所以堆還可以細分為新生代和老年代,再細致一點還有Eden區、From Survivor區、To Survivor區。
(2) METHOD AREA,方法區
這塊區域用于存儲虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯后的代碼等數據,虛擬機規范是把這塊區域描述為堆的一個邏輯部分的,但實際它應該是要和堆區分開的。從上面提到的分代收集算法的角度來看,HotSpot中,方法區≈永久代。不過JDK7之后,我們使用的HotSpot應該就沒有永久代這個概念了,會采用Native Memory來實現方法區的規劃了。
(3) RUNTIME CONSTANT POOL,運行時常量池
它是方法區的一部分。Class文件中除了有類的版本信息、字段、方法、接口等描述信息外,還有一項信息就是常量池,用于存放編譯期間生成的各種字面量和符號引用,這部分內容將在類加載后進入方法區的運行時常量池中,另外翻譯出來的直接引用也會存儲在這個區域中。
(在JVM中,類從被加載到虛擬機內存中開始,到卸載出內存為止,它的整個生命周期包括:加載、驗證、準備、解析、初始化、使用和卸載7個階段。而解析階段即是虛擬機將常量池內的符號引用替換為直接引用的過程。
在Java中,一個java類將會編譯成一個class文件。在編譯時,java類并不知道所引用的類的實際地址,因此只能使用符號引用來代替。而解析階段即是虛擬機將常量池內的符號引用替換為直接引用的過程,翻譯出來的直接引用也是存儲在方法區的運行時常量池中。)
3.直接內存
直接內存并不是虛擬機運行時數據區的一部分,也不是Java虛擬機規范中定義的內存區域。但是這部分內存也被頻繁地使用,而且也可能導致內存溢出的問題。JDK1.4中新增加了NIO,引入了一種基于通道與緩沖區的I/O方式,它可以使用Native函數庫直接分配堆外內存,然后通過一個存儲在Java堆中的DirectByteBuffer對象作為這塊內存的引用進行操作。這樣能在一些場景中顯著提高性能,因為避免了在Java堆和Native堆中來回復制數據。顯然本機直接內存的分配不會受到Java堆大小的限制,但是,既然是內存,肯定還是會受到本機總內存大小及處理器尋址空間的限制。
我們來看一下在虛擬機層面上創建對象的步驟:
1.虛擬機遇到一條new指令,首先去檢查這個指令的參數能否在常量池中定位到一個類的符號引用,并且檢查這個符號引用代表的類是否已經被加載、解析和初始化。如果沒有,那么必須執行類的初始化過程。
2.類加載檢查通過后,虛擬機為新生對象分配內存。對象所需內存大小在類加載完成后便可以完全確定,為對象分配空間無非是從Java堆中劃分出一個確定大小的內存而已。這個地方會有兩個問題:
(1) 如果內存是規整的,那么虛擬機將采用的是指針碰撞法來為對象分配內存。意思是所有用過的內存在一邊,空閑的內存在另一邊,中間放著一個指針作為分界點指示器,分配內存就僅僅是把指針向空閑那邊挪動一段與對象大小相等的距離罷了。如果垃圾收集器選擇的是Serial、ParNew這種基于壓縮算法的,虛擬機采用這種分配方式。
(2) 如果內存不是規整的,已使用的內存和未使用的內存相互交錯,那么虛擬機將采用的是空閑列表法來為對象分配內存。意思是虛擬機維護了一個列表記錄了哪些內存是可用的,再分配的時候從列表中找到一塊足夠大的空間劃分給實例,并更新列表上的內容。如果垃圾收集器選擇是CMS這種基于標記-清除(Mark-Sweep)算法的,虛擬機采用這種分配方式。
簡言之,
垃圾收集器選擇的是Serial、ParNew這種基于Compact(壓縮)算法的,虛擬機采用指針碰撞法為對象分配內存;
垃圾收集器選擇是CMS這種基于Mark-Sweep(標記-清除)算法的,虛擬機采用空閑列表法為對象分配內存。
另外一個問題即是保證new對象時候的線程安全。因為可能出現虛擬機正在給對象A分配內存,指針還沒有來得及修改,對象B又同時使用了原來的指針來分配內存的情況。虛擬機采用了CAS配上失敗重試和TLAB兩種方式保證更新操作的原子性來解決這個問題。
(每個線程在Java堆中預先分配一小塊內存,成為本地線程分配緩沖區——TLAB,線程內部需要分配內存時直接在TLAB上分配就行,避免了線程沖突。只有當緩沖區的內存用光需要重新分配內存的時候才會進行CAS操作分配更大的內存空間。虛擬機是否使用TLAB,可以通過-XX:+/-UseTLAB參數來進行配置,但是JDK5及以后的版本默認是啟用TLAB的)
3.內存分配結束,虛擬機講分配到的內存空間都初始化為零值(不包括對象頭)。這一步保證了對象的實例字段在Java代碼中可以不用賦初始值就可以直接使用,程序能訪問到這些字段的數據類型所對應的零值。
4.對對象進行必要的配置,例如這個對象是哪個類的實例、如何才能找到類的元數據信息、對象的哈希嗎、對象的GC分代年齡等信息,這些信息存放在對象的對象頭中。
5.執行
建立對象是為了使用對象,我們的Java程序需要通過棧上的reference數據來操作對上的具體對象。
比如,Object obj = new Object();而new Object()之后其實有兩部分內容:一部分是類數據(方法區)、一部分是實例數據(堆)。
由于reference類型在Java虛擬機規范里面只規定了是一個指向對象的引用,并沒有定義這個引用應該通過什么方式去定位、訪問到堆中的對象的具體位置,對象訪問方式也是取決于虛擬機實現而定的。主流的訪問方式有使用句柄和直接指針兩種。
1.使用句柄
在Java堆中將會劃分出一塊內存來作為句柄池,reference中存儲的就是對象的句柄地址,而句柄中包含了對象實例與類型數據的具體各自的地址信息,
2.使用直接指針
使用直接指針訪問的話,Java堆對象的布局中就必須考慮如何放置訪問類型數據的相關信息,reference中存儲的直接就是對象地址,
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/69081.html
摘要:下面的截圖內容來自從規范我們可以看到,規范要求的運行時數據區域有程序計數器虛擬機棧堆方法區本地方法棧運行時常量池這及部分。查了一下,還是沒有查到官方對于運行時數據區域的說明,但是許多博客都指出將字符串常量池移動到了堆中。 不少java程序員一提JVM運行時數據區域,就會說堆和棧,當然也有java程序員給出方法區、虛擬機棧、本地方法棧、堆、程序計數器這個答案,但是還有人給出永久代、虛擬機...
摘要:框架說明開發者都知道會執行字節碼。但是可能大多數人都不知道一個事實是的實現,它分析字節碼,解釋并執行代碼。執行引擎字節碼加載到運行時數據區后,會被執行引擎執行。解釋器更快的解釋字節碼,但是執行非常慢。垃圾收集收集并移除不再被使用的對象。 JVM框架說明 java開發者都知道JRE(Java Runtime Environment)會執行字節碼。但是可能大多數人都不知道一個事實:JRE是...
摘要:對字節碼文件進行解釋執行,把字節碼翻譯成相關平臺上的機器指令。使用命令可對字節碼文件以及配置文件進行打包可對一個由多個字節碼文件和配置文件等資源文件構成的項目進行打包。和不存在永久代這種說法。 Java技術體系 從廣義上講,Clojure、JRuby、Groovy等運行于Java虛擬機上的語言及其相關的程序都屬于Java技術體系中的一員。如果僅從傳統意義上來看,Sun官方所定義的Jav...
摘要:方法區在實際內存空間站可以是不連續的。這一規定,可以說是給了虛擬機廠商很大的自由。但是值得注意的是,堆其實還未每一個線程單獨分配了一塊空間,這部分空間在分配時是線程獨享的,在使用時是線程共享的。 在我的博客中,之前有很多文章介紹過JVM內存結構,相信很多看多我文章的朋友對這部分知識都有一定的了解了。 那么,請大家嘗試著回答一下以下問題: 1、JVM管理的內存結構是怎樣的? 2、不同的...
摘要:編譯器只需面向,生成能理解的代碼或字節碼文件。源文件經編譯器,編譯成字節碼程序,通過將每一條指令翻譯成不同平臺機器碼,通過特定平臺運行。漲見識,字節碼執行過程分析。解決辦法減少默認棧的容量來換取更多的線程支持。 前言 JVM是java的核心和基礎,在java編譯器和os平臺之間的虛擬處理器。它是一種基于下層的操作系統和硬件平臺并利用軟件方法來實現的抽象的計算機,可以在上面執行java的...
閱讀 1948·2021-11-19 09:40
閱讀 2140·2021-10-09 09:43
閱讀 3297·2021-09-06 15:00
閱讀 2815·2019-08-29 13:04
閱讀 2773·2019-08-26 11:53
閱讀 3531·2019-08-26 11:46
閱讀 2326·2019-08-26 11:38
閱讀 396·2019-08-26 11:27