最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會員登陸 & 注冊

004、大廠面試題:JVM中有哪些內(nèi)存區(qū)域,分別都是用來干嘛的?

2023-05-31 23:02 作者:儒猿課堂  | 我要投稿

大廠面試題

JVM中有哪些內(nèi)存區(qū)域,分別是用來干嘛的?


目錄:

  1. 前文回顧

  2. 大廠面試背景引入

  3. 到底什么是JVM的內(nèi)存區(qū)域劃分?

  4. 存放類的方法區(qū)

  5. 執(zhí)行代碼指令用的程序計數(shù)器

  6. Java虛擬機棧

  7. Java堆內(nèi)存

  8. 核心內(nèi)存區(qū)域的全流程串講

  9. 其他內(nèi)存區(qū)域

  10. 本文小結(jié)

  11. 昨日思考題解答

  12. 今日思考題


1、前文回顧

上一篇文章我們聊了一下JVM類加載這塊的機制,先簡單回顧一下。

大家需要搞明白的是,在什么情況下會觸發(fā)類的加載?加載之后的驗證、準(zhǔn)備和解析分別是干什么的?

尤為重要的是準(zhǔn)備階段和初始化階段,是如何為類分配內(nèi)存空間的?然后類加載器的規(guī)則是什么?

來看一下上篇文章的圖,簡單回顧一下。


2、大廠面試背景引入

很多人想要到阿里、美團、京東等互聯(lián)網(wǎng)大公司去面試,但是現(xiàn)在互聯(lián)網(wǎng)大廠面試一般都必定會考核JVM相關(guān)的知識積累

所以在了解完了JVM的類加載機制之后,先一起來看看JVM的內(nèi)存區(qū)域劃分,這個基本上是互聯(lián)網(wǎng)公司面試必問。


3、到底什么是JVM的內(nèi)存區(qū)域劃分?

其實這個問題非常簡單,JVM在運行我們寫好的代碼時,他是必須使用多塊內(nèi)存空間的,不同的內(nèi)存空間用來放不同的數(shù)據(jù),然后配合我們寫的代碼流程,才能讓我們的系統(tǒng)運行起來。

舉個最簡單的例子,比如咱們現(xiàn)在知道了JVM會加載類到內(nèi)存里來供后續(xù)運行,那么我問問大家,這些類加載到內(nèi)存以后,放到哪兒去了呢?想過這個問題嗎?

所以JVM里就必須有一塊內(nèi)存區(qū)域,用來存放我們寫的那些類。

我們來看下面的圖:

繼續(xù)來看,我們的代碼運行起來時,是不是需要執(zhí)行我們寫的一個一個的方法?

那么運行方法的時候,方法里面有很多變量之類的東西,是不是需要放在某個內(nèi)存區(qū)域里?

接著如果我們寫的代碼里創(chuàng)建一些對象,這些對象是不是也需要內(nèi)存空間來存放?

同樣的,大家看下圖:

這就是為什么JVM中必須劃分出來不同的內(nèi)存區(qū)域,它是為了我們寫好的代碼在運行過程中根據(jù)需要來使用的。

接下來,我們就依次看看JVM中有哪些內(nèi)存區(qū)域。


4、存放類的方法區(qū)

這個方法區(qū)是在JDK 1.8以前的版本里,代表JVM中的一塊區(qū)域。

主要是放從“.class”文件里加載進來的類,還會有一些類似常量池的東西放在這個區(qū)域里。

但是在JDK 1.8以后,這塊區(qū)域的名字改了,叫做“Metaspace”,可以認(rèn)為是“元數(shù)據(jù)空間”這樣的意思。當(dāng)然這里主要還是存放我們自己寫的各種類相關(guān)的信息。

舉個例子,還是跟我們之前說的那樣,假設(shè)我們有一個“Kafka.class”類和“ReplicaManager.class”類,類似下面的代碼。

這兩個類加載到JVM后,就會放在這個方法區(qū)中,大家看下圖:



5、執(zhí)行代碼指令用的程序計數(shù)器

繼續(xù)假設(shè)我們的代碼是如下所示:


之前給大家講過,實際上上面那段代碼首先會存在于“.java”后綴的文件里,這個文件就是java源代碼文件。

但是這個文件是面向我們程序員的,計算機他是看不懂你寫的這段代碼的。

所以此時就得通過編譯器,把“.java”后綴的源代碼文件編譯為“.class”后綴的字節(jié)碼文件。

這個“.class”后綴的字節(jié)碼文件里,存放的就是對你寫出來的代碼編譯好的字節(jié)碼了。

字節(jié)碼才是計算器可以理解的一種語言,而不是我們寫出來的那一堆代碼。

字節(jié)碼看起來大概是下面這樣的,跟上面的代碼無關(guān),就是一個示例而已,給大家感受一下。


public?java.lang.String?getName();
?????descriptor:?()Ljava/lang/String;
?????flags:?ACC_PUBLIC
?????Code:
?????????stack=1,?locals=1,?args_size=1
?????????????0:?aload_0
?????????????1:?get_field????#2
?????????????4:?areturn


這段字節(jié)碼就是讓大家知道“.java”翻譯成的“.class”是大概什么樣子的。

比如“0: aload_0”這樣的,就是“字節(jié)碼指令”,他對應(yīng)了一條一條的機器指令,計算機只有讀到這種機器碼指令,才知道具體應(yīng)該要干什么。

比如字節(jié)碼指令可能會讓計算機從內(nèi)存里讀取某個數(shù)據(jù),或者把某個數(shù)據(jù)寫入到內(nèi)存里去,都有可能,各種各樣的指令就會指示計算機去干各種各樣的事情。

所以現(xiàn)在大家首先明白一點:我們寫好的Java代碼會被翻譯成字節(jié)碼,對應(yīng)各種字節(jié)碼指令

現(xiàn)在Java代碼通過JVM跑起來的第一件事情就明確了, 首先Java代碼被編譯成字節(jié)碼指令,然后字節(jié)碼指令一定會被一條一條執(zhí)行,這樣才能實現(xiàn)我們寫好的代碼執(zhí)行的效果。

所以當(dāng)JVM加載類信息到內(nèi)存之后,實際就會使用自己的字節(jié)碼執(zhí)行引擎,去執(zhí)行我們寫的代碼編譯出來的代碼指令,如下圖。

那么在執(zhí)行字節(jié)碼指令的時候,JVM里就需要一個特殊的內(nèi)存區(qū)域了,那就是“程序計數(shù)器”

這個程序計數(shù)器就是用來記錄當(dāng)前執(zhí)行的字節(jié)碼指令的位置的,也就是記錄目前執(zhí)行到了哪一條字節(jié)碼指令。

我們通過一張圖來說明:

大家都知道JVM是支持多個線程的,所以其實你寫好的代碼可能會開啟多個線程并發(fā)執(zhí)行不同的代碼,所以就會有多個線程來并發(fā)的執(zhí)行不同的代碼指令

因此每個線程都會有自己的一個程序計數(shù)器,專門記錄當(dāng)前這個線程目前執(zhí)行到了哪一條字節(jié)碼指令了

下圖更加清晰的展示出了他們之間的關(guān)系。

6、Java虛擬機棧

Java代碼在執(zhí)行的時候,一定是線程來執(zhí)行某個方法中的代碼

哪怕就是下面的代碼,也會有一個main線程來執(zhí)行main()方法里的代碼

在main線程執(zhí)行main()方法的代碼指令的時候,就會通過main線程對應(yīng)的程序計數(shù)器記錄自己執(zhí)行的指令位置。

但是在方法里,我們經(jīng)常會定義一些方法內(nèi)的局部變量

比如在上面的main()方法里,其實就有一個“replicaManager”局部變量,他是引用一個ReplicaManager實例對象的,關(guān)于這個對象我們先別去管他,先來看方法和局部變量。

因此,JVM必須有一塊區(qū)域是來保存每個方法內(nèi)的局部變量等數(shù)據(jù)的,這個區(qū)域就是Java虛擬機棧

每個線程都有自己的Java虛擬機棧,比如這里的main線程就會有自己的一個Java虛擬機棧,用來存放自己執(zhí)行的那些方法的局部變量。

如果線程執(zhí)行了一個方法,就會對這個方法調(diào)用創(chuàng)建對應(yīng)的一個棧幀

棧幀里就有這個方法的局部變量表 、操作數(shù)棧、動態(tài)鏈接、方法出口等東西,這里大家先不用全都理解,我們先關(guān)注局部變量。

比如main線程執(zhí)行了main()方法,那么就會給這個main()方法創(chuàng)建一個棧幀,壓入main線程的Java虛擬機棧

同時在main()方法的棧幀里,會存放對應(yīng)的“replicaManager”局部變量

上述過程,如下圖所示:

然后假設(shè)main線程繼續(xù)執(zhí)行ReplicaManager對象里的方法,比如下面這樣,就在“loadReplicasFromDisk”方法里定義了一個局部變量:“hasFinishedLoad”


那么main線程在執(zhí)行上面的“l(fā)oadReplicasFromDisk”方法時,就會為“l(fā)oadReplicasFromDisk”方法創(chuàng)建一個棧幀壓入線程自己的Java虛擬機棧里面去。

然后在棧幀的局部變量表里就會有“hasFinishedLoad”這個局部變量。

整個過程如下圖所示:


接著如果“l(fā)oadReplicasFromDisk”方法調(diào)用了另外一個“isLocalDataCorrupt()”方法 ,這個方法里也有自己的局部變量

比如下面這樣的代碼:


那么這個時候會給“isLocalDataCorrupt”方法又創(chuàng)建一個棧幀,壓入線程的Java虛擬機棧里。

而且“isLocalDataCorrupt”方法的棧幀的局部變量表里會有一個“isCorrupt”變量,這是“isLocalDataCorrupt”方法的局部變量

整個過程,如下圖所示:

接著如果“isLocalDataCorrupt”方法執(zhí)行完畢了,就會把“isLocalDataCorrupt”方法對應(yīng)的棧幀從Java虛擬機棧里給出棧

然后如果“l(fā)oadReplicasFromDisk”方法也執(zhí)行完畢了,就會把“l(fā)oadReplicasFromDisk”方法也從Java虛擬機棧里出棧。

上述就是JVM中的“Java虛擬機?!边@個組件的作用:調(diào)用執(zhí)行任何方法時,都會給方法創(chuàng)建棧幀然后入棧

在棧幀里存放了這個方法對應(yīng)的局部變量之類的數(shù)據(jù),包括這個方法執(zhí)行的其他相關(guān)的信息,方法執(zhí)行完畢之后就出棧。

咱們再來看一個圖,了解一下每個線程在執(zhí)行代碼時,除了程序計數(shù)器以外,還搭配了一個Java虛擬機棧內(nèi)存區(qū)域來存放每個方法中的局部變量表。


7、Java堆內(nèi)存

現(xiàn)在大家都知道了,main線程執(zhí)行main()方法的時候,會有自己的程序計數(shù)器。

此外,還會依次把main()方法,loadReplicasFromDisk()方法,isLocalDataCorrupt()方法的棧幀壓入Java虛擬機棧,存放每個方法的局部變量。

那么接著我們就得來看JVM中的另外一個非常關(guān)鍵的區(qū)域,就是Java堆內(nèi)存,這里就是存放我們在代碼中創(chuàng)建的各種對象的

比如下面的代碼:


上面的“new ReplicaManager()”這個代碼就是創(chuàng)建了一個ReplicaManager類的對象實例,這個對象實例里面會包含一些數(shù)據(jù),如下面的代碼所示。

這個“ReplicaManager”類里的“replicaCount”就是屬于這個對象實例的一個數(shù)據(jù)。

類似ReplicaManager這樣的對象實例,就會存放在Java堆內(nèi)存里。


Java堆內(nèi)存區(qū)域里會放入類似ReplicaManager的對象,然后我們因為在main方法里創(chuàng)建了ReplicaManager對象的,那么在線程執(zhí)行main方法代碼的時候,就會在main方法對應(yīng)的棧幀的局部變量表里,讓一個引用類型的“replicaManager”局部變量來存放ReplicaManager對象的地址

相當(dāng)于你可以認(rèn)為局部變量表里的“replicaManager”指向了Java堆內(nèi)存里的ReplicaManager對象

還是給大家來一張圖,更加清晰一些:


8、核心內(nèi)存區(qū)域的全流程串講

其實我們把上面的那個圖和下面的這個總的大圖一起串起來看看,還有配合整體的代碼,我們來捋一下整體的流程,大家就會覺得很清晰。



首先,你的JVM進程會啟動,就會先加載你的Kafka類到內(nèi)存里。然后有一個main線程,開始執(zhí)行你的Kafka中的main()方法。

main線程是關(guān)聯(lián)了一個程序計數(shù)器的,那么他執(zhí)行到哪一行指令,就會記錄在這里

大家結(jié)合上圖中的程序計數(shù)器來理解一下。

其次,就是main線程在執(zhí)行main()方法的時候,會在main線程關(guān)聯(lián)的Java虛擬機棧里,壓入一個main()方法的棧幀。

接著會發(fā)現(xiàn)需要創(chuàng)建一個ReplicaManager類的實例對象,此時會加載ReplicaManager類到內(nèi)存里來。

然后會創(chuàng)建一個ReplicaManager的對象實例分配在Java堆內(nèi)存里,并且在main()方法的棧幀里的局部變量表引入一個“replicaManager”變量,讓他引用ReplicaManager對象在Java堆內(nèi)存中的地址。

看到這里,大家結(jié)合上面的兩個圖理解一下。

接著,main線程開始執(zhí)行ReplicaManager對象中的方法,會依次把自己執(zhí)行到的方法對應(yīng)的棧幀壓入自己的Java虛擬機棧

執(zhí)行完方法之后再把方法對應(yīng)的棧幀從Java虛擬機棧里出棧。

其實大家理解了這個過程,那么JVM中的各個核心內(nèi)存區(qū)域的功能和對應(yīng)的我們的Java代碼之間的關(guān)系,就徹底理解了


9、其他內(nèi)存區(qū)域

其實在JDK很多底層API里,比如IO相關(guān)的,NIO相關(guān)的,網(wǎng)絡(luò)Socket相關(guān)的

如果大家去看他內(nèi)部的源碼,會發(fā)現(xiàn)很多地方都不是Java代碼了,而是走的native方法去調(diào)用本地操作系統(tǒng)里面的一些方法,可能調(diào)用的都是c語言寫的方法,或者一些底層類庫

比如下面這樣的:public native int hashCode();

在調(diào)用這種native方法的時候,就會有線程對應(yīng)的本地方法棧,這個里面也是跟Java虛擬機棧類似的,也是存放各種native方法的局部變量表之類的信息。

還有一個區(qū)域,是不屬于JVM的,通過NIO中的allocateDirect這種API,可以在Java堆外分配內(nèi)存空間。然后,通過Java虛擬機里的DirectByteBuffer來引用和操作堆外內(nèi)存空間。

其實很多技術(shù)都會用這種方式,因為有一些場景下,堆外內(nèi)存分配可以提升性能。


10、本文小結(jié)

本文到這里就結(jié)束了,基本上把JVM里的核心內(nèi)存區(qū)域的功能解釋清楚了

大家需要重點去關(guān)注方法區(qū)、程序計數(shù)器、Java虛擬機棧和Java堆這些內(nèi)存區(qū)域的作用,和我們配套的圖以及代碼結(jié)合起來去理解。


11、昨日思考題解答

昨天讓大家去思考一下:Tomcat這種Web容器中的類加載器應(yīng)該如何設(shè)計實現(xiàn)?

這里給大家一個簡單的思路給回復(fù)。

首先Tomcat的 類加載器體系如下圖所示,他是自定義了很多類加載器的。

Tomcat自定義了Common、Catalina、Shared等類加載器,其實就是用來加載Tomcat自己的一些核心基礎(chǔ)類庫的。

然后Tomcat為每個部署在里面的Web應(yīng)用都有一個對應(yīng)的WebApp類加載器,負(fù)責(zé)加載我們部署的這個Web應(yīng)用的類

至于Jsp類加載器,則是給每個JSP都準(zhǔn)備了一個Jsp類加載器。

而且大家一定要記得,Tomcat是打破了雙親委派機制的

每個WebApp負(fù)責(zé)加載自己對應(yīng)的那個Web應(yīng)用的class文件,也就是我們寫好的某個系統(tǒng)打包好的war包中的所有class文件,不會傳導(dǎo)給上層類加載器去加載。

如果大家感興趣,可以自己找資料去學(xué)習(xí)研究Tomcat的類加載機制。


12、今日思考題

今天我們學(xué)習(xí)了JVM中的各個內(nèi)存區(qū)域,那么先留給大家一個思考題:

我們在Java堆內(nèi)存中分配的那些對象,到底會占用多少內(nèi)存?一般怎么來計算和估算我們的系統(tǒng)創(chuàng)建的對象對內(nèi)存占用的一個壓力呢?

大家思考一下,明天的文章末尾會給出答案。


End

版權(quán):公眾號儒猿技術(shù)窩

未經(jīng)許可不得傳播,如有侵權(quán)將追究法律責(zé)任

004、大廠面試題:JVM中有哪些內(nèi)存區(qū)域,分別都是用來干嘛的?的評論 (共 條)

分享到微博請遵守國家法律
芦山县| 大厂| 阿尔山市| 微山县| 迁西县| 迁安市| 永宁县| 卓资县| 富蕴县| 北碚区| 龙海市| 天水市| 河东区| 合肥市| 花垣县| 松滋市| 灯塔市| 龙山县| 襄垣县| 麻江县| 理塘县| 嘉义县| 澜沧| 楚雄市| 灵山县| 青龙| 西平县| 东港市| 荆州市| 于都县| 永寿县| 全州县| 怀安县| 浦城县| 光泽县| 海兴县| 宜丰县| 武平县| 沙河市| 平谷区| 普兰店市|