想要面試阿里Java研發崗?你必須得跨過 JVM 這道坎!

概述

很多人想要到阿里巴巴、美團、京東等互聯網大公司去面試,但是現在互聯網大廠面試一般都必定會考核JVM相關的知識積累和實踐經驗,畢竟線上系統寫好代碼部署之後,每個工程師都必須關注JVM相關的東西,比如OOM、GC等問題.

所以一起來看看JVM的最基本的區域劃分以及工作原理,這個基本上是互聯網公司面試必問。

區域劃分

jvm的區域劃分如下所示:

想要面試阿里Java研發崗?你必須得跨過 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”後綴的字節碼文件裡,存放的就是對你寫出來的代碼編譯好的字節碼了,這個字節碼才是計算器可以理解的一種語言,而不是我們寫出來的那一堆代碼。

這個字節碼看起來大概是下面這樣的:

想要面試阿里Java研發崗?你必須得跨過 JVM 這道坎!


這段字節碼並不是完全對照著HelloWorld那個類來寫的,就是給一段示例,讓大家知道“.java”翻譯成的“.class”是大概什麼樣子的。

這裡比如說“0: aload_0”這樣的,就是“字節碼指令”,他對應了一條一條的機器指令,計算機只有讀到這種機器碼指令,才知道具體應該要幹什麼。

比如說字節碼指令可能會讓計算機從內存裡讀取某個數據,或者把某個數據寫入到內存裡去,都有可能,各種各樣的指令,就會指示計算機去幹各種各樣的事情。

所以現在首先明白一點,我們寫好的Java代碼是會被翻譯成字節碼的,對應各種字節碼指令。

那麼Java代碼通過JVM跑起來的第一件事情就明確了, 首先Java代碼被編譯出來的字節碼指令一定會被一條一條的執行,這樣才能實現我們寫好的代碼被執行的效果。

那麼在執行字節碼指令的時候,JVM裡的程序計數器就是用來記錄每個線程當前執行的字節碼指令的位置的,記錄當前線程目前執行到了哪一條字節碼指令。

因為會有多個線程來併發的執行各種不同的代碼,所以每個線程都有自己的一個程序計數器,專門記錄當前這個線程目前執行到了哪一條字節碼指令了

下圖更加清晰的展示出了他們之間的關係。

想要面試阿里Java研發崗?你必須得跨過 JVM 這道坎!

Java虛擬機棧

Java代碼在執行的時候,一定是線程來執行某個方法中的代碼,比如哪怕就是上面的那個最基礎的HelloWorld代碼,也會有一個main線程來執行main方法裡的代碼。

在方法裡,經常會定義一些方法內的局部變量,比如下面這樣,就在方法裡定義了一個局部變量“name”。

public void sayHello() {
String name = "hello";
}

所以JVM必須有一塊區域是來保存每個方法內的局部變量等等數據的,這個區域就是Java虛擬機棧

每個線程都會去執行各種方法的代碼,方法內還會嵌套調用其他的方法,所以首先每個線程都有自己的Java虛擬機棧。

如果線程執行了一個方法,那麼就會被這個方法調用創建對應的一個棧幀,棧幀裡就有這個方法的局部變量表 、操作數棧、動態鏈接、方法出口等東西,但是這裡別的不太好理解,先理解一個局部變量就可以。

比如說一個線程調用了上面寫的“sayHello”方法,那麼就會為“sayHello”方法創建一個棧幀,壓入線程自己的Java虛擬機棧裡面去。

在棧幀的局部變量表裡就會有“name”這個局部變量,下圖展示了這個過程。

想要面試阿里Java研發崗?你必須得跨過 JVM 這道坎!

接著如果“sayHello”方法調用了另外一個“greeting”方法 ,比如下面那樣的代碼:

想要面試阿里Java研發崗?你必須得跨過 JVM 這道坎!

那麼這個時候會給“greeting”方法又創建一個棧幀壓入線程的Java虛擬機棧裡,因為開始執行“greeting”方法了,而且“greeting”方法的棧幀的局部變量表裡會有一個“greet”變量,這是“greeting”方法的局部變量。

想要面試阿里Java研發崗?你必須得跨過 JVM 這道坎!

接著如果“greeting”方法執行完畢了,就會把“greeting”方法對應的棧幀從Java虛擬機棧裡給出棧,然後如果“sayHello”方法也執行完畢了,就會把“sayHello”方法也從Java虛擬機棧裡出棧。

這就是JVM中的 “ Java虛擬機棧 ” 這個組件的作用,調用執行任何方法的時候,都會給方法創建棧幀然後入棧。

而在棧幀裡存放了這個方法對應的局部變量之類的數據,包括這個方法執行的其他相關的信息,方法執行完畢之後就出棧。

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對象。

看下圖會更加清晰一些。

想要面試阿里Java研發崗?你必須得跨過 JVM 這道坎!

方法區 / 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虛擬機棧是類似的
  • 堆外內存,是可以在Java堆外分配內存空間來存儲一些對象。

最後

大家可以轉發關注下,以後還會更新更多精選文章分享!

相關推薦

推薦中...