您好,登錄后才能下訂單哦!
JVM中有哪些內存區(qū)域?針對這個問題,這篇文章詳細介紹了相對應的分析和解答,希望可以幫助更多想解決這個問題的小伙伴找到更簡單易行的方法。
什么是jvm內存區(qū)域劃分?
其實這個問題非常簡單,JVM在運行我們寫好的代碼時,他是必須使用多塊內存空間的,不同的內存空間用來放不同的數(shù)據,然后配合我們寫的代碼流程,才能讓我們的系統(tǒng)運行起來。
舉個最簡單的例子,比如咱們現(xiàn)在知道了JVM會加載類到內存里來供后續(xù)運行,那么我問問大家,這些類加載到內存以后,放到哪兒去了呢?想過這個問題嗎?
所以JVM里就必須有一塊內存區(qū)域,用來存放我們寫的那些類。
包括我們定義的成員變量,類變量,方法,局部變量等等,都在jvm內存中對應著一塊內存來記錄存儲。
存放類的方法區(qū)
在JDK1.8之前的版本里,代表JVM的一塊區(qū)域。在1.8版本以后,這塊區(qū)域的名字改了,叫做“Matespace”,可以認為是“元數(shù)據空間”這樣的意思,當然這里主要存放的還是我們自己寫的各種類的相關信息。
舉個栗子。有如下兩個類,People類沒有成員變量,而Student類有一個name的類變量。
public class Student{ private static String name = "lisi"; } public class People{ public static void main(){ Student student = new Student(); } }
這兩個類被加載到JVM,就會存放在這個方法區(qū)里面(注意:如果讀過我之前的章節(jié),就會明白這里的加載代表的是:加載->驗證->準備->解析->初始化,類的所有類變量都會被賦值)。
執(zhí)行代碼指令的程序計數(shù)器
我們知道,被加載到jvm的類對象是我們寫的.java文件被編譯之后的.class文件。
在編譯過后會將我們的代碼編譯成計算機能讀懂的字節(jié)碼。而這個.calss文件就是,就是我們代碼編譯好的字節(jié)碼了。
加載到內存以后,字節(jié)碼執(zhí)行引擎就開始工作了。去執(zhí)行我們編譯出來的代碼指令,如下圖
此時問題來了,我們是不是需要一塊內存空間來記錄我們字節(jié)碼執(zhí)行引擎目前執(zhí)行到了哪行代碼?這一塊特殊的內存區(qū)域就是“程序計數(shù)器”
這個程序計數(shù)器就是用來記錄當前執(zhí)行的字節(jié)碼指令的位置。
如下圖:
到這里我相信會有人產生疑惑,就按照當前的代碼順序執(zhí)行就行了,為什么要記錄執(zhí)行到哪里了?
因為我們寫好的代碼可能會開啟多個線程并發(fā)的執(zhí)行不同的代碼。可能當前線程這段代碼還沒有執(zhí)行完畢,就上下文切換到另一段代碼中。
當線程再次上下文切換到之前的代碼時,就需要一個專門記錄當前線程執(zhí)行到了哪一條字節(jié)碼。所以,每一個線程都有這自己的程序計數(shù)器。
如下圖:
java虛擬機棧
java代碼在執(zhí)行的時候,一定是某個線程來執(zhí)行某個方法中的代碼。
當線程執(zhí)行到某個方法的時候,如果這個方法有局部變量,那么就需要一塊區(qū)域來存放局部變量的數(shù)據信息。這個區(qū)域就叫做java虛擬機棧。
每一個線程都有一個自己的java虛擬機棧,比如說當執(zhí)行main方法的時候就會有一個main線程,用來存放main方法中定義的局部變量
public static void main(){ People people = new People(); int i = 9; }
比如上面的main()方法中,其實就有一個"people"的局部變量,他是引用一個People的實例對象的,這個對象我們先不管他。然后有一個"i"的局部變量。
如下圖:
我想大家應該都知道棧的數(shù)據結構,后進先出。當方法執(zhí)行完畢以后,這個棧楨就會出棧,里面的局部變量信息就會從內存刪除。所以局部變量是線程安全的。因為只有當前線程能獲取到這個值。
為什么要用后進先出的數(shù)據結構?
假設a方法當中同步調用b方法,此時a方法的棧楨先入棧,然后再是b方法的棧楨入棧。b方法執(zhí)行完畢后,b方法的棧楨出棧,繼續(xù)執(zhí)行a方法。所以使用一個后進先出的棧結構是非常完美的。
此時jvm的內存模型圖如下:
java堆內存
這一塊內存是非常非常重要的。
我們實例化的所有對象都是存放在這個內存中。這個實例化的對象里面會包含一些數(shù)據,我們用上面的代碼來做栗子。
public class Student{ private String name = "lisi"; public String getNmae(){ return name; } } public class People{ public static void main(){ Student student = new Student(); student.getName(); } }
還是這個代碼,當main線程執(zhí)行main()方法的時候,首先在堆內存中實例化Student對象,然后在局部變量中創(chuàng)建student,student存的是實例化Student對象的內存地址。然后執(zhí)行Student對象的getName()方法。
如下圖:
由上圖可以看出來,??臻g是封閉的,是線程安全的,而堆內存中是我們主要發(fā)生線程不安全的地方,因為堆內存的空間所有的線程其實都是能共享的。
此時jvm的內存劃分的最終模型為:
其他內存區(qū)域
很多java程序猿對這一塊區(qū)域的接觸是非常少的。
其實在JDK的很多底層代碼API中,比如NIO。
如果你去看源碼會發(fā)現(xiàn)很多地方的代碼不是java寫的,而是走的native方法去調用本地操作系統(tǒng)里面的一些方法,可能調用的都是c語言寫的方法。
比如說:public native int hashCode();
在調用這種native方法的時候,就會有線程對應的本地方法棧,這個其實類似于java虛擬機棧。也是存放各種native方法的局部變量表之類的信息。
還有一塊區(qū)域,是不是jvm的,通過NIO中的allocateDirect這種API,可以在jva堆外分配內存空間,然后通過java虛擬機棧里的DirectByteBuffer來引用和操作堆外內存空間。
關于JVM中有哪些內存區(qū)域問題的解答就分享到這里了,希望以上內容可以對大家有一定的幫助,如果你還有很多疑惑沒有解開,可以關注億速云行業(yè)資訊頻道了解更多相關知識。
免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。