您好,登錄后才能下訂單哦!
本文小編為大家詳細介紹“Java虛擬機中內存區域怎么分配”,內容詳細,步驟清晰,細節處理妥當,希望這篇“Java虛擬機中內存區域怎么分配”文章能幫助大家解決疑惑,下面跟著小編的思路慢慢深入,一起來學習新知識吧。
程序計數器(Program Counter Register)所占的內存空間不大,很小一塊,可以看作是當前線程所執行的字節碼指令的行號指示器。字節碼解釋器會在工作的時候改變這個計數器的值來選取下一條需要執行的字節碼指令,像分支、循環、跳轉、異常處理、線程恢復等功能都需要依賴這個計數器來完成。
在 JVM 中,多線程是通過線程輪流切換來獲得 CPU 執行時間的,因此,在任一具體時刻,一個 CPU 的內核只會執行一條線程中的指令,因此,為了線程切換后能恢復到正確的執行位置,每個線程都需要有一個獨立的程序計數器,并且不能互相干擾,否則就會影響到程序的正常執行次序。
也就是說,我們要求程序計數器是線程私有的。
《Java 虛擬機規范》中規定,如果線程執行的是非本地(native)方法,則程序計數器中保存的是當前需要執行的指令地址;如果線程執行的是本地方法,則程序計數器中的值是 undefined。
為什么本地方法在程序計數器中的值是 undefined 的?因為本地方法大多是通過 C/C++ 實現的,并未編譯成需要執行的字節碼指令。
由于程序計數器中存儲的數據所占的空間不會隨程序的執行而發生大小上的改變,因此,程序計數器是不會發生內存溢出現象(OutOfMemory)的。
Java 虛擬機棧中是一個個棧幀,每個棧幀對應一個被調用的方法。當線程執行一個方法時,會創建一個對應的棧幀,并將棧幀壓入棧中。當方法執行完畢后,將棧幀從棧中移除。棧遵循的是后進先出的原則,所以線程當前執行的方法對應的棧幀必定在 Java 虛擬機棧的頂部。
顧名思義,就是用來存儲方法中的局部變量的,包括方法的參數。對于基本數據類型的變量,直接存儲變量的值;對于引用類型的變量,存儲的是對象的引用。局部變量表的大小在編譯期間就確定了,程序執行期間,它的大小是不會改變的。
表達式的計算是在操作數棧中完成的。當一個方法剛開始執行的時候,這個方法的操作數棧是空的,在方法的執行過程中,會有各種字節碼指令往操作數棧中寫入和提取內容,也就是入棧/出棧操作。例如,在做算術運算的時候是通過操作數棧來進行的,又或者在調用其他方法的時候是通過操作數棧來進行參數傳遞的。
當前方法所屬的類的運行時常量池的引用,引用其他的常量類或者使用字符串常量池中的字符串。
方法執行完(不論是正常執行還是發生了異常)后需要返回到方法被調用的位置,程序才能繼續執行,方法返回地址保存一些用來幫助恢復上層方法的執行狀態的信息。
每個棧幀都包含了一個指向運行時常量池中該棧幀所屬方法的引用,持有這個引用是為了支持方法調用過程中的動態鏈接。
與程序計數器一樣,Java 虛擬機棧也是線程私有的,它的生命周期和線程相同,描述的是 Java 方法執行的內存模型,每次方法調用的數據都是通過棧傳遞的。
Java 虛擬機棧會出現兩種錯誤:
StackOverFlowError:當線程請求棧的深度超過 Java 虛擬機棧的最大深度的時候拋出。
OutOfMemoryError:如果 Java 虛擬機棧允許動態擴容,當棧擴容時無法申請到足夠的內存時拋出。
最有名的 HotSpot 虛擬機的棧容量是不允許動態擴容的,所以在 HotSpot 虛擬機上是不會出現 OutOfMemoryError 的。
本地方法棧與 Java 虛擬機棧類似,區別是本地方法棧執行的是本地方法,也就是帶有 native 關鍵字修飾的方法。
在 HotSpot 虛擬機中,本地方法棧和 Java 虛擬機棧不做區分。
堆是所有線程共享的一塊內存區域,在 Java 虛擬機啟動的時候創建,用來存儲對象(數組也是一種對象)。
以前,Java 中“幾乎”所有的對象都會在堆中分配,但隨著 JIT(Just-In-Time)編譯器的發展和逃逸技術的逐漸成熟,所有的對象都分配到堆上漸漸變得不那么“絕對”了。從 JDK 7 開始,Java 虛擬機已經默認開啟逃逸分析了,意味著如果某些方法中的對象引用沒有被返回或者未被外面使用(也就是未逃逸出去),那么對象可以直接在棧上分配內存。
簡單解釋一下 JIT 和逃逸分析。
常見的編譯型語言如 C++,通常會把代碼直接編譯成 CPU 所能理解的機器碼來運行。而 Java 為了實現“一次編譯,處處運行”的特性,把編譯的過程分成兩部分,首先它會先由 javac 編譯成通用的中間形式——字節碼,然后再由解釋器逐條將字節碼解釋為機器碼來執行。所以在性能上,Java 可能會干不過 C++ 這類編譯型語言。
為了優化 Java 的性能 ,JVM 在解釋器之外引入了 JIT 編譯器:當程序運行時,解釋器首先發揮作用,代碼可以直接執行。隨著時間推移,即時編譯器逐漸發揮作用,把越來越多的代碼編譯優化成本地代碼,來獲取更高的執行效率。解釋器這時可以作為編譯運行的降級手段,在一些不可靠的編譯優化出現問題時,再切換回解釋執行,保證程序可以正常運行。
逃逸分析(Escape Analysis),簡單來講就是,Hotspot 虛擬機可以分析新創建對象的使用范圍,并決定是否在 Java 堆上分配內存的一項技術。
堆是 Java 垃圾收集器管理的主要區域,因此也被稱作 GC 堆(Garbage Collected Heap)。從垃圾回收的角度來看,由于垃圾收集器基本都采用了分代垃圾收集的算法,所以堆還可以細分為:新生代和老年代。新生代還可以細分為:Eden 空間、From Survivor、To Survivor 空間等。進一步劃分的目的是更好地回收內存,或者更快地分配內存。
堆這最容易出現的就是 OutOfMemoryError 錯誤,分為以下幾種表現形式:
OutOfMemoryError: GC Overhead Limit Exceeded:當 JVM 花太多時間執行垃圾回收并且只能回收很少的堆空間時,就會發生該錯誤。
java.lang.OutOfMemoryError: Java heap space:假如在創建新的對象時, 堆內存中的空間不足以存放新創建的對象, 就會引發該錯誤。和本機的物理內存無關,和我們配置的虛擬機內存大小有關!
JDK 8 的時候,原有的方法區(更準確的說應該是永久代)被徹底移除,取而代之的是元空間。
我們來說說方法區吧。方法區和堆一樣,是線程共享的區域,它用來存儲已經被 Java 虛擬機加載的類信息、常量、靜態變量,以及便器編譯后的代碼等。
在有些地方,方法區也被稱為永久代。但其實不能這么理解。
《Java 虛擬機規范》中只規定了有方法區這么一個概念和它的作用,并沒有規定如何去實現它。那么不同的 Java 虛擬機可能就會有不同的實現。永久代是 HotSpot 對方法區的一種實現形式。也就是說,永久代只是 HotSpot 中的一個概念,而方法區則是 Java 虛擬機規范中的一個定義,一種規范。
換句話說,方法區和永久代的關系就像是 Java 中接口和類的關系,類實現了接口。
在方法區中,還有一塊非常重要的部分,也就是運行時常量池。在講 class 文件的時候,提到了每個 class 文件都會有個常量池,用來存放字符串常量、類和接口的名字、字段名、常量等等。運行時常量池和 class 文件的常量池是一一對應的,它就是通過 class 文件中的常量池來構建的。
JDK 7 之前,運行時常量池中包含著字符串常量池,都在方法區。
JDK 7 的時候,字符串常量池從方法區中拿出來放到了堆中,運行時常量池中的其他東西還在方法區中。
JDK 8 的時候,HotSpot 移除了永久代,也就是說方法區不存在了,取而代之的是元空間。也就意味著字符串常量池在堆中,運行時常量池跑到了元空間。
再來說說為什么要將永久代 (PermGen) 或者說方法區替換為元空間 (MetaSpace) 。
永久代放在 Java 虛擬機中,就會受到 Java 虛擬機內存大小的限制,而元空間使用的是本地內存,也就脫離了 Java 虛擬機內存的限制。
JDK 8 的時候,在 HotSpot 中融合了 JRockit 虛擬機,而 JRockit 中并沒有永久代的概念,因此新的 HotSpot 就沒有必要再開辟一塊空間來作為永久代了。
對于我們 Java 程序員來說,不需要像 C/C++ 程序員那樣時時刻刻關心著內存泄露和內存溢出的問題,但實際的工作中,這兩個問題出現的頻率還是蠻高的,尤其是在多線程并發的情況下。如果不了解 Java 虛擬機是如何管理內存的,那么一旦遇到問題可能就會束手無策。
讀到這里,這篇“Java虛擬機中內存區域怎么分配”文章已經介紹完畢,想要掌握這篇文章的知識點還需要大家自己動手實踐使用過才能領會,如果想了解更多相關內容的文章,歡迎關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。