1. 程式人生 > >想要去阿里面試?你必須得跨過 JVM 這道坎!

想要去阿里面試?你必須得跨過 JVM 這道坎!

概述

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

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

區域劃分

jvm的區域劃分如下所示:

大致就是分為:程式計數器,虛擬機器棧,堆,方法區,本地方法棧,這幾個部分。

接下來我們從自己寫好的Java程式碼如何通過JVM來執行的角度,來分析一下JVM裡這些區域是如何支撐我們的Java程式碼跑起來的。

程式計數器

假設我們有如下的一個類,就是最最基本的一個HelloWorld而已:

上面那段程式碼首先會存在於 “.java” 字尾的檔案裡,這個檔案就是java原始碼檔案,但是這個檔案是面向我們程式設計師的,計算機他是看不懂你寫的這段程式碼的

所以此時就得通過編譯器,把“.java”字尾的原始碼檔案編譯為“.class”字尾的位元組碼檔案。

這個“.class”字尾的位元組碼檔案裡,存放的就是對你寫出來的程式碼編譯好的位元組碼了,這個位元組碼才是計算器可以理解的一種語言,而不是我們寫出來的那一堆程式碼。

這個位元組碼看起來大概是下面這樣的:

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

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

比如說位元組碼指令可能會讓計算機從記憶體裡讀取某個資料,或者把某個資料寫入到記憶體裡去,都有可能,各種各樣的指令,就會指示計算機去幹各種各樣的事情。

所以現在首先明白一點,我們寫好的Java程式碼是會被翻譯成位元組碼的,對應各種位元組碼指令。

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

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

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

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

Java虛擬機器棧

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

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

所以JVM必須有一塊區域是來儲存每個方法內的區域性變數等等資料的,這個區域就是Java虛擬機器棧

每個執行緒都會去執行各種方法的程式碼,方法內還會巢狀呼叫其他的方法,所以首先每個執行緒都有自己的Java虛擬機器棧。

如果執行緒執行了一個方法,那麼就會被這個方法呼叫建立對應的一個棧幀,棧幀裡就有這個方法的區域性變量表 、運算元棧、動態連結、方法出口等東西,但是這裡別的不太好理解,先理解一個區域性變數就可以。

比如說一個執行緒呼叫了上面寫的“sayHello”方法,那麼就會為“sayHello”方法建立一個棧幀,壓入執行緒自己的Java虛擬機器棧裡面去。

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

接著如果“sayHello”方法呼叫了另外一個“greeting”方法 ,比如下面那樣的程式碼:

那麼這個時候會給“greeting”方法又建立一個棧幀壓入執行緒的Java虛擬機器棧裡,因為開始執行“greeting”方法了,而且“greeting”方法的棧幀的區域性變量表裡會有一個“greet”變數,這是“greeting”方法的區域性變數。

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

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

而在棧幀裡存放了這個方法對應的區域性變數之類的資料,包括這個方法執行的其他相關的資訊,方法執行完畢之後就出棧。

Java堆記憶體

JVM中有另外一個非常關鍵的區域,就是Java堆,這裡就是存放我們在程式碼中建立的各種物件的,比如說下面的程式碼:

上面的 “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虛擬機器棧是類似的

        ·     堆外記憶體,是可以在Java堆外分配記憶體空間來儲存一些物件。

Java架構/分散式:705127209(大牛交流群)沒有