溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

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

發(fā)布時(shí)間:2020-07-02 11:11:59 來(lái)源:網(wǎng)絡(luò) 閱讀:250 作者:灰色程序 欄目:編程語(yǔ)言

概述

很多人想要到阿里巴巴、美團(tuán)、京東等互聯(lián)網(wǎng)大公司去面試,但是現(xiàn)在互聯(lián)網(wǎng)大廠面試一般都必定會(huì)考核JVM相關(guān)的知識(shí)積累和實(shí)踐經(jīng)驗(yàn),畢竟線上系統(tǒng)寫(xiě)好代碼部署之后,每個(gè)工程師都必須關(guān)注JVM相關(guān)的東西,比如OOM、GC等問(wèn)題.

所以一起來(lái)看看JVM的最基本的區(qū)域劃分以及工作原理,這個(gè)基本上是互聯(lián)網(wǎng)公司面試必問(wèn)。

區(qū)域劃分

jvm的區(qū)域劃分如下所示:

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

大致就是分為:程序計(jì)數(shù)器,虛擬機(jī)棧,堆,方法區(qū),本地方法棧,這幾個(gè)部分。

接下來(lái)我們從自己寫(xiě)好的Java代碼如何通過(guò)JVM來(lái)運(yùn)行的角度,來(lái)分析一下JVM里這些區(qū)域是如何支撐我們的Java代碼跑起來(lái)的。

程序計(jì)數(shù)器

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

public class HelloWorld {

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

? ? ? ? ? ??System.out.println("Hello World");

? ? ? ? }

}

上面那段代碼首先會(huì)存在于 “.java” 后綴的文件里,這個(gè)文件就是java源代碼文件,但是這個(gè)文件是面向我們程序員的,計(jì)算機(jī)他是看不懂你寫(xiě)的這段代碼的

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

這個(gè)“.class”后綴的字節(jié)碼文件里,存放的就是對(duì)你寫(xiě)出來(lái)的代碼編譯好的字節(jié)碼了,這個(gè)字節(jié)碼才是計(jì)算器可以理解的一種語(yǔ)言,而不是我們寫(xiě)出來(lái)的那一堆代碼。

這個(gè)字節(jié)碼看起來(lái)大概是下面這樣的:

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

這段字節(jié)碼并不是完全對(duì)照著HelloWorld那個(gè)類來(lái)寫(xiě)的,就是給一段示例,讓大家知道“.java”翻譯成的“.class”是大概什么樣子的。

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

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

所以現(xiàn)在首先明白一點(diǎn),我們寫(xiě)好的Java代碼是會(huì)被翻譯成字節(jié)碼的,對(duì)應(yīng)各種字節(jié)碼指令。

那么Java代碼通過(guò)JVM跑起來(lái)的第一件事情就明確了, 首先Java代碼被編譯出來(lái)的字節(jié)碼指令一定會(huì)被一條一條的執(zhí)行,這樣才能實(shí)現(xiàn)我們寫(xiě)好的代碼被執(zhí)行的效果。

那么在執(zhí)行字節(jié)碼指令的時(shí)候,JVM里的程序計(jì)數(shù)器就是用來(lái)記錄每個(gè)線程當(dāng)前執(zhí)行的字節(jié)碼指令的位置的,記錄當(dāng)前線程目前執(zhí)行到了哪一條字節(jié)碼指令。

因?yàn)闀?huì)有多個(gè)線程來(lái)并發(fā)的執(zhí)行各種不同的代碼,所以每個(gè)線程都有自己的一個(gè)程序計(jì)數(shù)器,專門(mén)記錄當(dāng)前這個(gè)線程目前執(zhí)行到了哪一條字節(jié)碼指令了

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

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

Java虛擬機(jī)棧

Java代碼在執(zhí)行的時(shí)候,一定是線程來(lái)執(zhí)行某個(gè)方法中的代碼,比如哪怕就是上面的那個(gè)最基礎(chǔ)的HelloWorld代碼,也會(huì)有一個(gè)main線程來(lái)執(zhí)行main方法里的代碼。

在方法里,經(jīng)常會(huì)定義一些方法內(nèi)的局部變量,比如下面這樣,就在方法里定義了一個(gè)局部變量“name”。

public void sayHello() {

? ? ? ? String name = "hello";

}

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

每個(gè)線程都會(huì)去執(zhí)行各種方法的代碼,方法內(nèi)還會(huì)嵌套調(diào)用其他的方法,所以首先每個(gè)線程都有自己的Java虛擬機(jī)棧。

如果線程執(zhí)行了一個(gè)方法,那么就會(huì)被這個(gè)方法調(diào)用創(chuàng)建對(duì)應(yīng)的一個(gè)棧幀,棧幀里就有這個(gè)方法的局部變量表 、操作數(shù)棧、動(dòng)態(tài)鏈接、方法出口等東西,但是這里別的不太好理解,先理解一個(gè)局部變量就可以。

比如說(shuō)一個(gè)線程調(diào)用了上面寫(xiě)的“sayHello”方法,那么就會(huì)為“sayHello”方法創(chuàng)建一個(gè)棧幀,壓入線程自己的Java虛擬機(jī)棧里面去。

在棧幀的局部變量表里就會(huì)有“name”這個(gè)局部變量,下圖展示了這個(gè)過(guò)程。

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

接著如果“sayHello”方法調(diào)用了另外一個(gè)“greeting”方法 ,比如下面那樣的代碼:

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

那么這個(gè)時(shí)候會(huì)給“greeting”方法又創(chuàng)建一個(gè)棧幀壓入線程的Java虛擬機(jī)棧里,因?yàn)殚_(kāi)始執(zhí)行“greeting”方法了,而且“greeting”方法的棧幀的局部變量表里會(huì)有一個(gè)“greet”變量,這是“greeting”方法的局部變量。

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

接著如果“greeting”方法執(zhí)行完畢了,就會(huì)把“greeting”方法對(duì)應(yīng)的棧幀從Java虛擬機(jī)棧里給出棧,然后如果“sayHello”方法也執(zhí)行完畢了,就會(huì)把“sayHello”方法也從Java虛擬機(jī)棧里出棧。

這就是JVM中的 “?Java虛擬機(jī)棧?” 這個(gè)組件的作用,調(diào)用執(zhí)行任何方法的時(shí)候,都會(huì)給方法創(chuàng)建棧幀然后入棧。

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

Java堆內(nèi)存

JVM中有另外一個(gè)非常關(guān)鍵的區(qū)域,就是Java堆,這里就是存放我們?cè)诖a中創(chuàng)建的各種對(duì)象的,比如說(shuō)下面的代碼:

public void teach(String name) {

? ? Student student = new Student(name);

? ? student.study();

}

上面的 “new Student(name)” 這個(gè)代碼就是創(chuàng)建了一個(gè)Student類型的對(duì)象實(shí)例,這個(gè)對(duì)象實(shí)例里面會(huì)包含一些數(shù)據(jù)。

比如說(shuō)這個(gè)Student的“name”就是屬于這個(gè)對(duì)象實(shí)例的一個(gè)數(shù)據(jù),那么類似Student這樣的對(duì)象,就會(huì)存放在Java堆內(nèi)存里。

Java堆內(nèi)存區(qū)域里會(huì)放入類似Student的對(duì)象,然后方法的棧幀的局部變量表里,這個(gè)引用類型的“student”局部變量就會(huì)存放Student對(duì)象的地址。

相當(dāng)于你可以認(rèn)為局部變量表里的“student”指向了Java堆里的Student對(duì)象。

看下圖會(huì)更加清晰一些。

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

方法區(qū) / Metaspace

這個(gè)方法區(qū)是在JDK 1.8以前的版本里,代表JVM中的一塊區(qū)域,主要是放類似Student類自己的信息的,平時(shí)用到的各種類的信息,都是放在這個(gè)區(qū)域里的,還會(huì)有一些類似常量池的東西放在這個(gè)區(qū)域里。

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

本地方法棧

其實(shí)在JDK很多底層API里,比如IO相關(guān)的,NIO相關(guān)的,網(wǎng)絡(luò)Socket相關(guān)的,如果大家去看他內(nèi)部的源碼,會(huì)發(fā)現(xiàn)很多地方都不是Java代碼了。

很多地方都會(huì)去走native方法,去調(diào)用本地操作系統(tǒng)里面的一些方法,可能調(diào)用的都是c語(yǔ)言寫(xiě)的方法,或者一些底層類庫(kù),比如下面這樣的:

public native int hashCode();

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

堆外內(nèi)存

還有一個(gè)區(qū)域,是不屬于JVM的,通過(guò)NIO中的allocateDirect這種API,可以在Java堆外分配內(nèi)存空間。

然后通過(guò)Java虛擬機(jī)里的 DirectByteBuffer 來(lái)引用和操作堆外內(nèi)存空間,其實(shí)很多技術(shù)都會(huì)用這種方式,因?yàn)橛幸恍﹫?chǎng)景下,堆外內(nèi)存分配可以提升性能。

總結(jié)

最后做一點(diǎn)總結(jié),我們的Java代碼通過(guò)JVM來(lái)運(yùn)行的時(shí)候,首先一定會(huì)一行一行執(zhí)行編譯好的字節(jié)碼指令。

然后在執(zhí)行的過(guò)程中,對(duì)于方法的調(diào)用,會(huì)通過(guò)Java虛擬機(jī)棧來(lái)為每個(gè)方法創(chuàng)建棧幀入棧和出棧,而且棧幀里有方法的局部變量表

接著對(duì)于對(duì)象的創(chuàng)建,會(huì)分配到Java堆內(nèi)存里去

對(duì)于類信息的存儲(chǔ),會(huì)放在方法區(qū) / Metaspace這樣的區(qū)域里。

另外有兩塊特殊的區(qū)域:

  • 本地方法棧,是執(zhí)行native方法時(shí)候用的棧,跟Java虛擬機(jī)棧是類似的

  • 堆外內(nèi)存,是可以在Java堆外分配內(nèi)存空間來(lái)存儲(chǔ)一些對(duì)象。
向AI問(wèn)一下細(xì)節(jié)
AI