概念 (這是我學習過程中的一些總結) JAVA虛擬機記憶體模型 從屬於線程的記憶體區域 JVM的記憶體劃分中,有部分區域是線程私有的,有部分是屬於整個JVM進程;我們將這部分歸為一類。 1.程式計數器(Program Counter Register) 在JVM規範中,每個線程都有自己的程式計數器。這是一 ...
概念
(這是我學習過程中的一些總結)
JAVA虛擬機記憶體模型
從屬於線程的記憶體區域
JVM的記憶體劃分中,有部分區域是線程私有的,有部分是屬於整個JVM進程;我們將這部分歸為一類。
1.程式計數器(Program Counter Register)
在JVM規範中,每個線程都有自己的程式計數器。這是一塊比較小的記憶體空間,存儲當前線程正在執行的Java方法的JVM指令地址,即位元組碼的行號。 如果正在執行Native方法,則這個計數器為空。
2.Java虛擬機棧(Java Virtal Machine Stack)
同樣也是屬於線程私有區域,每個線程在創建的時候都會創建一個虛擬機棧,生命周期與線程一致,線程退出時,線程的虛擬機棧也回收。虛擬機棧內部保持一個個的棧幀,每次方法調用都會進行壓棧,JVM對棧幀的操作只有出棧和壓棧兩種,方法調用結束時會進行出棧操作。該區域存儲著局部變數表,編譯時期可知的各種基本類型數據、對象引用、方法出口等信息。
3. 本地方法棧(Native Method Stack)
與虛擬機棧類似,本地方法棧是在調用本地方法時使用的棧,每個線程都有一個本地方法棧。
1. 同樣也是屬於線程私有區域,每個線程在創建的時候都會創建一個虛擬機棧,生命周期與線程一致,線程退出時,線程的虛擬機棧也回收。虛擬機棧內部保持一個個的棧幀,每次方法調用都會進行壓棧,JVM對棧幀的操作只有出棧和壓棧兩種,方法調用結束時會進行出棧操作。該區域存儲著局部變數表,編譯時期可知的各種基本類型數據、對象引用、方法出口等信息。
堆(heap)
堆(Heap),幾乎所有創建的Java對象實例,都是被直接分配到堆上的。堆被所有的線程所共用,在堆上的區域,會被垃圾回收器做進一步劃分,例如新生代、老年代的劃分。Java虛擬機在啟動的時候,可以使用“Xmx”之類的參數指定堆區域的大小。
方法區(Method Area)
方法區與堆一樣,也是所有的線程所共用,存儲被虛擬機載入的元(Meta)數據,包括類信息、常量、靜態變數、即時編譯器編譯後的代碼等數據。
方法區是一種java虛擬機的規範。由於方法區存儲的數據和堆中存儲的數據一致,實質上也是堆,因此,在不同的JDK版本中方法區的實現方式不一樣。
運行時常量池(Run-Time Constant Pool)(方法區中)
這是方法區的一部分。常量池主要存放兩大類常量:
- 字面量(Literal),如文本字元串、final常量值。
- 符號引用,存放了與編譯相關的一些常量,因為Java不像C++那樣有連接的過程,因此欄位方法這些符號引用在運行期就需要進行轉換,以便得到真正的記憶體入口地址。
程式執行的記憶體分析過程
為了讓初學者順利的分析記憶體,更加容易的體會程式執行過程中記憶體的變化,加深理解。我們將JAVA虛擬機記憶體模型進行簡化。
在本節課中,Java虛擬機的記憶體可以簡單的分為三個區域:虛擬機棧stack、堆heap、方法區method area。
虛擬機棧(簡稱:棧)的特點如下:
- 棧描述的是方法執行的記憶體模型。每個方法被調用都會創建一個棧幀(存儲局部變數、操作數、方法出口等)
- JVM為每個線程創建一個棧,用於存放該線程執行方法的信息(實際參數、局部變數等)
- 棧屬於線程私有,不能實現線程間的共用!
- 棧的存儲特性是“先進後出,後進先出”
- 棧是由系統自動分配,速度快!棧是一個連續的記憶體空間!
堆的特點如下:
- 堆用於存儲創建好的對象和數組(數組也是對象)
- JVM只有一個堆,被所有線程共用
- 堆是一個不連續的記憶體空間,分配靈活,速度慢!
方法區(又叫靜態區,也是堆)特點如下:
- 方法區是JAVA虛擬機規範,可以有不同的實現。
- JD7以前是“永久代”
- JDK7部分去除“永久代”,靜態變數、字元串常量池都挪到了堆記憶體中
- JDK8是“元數據空間”和堆結合起來。
- JVM只有一個方法區,被所有線程共用!
- 方法區實際也是堆,只是用於存儲類、常量相關的信息!
- 用來存放程式中永遠是不變或唯一的內容。(類信息、靜態變數、字元串常量等)
代碼
(代碼我就不自己寫了,這是我學習時候參考過的,底部附帶原文鏈接)
package com.journaldev.test; public class Memory { public static void main(String[] args) { // Line 1 int i=1; // Line 2 Object obj = new Object(); // Line 3 Memory mem = new Memory(); // Line 4 mem.foo(obj); // Line 5 } // Line 9 private void foo(Object param) { // Line 6 String str = param.toString(); //// Line 7 System.out.println(str); } // Line 8 }
下邊的圖片展示了上邊程式堆和棧記憶體的引用,並且是怎麼用來存儲原始值、對象和變數的引用。
我們來看看程式執行的過程:
1、只要我們一運行這個程式,它會載入所有的運行類到堆記憶體中去,當在第一行找到main()方法的時候,Java創建可以被main()方法線程使用的棧記憶體。
2、當在第一行,我們創建了本地原始變數,它在main()的棧中創建和保存。
3、因為我們在第三行創建了對象,它在堆記憶體中被創建,在棧記憶體中保存了它的引用,同樣的過程也發生在第四行我們創建Memory對象的時候。
4、當在第五行我們調用foo()方法的時候,在堆的頂部創建了一個塊來被foo()方法使用,因為Java是值傳遞的,在第六行一個新的對象的引用在foo()方法中的棧中被創建
5、在第七行一個String被創建,它在堆空間中的String池中運行,並且它的引用也在foo()方法的棧空間中被創建
6、foo()方法在第八行結束,此時在堆中為foo()方法分配的記憶體塊可以被釋放
7、在第九行,main()方法結束,棧為main()方法創建的記憶體空間可以被銷毀。同樣程式也在行結束,Java釋放了所有的記憶體,結束了程式的運行
堆記憶體和棧記憶體的區別
基於上邊的解釋我們可以很簡單的總結出堆和棧的區別:
1、應用程式所有的部分都使用堆記憶體,然後棧記憶體通過一個線程運行來使用。
2、不論對象什麼時候創建,他都會存儲在堆記憶體中,棧記憶體包含它的引用。棧記憶體只包含原始值變數好和堆中對象變數的引用。
3、存儲在堆中的對象是全局可以被訪問的,然而棧記憶體不能被其他線程所訪問。
4、棧中的記憶體管理使用LIFO的方式完成,而堆記憶體的管理要更複雜了,因為它是全局被訪問的。堆記憶體被分為,年輕一代,老一代等等,更多的細節請看,這篇文章
5、棧記憶體是生命周期很短的,然而堆記憶體的生命周期從程式的運行開始到運行結束。
6、我們可以使用-Xms和-Xmx JVM選項定義開始的大小和堆記憶體的最大值,我們可以使用-Xss定義棧的大小
7、當棧記憶體滿的時候,Java拋出java.lang.StackOverFlowError異常而堆記憶體滿的時候拋出java.lang.OutOfMemoryError: Java Heap Space錯誤
8、和堆記憶體比,棧記憶體要小的多,因為明確使用了記憶體分配規則(LIFO),和堆記憶體相比棧記憶體非常快。
————————————————
版權聲明:本文為CSDN博主「遇見美好」的原創文章,遵循CC 4.0 by-sa版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/chensi16114/article/details/72867260