Java8記憶體結構圖

虛擬機記憶體與本地記憶體的區別
Java虛擬機在執行的時候會把管理的記憶體分配成不同的區域,這些區域被稱為虛擬機記憶體,同時,對于虛擬機沒有直接管理的物理記憶體,也有一定的利用,這些被利用卻不在虛擬機記憶體資料區的記憶體,我們稱它為本地記憶體,這兩種記憶體有一定的區別:
JVM記憶體
- 受虛擬機記憶體大小的引數控制,當大小超過引數設定的大小時就會報OOM
本地記憶體
- 本地記憶體不受虛擬機記憶體引數的限制,只受物理記憶體容量的限制
- 雖然不受引數的限制,但是如果記憶體的占用超出物理記憶體的大小,同樣也會報OOM
java運行時資料區域
java虛擬機在執行程序中會將所管理的記憶體劃分為不同的區域,有的隨著執行緒產生和消失,有的隨著java行程產生和消失,根據《Java虛擬機規范》的規定,運行時資料區分為以下一個區域:
程式計數器(Program Counter Register)
程式計數器就是當前執行緒所執行的位元組碼的行號指示器,通過改變計數器的值,來選取下一行指令,通過他來實作跳轉、回圈、恢復執行緒等功能,
- 在任何時刻,一個處理器內核只能運行一個執行緒,多執行緒是通過執行緒輪流切換,分配時間來完成的,這就需要有一個標志來記住每個執行緒執行到了哪里,這里便需要到了程式計數器,
- 所以,程式計數器是執行緒私有的,每個執行緒都已自己的程式計數器,
虛擬機堆疊(JVM Stacks)

虛擬機堆疊是執行緒私有的,隨執行緒生滅,虛擬機堆疊描述的是執行緒中的方法的記憶體模型:
每個方法被執行的時候,都會在虛擬機堆疊中同步創建一個堆疊幀(stack frame),
每個堆疊幀的包含如下的內容
-
區域變數表
-
- 區域變數表中存盤著方法里的java基本資料型別(byte/boolean/char/int/long/double/float/short)以及物件的參考(注:這里的基本資料型別指的是方法內的區域變數)
-
運算元堆疊
-
動態連接
-
方法回傳地址
方法被執行時入堆疊,執行完后出堆疊
虛擬機堆疊可能會拋出兩種例外:
- 如果執行緒請求的堆疊深度大于虛擬機所規定的堆疊深度,則會拋出StackOverFlowError即堆疊溢位
- 如果虛擬機的堆疊容量可以動態擴展,那么當虛擬機堆疊申請不到記憶體時會拋出OutOfMemoryError即OOM記憶體溢位
本地方法堆疊(Native Method Stacks)
本地方法堆疊與虛擬機堆疊的作用是相似的,都會拋出OutOfMemoryError和StackOverFlowError,都是執行緒私有的,主要的區別在于:
- 虛擬機堆疊執行的是java方法
- 本地方法堆疊執行的是native方法(什么是Native方法?)
Java堆(Java Heap)
java堆是JVM記憶體中最大的一塊,由所有執行緒共享,是由垃圾收集器管理的記憶體區域,主要存放物件實體,當然由于java虛擬機的發展,堆中也多了許多東西,現在主要有:
-
物件實體
-
- 類初始化生成的物件
- 基本資料型別的陣列也是物件實體
-
字串常量池
-
- 字串常量池原本存放于方法區,jdk7開始放置于堆中,
- 字串常量池存盤的是string物件的直接參考,而不是直接存放的物件,是一張string table
-
靜態變數
-
- 靜態變數是有static修飾的變數,jdk7時從方法區遷移至堆中
-
執行緒分配緩沖區(Thread Local Allocation Buffer)
-
- 執行緒私有,但是不影響java堆的共性
- 增加執行緒分配緩沖區是為了提升物件分配時的效率
java堆既可以是固定大小的,也可以是可擴展的(通過引數-Xmx和-Xms設定),如果堆無法擴展或者無法分配記憶體時也會報OOM,
方法區(Method Area)
方法區絕對是網上所有關于java記憶體結構文章爭論的焦點,因為方法區的實作在java8做了一次大革新,現在我們來討論一下:
方法區是所有執行緒共享的記憶體,在java8以前是放在JVM記憶體中的,由永久代實作,受JVM記憶體大小引數的限制,在java8中移除了永久代的內容,方法區由元空間(Meta Space)實作,并直接放到了本地記憶體中,不受JVM引數的限制(當然,如果物理記憶體被占滿了,方法區也會報OOM),并且將原來放在方法區的字串常量池和靜態變數都轉移到了Java堆中,方法區與其他區域不同的地方在于,方法區在編譯期間和類加載完成后的內容有少許不同,不過總的來說分為這兩部分:
類元資訊(Klass)
- 類元資訊在類編譯期間放入方法區,里面放置了類的基本資訊,包括類的版本、欄位、方法、介面以及常量池表(Constant Pool Table)
- 常量池表(Constant Pool Table)存盤了類在編譯期間生成的字面量、符號參考(什么是字面量?什么是符號參考?),這些資訊在類加載完后會被決議到運行時常量池中
運行時常量池(Runtime Constant Pool)
- 運行時常量池主要存放在類加載后被決議的字面量與符號參考,但不止這些
- 運行時常量池具備動態性,可以添加資料,比較多的使用就是String類的intern()方法
直接記憶體
直接記憶體位于本地記憶體,不屬于JVM記憶體,但是也會在物理記憶體耗盡的時候報OOM,所以也講一下,
在jdk1.4中加入了NIO(New Input/Putput)類,引入了一種基于通道(channel)與緩沖區(buffer)的新IO方式,它可以使用native函式直接分配堆外記憶體,然后通過存盤在java堆中的DirectByteBuffer物件作為這塊記憶體的參考進行操作,這樣可以在一些場景下大大提高IO性能,避免了在java堆和native堆來回復制資料,
常見問題
什么是Native方法?
由于java是一門高級語言,離硬體底層比較遠,有時候無法操作底層的資源,于是,java添加了native關鍵字,被native關鍵字修飾的方法可以用其他語言重寫,這樣,我們就可以寫一個本地方法,然后用C語言重寫,這樣來操作底層資源,當然,使用了native方法會導致系統的可移植性不高,這是需要注意的,
成員變數、區域變數、類變數分別存盤在記憶體的什么地方?
類變數
- 類變數是用static修飾符修飾,定義在方法外的變數,隨著java行程產生和銷毀
- 在java8之前把靜態變數存放于方法區,在java8時存放在堆中
成員變數
- 成員變數是定義在類中,但是沒有static修飾符修飾的變數,隨著類的實體產生和銷毀,是類實體的一部分
- 由于是實體的一部分,在類初始化的時候,從運行時常量池取出直接參考或者值,與初始化的物件一起放入堆中
區域變數
- 區域變數是定義在類的方法中的變數
- 在所在方法被呼叫時放入虛擬機堆疊的堆疊幀中,方法執行結束后從虛擬機堆疊中彈出,所以存放在虛擬機堆疊中
由final修飾的常量存放在哪里?
final關鍵字并不影響在記憶體中的位置,具體位置請參考上一問題,
類常量池、運行時常量池、字串常量池有什么關系?有什么區別?
類常量池與運行時常量池都存盤在方法區,而字串常量池在jdk7時就已經從方法區遷移到了java堆中,
在類編譯程序中,會把類元資訊放到方法區,類元資訊的其中一部分便是類常量池,主要存放字面量和符號參考,而字面量的一部分便是文本字符,在類加載時將字面量和符號參考決議為直接參考存盤在運行時常量池;
對于文本字符來說,它們會在決議時查找字串常量池,查出這個文本字符對應的字串物件的直接參考,將直接參考存盤在運行時常量池;字串常量池存盤的是字串物件的參考,而不是字串本身,
什么是字面量?什么是符號參考?
字面量
java代碼在編譯程序中是無法構建參考的,字面量就是在編譯時對于資料的一種表示:
int a=1;//這個1便是字面量
String b="iloveu";//iloveu便是字面量
符號參考
由于在編譯程序中并不知道每個類的地址,因為可能這個類還沒有加載,所以如果你在一個類中參考了另一個類,那么你完全無法知道他的記憶體地址,那怎么辦,我們只能用他的類名作為符號參考,在類加載完后用這個符號參考去獲取他的記憶體地址,
例子:我在com.demo.Solution類中參考了com.test.Quest,那么我會把com.test.Quest作為符號參考存到類常量池,等類加載完后,拿著這個參考去方法區找這個類的記憶體地址,
原文鏈接:https://blog.csdn.net/qq_35621494/article/details/107351237
著作權宣告:本文為CSDN博主「lei6393」的原創文章,遵循CC 4.0 BY-SA著作權協議,轉載請附上原文出處鏈接及本宣告,
近期熱文推薦:
1.1,000+ 道 Java面試題及答案整理(2021最新版)
2.別在再滿屏的 if/ else 了,試試策略模式,真香!!
3.臥槽!Java 中的 xx ≠ null 是什么新語法?
4.Spring Boot 2.5 重磅發布,黑暗模式太炸了!
5.《Java開發手冊(嵩山版)》最新發布,速速下載!
覺得不錯,別忘了隨手點贊+轉發哦!
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/294369.html
標籤:其他
