JVM中有哪些內(nèi)存區(qū)域及其作用
前言
之前我們探討過(guò)一個(gè).class文件是如何被加載到j(luò)vm中的。但是jvm內(nèi)又是如何劃分內(nèi)存的呢?這個(gè)內(nèi)被加載到了那一塊內(nèi)存中?jvm內(nèi)存劃分也是面試當(dāng)中必被問(wèn)到的一個(gè)面試題。
什么是jvm內(nèi)存區(qū)域劃分?
其實(shí)這個(gè)問(wèn)題非常簡(jiǎn)單,JVM在運(yùn)行我們寫好的代碼時(shí),他是必須使用多塊內(nèi)存空間的,不同的內(nèi)存空間用來(lái)放不同的數(shù)據(jù),然后配合我們寫的代碼流程,才能讓我們的系統(tǒng)運(yùn)行起來(lái)。
舉個(gè)最簡(jiǎn)單的例子,比如咱們現(xiàn)在知道了JVM會(huì)加載類到內(nèi)存里來(lái)供后續(xù)運(yùn)行,那么我問(wèn)問(wèn)大家,這些類加載到內(nèi)存以后,放到哪兒去了呢?想過(guò)這個(gè)問(wèn)題嗎?
所以JVM里就必須有一塊內(nèi)存區(qū)域,用來(lái)存放我們寫的那些類。
包括我們定義的成員變量,類變量,方法,局部變量等等,都在jvm內(nèi)存中對(duì)應(yīng)著一塊內(nèi)存來(lái)記錄存儲(chǔ)。
存放類的方法區(qū)
在JDK1.8之前的版本里,代表JVM的一塊區(qū)域。在1.8版本以后,這塊區(qū)域的名字改了,叫做“Matespace”,可以認(rèn)為是“元數(shù)據(jù)空間”這樣的意思,當(dāng)然這里主要存放的還是我們自己寫的各種類的相關(guān)信息。
舉個(gè)栗子。有如下兩個(gè)類,People類沒(méi)有成員變量,而Student類有一個(gè)name的類變量。
public class Student{ private static String name = "lisi"; } public class People{ public static void main(){ Student student = new Student(); } }
這兩個(gè)類被加載到JVM,就會(huì)存放在這個(gè)方法區(qū)里面(注意:如果讀過(guò)我之前的章節(jié),就會(huì)明白這里的加載代表的是:加載->驗(yàn)證->準(zhǔn)備->解析->初始化,類的所有類變量都會(huì)被賦值)。
執(zhí)行代碼指令的程序計(jì)數(shù)器
我們知道,被加載到j(luò)vm的類對(duì)象是我們寫的.java文件被編譯之后的.class文件。
在編譯過(guò)后會(huì)將我們的代碼編譯成計(jì)算機(jī)能讀懂的字節(jié)碼。而這個(gè).calss文件就是,就是我們代碼編譯好的字節(jié)碼了。
加載到內(nèi)存以后,字節(jié)碼執(zhí)行引擎就開始工作了。去執(zhí)行我們編譯出來(lái)的代碼指令,如下圖
此時(shí)問(wèn)題來(lái)了,我們是不是需要一塊內(nèi)存空間來(lái)記錄我們字節(jié)碼執(zhí)行引擎目前執(zhí)行到了哪行代碼?這一塊特殊的內(nèi)存區(qū)域就是“程序計(jì)數(shù)器”
這個(gè)程序計(jì)數(shù)器就是用來(lái)記錄當(dāng)前執(zhí)行的字節(jié)碼指令的位置。
如下圖:
到這里我相信會(huì)有人產(chǎn)生疑惑,就按照當(dāng)前的代碼順序執(zhí)行就行了,為什么要記錄執(zhí)行到哪里了?
因?yàn)槲覀儗懞玫拇a可能會(huì)開啟多個(gè)線程并發(fā)的執(zhí)行不同的代碼??赡墚?dāng)前線程這段代碼還沒(méi)有執(zhí)行完畢,就上下文切換到另一段代碼中。
當(dāng)線程再次上下文切換到之前的代碼時(shí),就需要一個(gè)專門記錄當(dāng)前線程執(zhí)行到了哪一條字節(jié)碼。所以,每一個(gè)線程都有這自己的程序計(jì)數(shù)器。
如下圖:
java虛擬機(jī)棧
java代碼在執(zhí)行的時(shí)候,一定是某個(gè)線程來(lái)執(zhí)行某個(gè)方法中的代碼。
當(dāng)線程執(zhí)行到某個(gè)方法的時(shí)候,如果這個(gè)方法有局部變量,那么就需要一塊區(qū)域來(lái)存放局部變量的數(shù)據(jù)信息。這個(gè)區(qū)域就叫做java虛擬機(jī)棧。
每一個(gè)線程都有一個(gè)自己的java虛擬機(jī)棧,比如說(shuō)當(dāng)執(zhí)行main方法的時(shí)候就會(huì)有一個(gè)main線程,用來(lái)存放main方法中定義的局部變量
public static void main(){ People people = new People(); int i = 9; }
比如上面的main()方法中,其實(shí)就有一個(gè)"people"的局部變量,他是引用一個(gè)People的實(shí)例對(duì)象的,這個(gè)對(duì)象我們先不管他。然后有一個(gè)"i"的局部變量。
如下圖:
我想大家應(yīng)該都知道棧的數(shù)據(jù)結(jié)構(gòu),后進(jìn)先出。當(dāng)方法執(zhí)行完畢以后,這個(gè)棧楨就會(huì)出棧,里面的局部變量信息就會(huì)從內(nèi)存刪除。所以局部變量是線程安全的。因?yàn)橹挥挟?dāng)前線程能獲取到這個(gè)值。
為什么要用后進(jìn)先出的數(shù)據(jù)結(jié)構(gòu)?
假設(shè)a方法當(dāng)中同步調(diào)用b方法,此時(shí)a方法的棧楨先入棧,然后再是b方法的棧楨入棧。b方法執(zhí)行完畢后,b方法的棧楨出棧,繼續(xù)執(zhí)行a方法。所以使用一個(gè)后進(jìn)先出的棧結(jié)構(gòu)是非常完美的。
此時(shí)jvm的內(nèi)存模型圖如下:
java堆內(nèi)存
這一塊內(nèi)存是非常非常重要的。
我們實(shí)例化的所有對(duì)象都是存放在這個(gè)內(nèi)存中。這個(gè)實(shí)例化的對(duì)象里面會(huì)包含一些數(shù)據(jù),我們用上面的代碼來(lái)做栗子。
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(); } }
還是這個(gè)代碼,當(dāng)main線程執(zhí)行main()方法的時(shí)候,首先在堆內(nèi)存中實(shí)例化Student對(duì)象,然后在局部變量中創(chuàng)建student,student存的是實(shí)例化Student對(duì)象的內(nèi)存地址。然后執(zhí)行Student對(duì)象的getName()方法。
如下圖:
由上圖可以看出來(lái),棧空間是封閉的,是線程安全的,而堆內(nèi)存中是我們主要發(fā)生線程不安全的地方,因?yàn)槎褍?nèi)存的空間所有的線程其實(shí)都是能共享的。
此時(shí)jvm的內(nèi)存劃分的最終模型為:
其他內(nèi)存區(qū)域
很多java程序猿對(duì)這一塊區(qū)域的接觸是非常少的。
其實(shí)在JDK的很多底層代碼API中,比如NIO。
如果你去看源碼會(huì)發(fā)現(xiàn)很多地方的代碼不是java寫的,而是走的native方法去調(diào)用本地操作系統(tǒng)里面的一些方法,可能調(diào)用的都是c語(yǔ)言寫的方法。
比如說(shuō):public native int hashCode();
在調(diào)用這種native方法的時(shí)候,就會(huì)有線程對(duì)應(yīng)的本地方法棧,這個(gè)其實(shí)類似于java虛擬機(jī)棧。也是存放各種native方法的局部變量表之類的信息。
還有一塊區(qū)域,是不是jvm的,通過(guò)NIO中的allocateDirect這種API,可以在jva堆外分配內(nèi)存空間,然后通過(guò)java虛擬機(jī)棧里的DirectByteBuffer來(lái)引用和操作堆外內(nèi)存空間。
總結(jié)
基本上jvm的核心內(nèi)存區(qū)域的功能都解釋清楚了,面試能回答到這一個(gè)地步應(yīng)該也能順利通過(guò)了。
我們需要重點(diǎn)關(guān)注的是方法區(qū),程序計(jì)數(shù)器,java虛擬機(jī)棧和java堆內(nèi)存這些內(nèi)存區(qū)域的作用。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Java基礎(chǔ)知識(shí)之ByteArrayOutputStream流的使用
這篇文章主要介紹了Java基礎(chǔ)知識(shí)之ByteArrayOutputStream流的使用,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-12MyBatis-Plus使用ActiveRecord(AR)實(shí)現(xiàn)CRUD
本文將結(jié)合實(shí)例代碼,介紹MyBatis-Plus使用ActiveRecord(AR)實(shí)現(xiàn)CRUD,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-07-07Java實(shí)現(xiàn)圖書管理系統(tǒng)的示例代碼
這篇文章主要為大家詳細(xì)介紹了如何利用java語(yǔ)言實(shí)現(xiàn)簡(jiǎn)單的圖書管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-08-08SpringBoot使用CORS實(shí)現(xiàn)無(wú)縫跨域的方法實(shí)現(xiàn)
CORS 是一種在服務(wù)端設(shè)置響應(yīng)頭部信息的機(jī)制,允許特定的源進(jìn)行跨域訪問(wèn),本文主要介紹了SpringBoot使用CORS實(shí)現(xiàn)無(wú)縫跨域的方法實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的可以了解一下2023-10-10詳解Java中Checked Exception與Runtime Exception 的區(qū)別
這篇文章主要介紹了詳解Java中Checked Exception與Runtime Exception 的區(qū)別的相關(guān)資料,這里提供實(shí)例幫助大家學(xué)習(xí)理解這部分內(nèi)容,需要的朋友可以參考下2017-08-08多模字符串匹配算法原理及Java實(shí)現(xiàn)代碼
這篇文章主要介紹了多模字符串匹配算法原理及Java實(shí)現(xiàn)代碼,涉及算法背景,原理,構(gòu)建過(guò)程簡(jiǎn)單介紹幾Java代碼實(shí)現(xiàn)等相關(guān)內(nèi)容,具有一定參考價(jià)值,需要的朋友可以了解下。2017-11-11MyBatis如何調(diào)用存儲(chǔ)過(guò)程
這篇文章主要介紹了MyBatis如何調(diào)用存儲(chǔ)過(guò)程問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-05-05優(yōu)化Java內(nèi)存管理來(lái)防止“GC”錯(cuò)誤的方法詳解
垃圾回收(GC)是 Java 中的一個(gè)重要機(jī)制,它可以管理內(nèi)存并回收不再使用的對(duì)象所占用的資源,在本文中,我們將探討一些技巧,幫助您避免這一錯(cuò)誤,確保您的 Java 應(yīng)用程序順利運(yùn)行,需要的朋友可以參考下2023-11-11