您好,登錄后才能下訂單哦!
很多人想要到阿里巴巴、美團、京東等互聯網大公司去面試,但是現在互聯網大廠面試一般都必定會考核JVM相關的知識積累和實踐經驗,畢竟線上系統寫好代碼部署之后,每個工程師都必須關注JVM相關的東西,比如OOM、GC等問題.
所以一起來看看JVM的最基本的區域劃分以及工作原理,這個基本上是互聯網公司面試必問。
區域劃分
jvm的區域劃分如下所示:
大致就是分為:程序計數器,虛擬機棧,堆,方法區,本地方法棧,這幾個部分。
接下來我們從自己寫好的Java代碼如何通過JVM來運行的角度,來分析一下JVM里這些區域是如何支撐我們的Java代碼跑起來的。
程序計數器
假設我們有如下的一個類,就是最最基本的一個HelloWorld而已:
public class HelloWorld {
? ? ? ? public static void main(String[] args) {
? ? ? ? ? ??System.out.println("Hello World");
? ? ? ? }
}
上面那段代碼首先會存在于 “.java” 后綴的文件里,這個文件就是java源代碼文件,但是這個文件是面向我們程序員的,計算機他是看不懂你寫的這段代碼的
所以此時就得通過編譯器,把“.java”后綴的源代碼文件編譯為“.class”后綴的字節碼文件。
這個“.class”后綴的字節碼文件里,存放的就是對你寫出來的代碼編譯好的字節碼了,這個字節碼才是計算器可以理解的一種語言,而不是我們寫出來的那一堆代碼。
這個字節碼看起來大概是下面這樣的:
這段字節碼并不是完全對照著HelloWorld那個類來寫的,就是給一段示例,讓大家知道“.java”翻譯成的“.class”是大概什么樣子的。
這里比如說“0: aload_0”這樣的,就是“字節碼指令”,他對應了一條一條的機器指令,計算機只有讀到這種機器碼指令,才知道具體應該要干什么。
比如說字節碼指令可能會讓計算機從內存里讀取某個數據,或者把某個數據寫入到內存里去,都有可能,各種各樣的指令,就會指示計算機去干各種各樣的事情。
所以現在首先明白一點,我們寫好的Java代碼是會被翻譯成字節碼的,對應各種字節碼指令。
那么Java代碼通過JVM跑起來的第一件事情就明確了, 首先Java代碼被編譯出來的字節碼指令一定會被一條一條的執行,這樣才能實現我們寫好的代碼被執行的效果。
那么在執行字節碼指令的時候,JVM里的程序計數器就是用來記錄每個線程當前執行的字節碼指令的位置的,記錄當前線程目前執行到了哪一條字節碼指令。
因為會有多個線程來并發的執行各種不同的代碼,所以每個線程都有自己的一個程序計數器,專門記錄當前這個線程目前執行到了哪一條字節碼指令了
下圖更加清晰的展示出了他們之間的關系。
Java代碼在執行的時候,一定是線程來執行某個方法中的代碼,比如哪怕就是上面的那個最基礎的HelloWorld代碼,也會有一個main線程來執行main方法里的代碼。
在方法里,經常會定義一些方法內的局部變量,比如下面這樣,就在方法里定義了一個局部變量“name”。
public void sayHello() {
? ? ? ? String name = "hello";
}
所以JVM必須有一塊區域是來保存每個方法內的局部變量等等數據的,這個區域就是Java虛擬機棧
每個線程都會去執行各種方法的代碼,方法內還會嵌套調用其他的方法,所以首先每個線程都有自己的Java虛擬機棧。
如果線程執行了一個方法,那么就會被這個方法調用創建對應的一個棧幀,棧幀里就有這個方法的局部變量表 、操作數棧、動態鏈接、方法出口等東西,但是這里別的不太好理解,先理解一個局部變量就可以。
比如說一個線程調用了上面寫的“sayHello”方法,那么就會為“sayHello”方法創建一個棧幀,壓入線程自己的Java虛擬機棧里面去。
在棧幀的局部變量表里就會有“name”這個局部變量,下圖展示了這個過程。
接著如果“sayHello”方法調用了另外一個“greeting”方法 ,比如下面那樣的代碼:
那么這個時候會給“greeting”方法又創建一個棧幀壓入線程的Java虛擬機棧里,因為開始執行“greeting”方法了,而且“greeting”方法的棧幀的局部變量表里會有一個“greet”變量,這是“greeting”方法的局部變量。
接著如果“greeting”方法執行完畢了,就會把“greeting”方法對應的棧幀從Java虛擬機棧里給出棧,然后如果“sayHello”方法也執行完畢了,就會把“sayHello”方法也從Java虛擬機棧里出棧。
這就是JVM中的 “?Java虛擬機棧?” 這個組件的作用,調用執行任何方法的時候,都會給方法創建棧幀然后入棧。
而在棧幀里存放了這個方法對應的局部變量之類的數據,包括這個方法執行的其他相關的信息,方法執行完畢之后就出棧。
JVM中有另外一個非常關鍵的區域,就是Java堆,這里就是存放我們在代碼中創建的各種對象的,比如說下面的代碼:
public void teach(String name) {
? ? Student student = new Student(name);
? ? student.study();
}
上面的 “new Student(name)” 這個代碼就是創建了一個Student類型的對象實例,這個對象實例里面會包含一些數據。
比如說這個Student的“name”就是屬于這個對象實例的一個數據,那么類似Student這樣的對象,就會存放在Java堆內存里。
Java堆內存區域里會放入類似Student的對象,然后方法的棧幀的局部變量表里,這個引用類型的“student”局部變量就會存放Student對象的地址。
相當于你可以認為局部變量表里的“student”指向了Java堆里的Student對象。
看下圖會更加清晰一些。
方法區 / Metaspace
這個方法區是在JDK 1.8以前的版本里,代表JVM中的一塊區域,主要是放類似Student類自己的信息的,平時用到的各種類的信息,都是放在這個區域里的,還會有一些類似常量池的東西放在這個區域里。
但是在JDK 1.8以后,這塊區域的名字改了,叫做“Metaspace”,可以認為是“元數據空間”這樣的意思,這里當然主要其實還是存放我們自己寫的各種類相關的信息。
本地方法棧
其實在JDK很多底層API里,比如IO相關的,NIO相關的,網絡Socket相關的,如果大家去看他內部的源碼,會發現很多地方都不是Java代碼了。
很多地方都會去走native方法,去調用本地操作系統里面的一些方法,可能調用的都是c語言寫的方法,或者一些底層類庫,比如下面這樣的:
public native int hashCode();
在調用這種native方法的時候,就會有線程對應的本地方法棧,這個里面也是跟Java虛擬機棧類似的,也是存放各種native方法的局部變量表之類的信息。
堆外內存
還有一個區域,是不屬于JVM的,通過NIO中的allocateDirect這種API,可以在Java堆外分配內存空間。
然后通過Java虛擬機里的 DirectByteBuffer 來引用和操作堆外內存空間,其實很多技術都會用這種方式,因為有一些場景下,堆外內存分配可以提升性能。
總結
最后做一點總結,我們的Java代碼通過JVM來運行的時候,首先一定會一行一行執行編譯好的字節碼指令。
然后在執行的過程中,對于方法的調用,會通過Java虛擬機棧來為每個方法創建棧幀入棧和出棧,而且棧幀里有方法的局部變量表
接著對于對象的創建,會分配到Java堆內存里去
對于類信息的存儲,會放在方法區 / Metaspace這樣的區域里。
另外有兩塊特殊的區域:
本地方法棧,是執行native方法時候用的棧,跟Java虛擬機棧是類似的
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。