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

概述

 

 

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

 

因此一塊兒來看看JVM的最基本的區域劃分以及工做原理,這個基本上是互聯網公司面試必問。程序員

 

 

區域劃分

 

jvm的區域劃分以下所示:面試

 

 

大體就是分爲:程序計數器,虛擬機棧,堆,方法區,本地方法棧,這幾個部分。網絡

 

接下來咱們從本身寫好的Java代碼如何經過JVM來運行的角度,來分析一下JVM裏這些區域是如何支撐咱們的Java代碼跑起來的。併發

 

 

程序計數器

 

假設咱們有以下的一個類,就是最最基本的一個HelloWorld而已:jvm

 

public class HelloWorld {性能

        public static void main(String[] args) {spa

            System.out.println("Hello World");操作系統

        }線程

}

 

上面那段代碼首先會存在於 「.java」 後綴的文件裏,這個文件就是java源代碼文件,可是這個文件是面向咱們程序員的,計算機他是看不懂你寫的這段代碼的

 

因此此時就得經過編譯器,把「.java」後綴的源代碼文件編譯爲「.class」後綴的字節碼文件。

 

這個「.class」後綴的字節碼文件裏,存放的就是對你寫出來的代碼編譯好的字節碼了,這個字節碼纔是計算器能夠理解的一種語言,而不是咱們寫出來的那一堆代碼。

 

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

 

 

這段字節碼並非徹底對照着HelloWorld那個類來寫的,就是給一段示例,讓你們知道「.java」翻譯成的「.class」是大概什麼樣子的。

 

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

 

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

 

因此如今首先明白一點,咱們寫好的Java代碼是會被翻譯成字節碼的,對應各類字節碼指令。

 

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

 

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

 

由於會有多個線程來併發的執行各類不一樣的代碼,因此每一個線程都有本身的一個程序計數器,專門記錄當前這個線程目前執行到了哪一條字節碼指令了

 

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

 

 

 

Java虛擬機棧

 

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虛擬機棧 」 這個組件的做用,調用執行任何方法的時候,都會給方法建立棧幀而後入棧。

 

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

 

 

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虛擬機棧是相似的

     

  • 堆外內存,是能夠在Java堆外分配內存空間來存儲一些對象。

相關文章
相關標籤/搜索