深入理解java虛擬機(jī)(一)

? ? ? ?Java程序員除了需要寫(xiě)基本的業(yè)務(wù)代碼外,服務(wù)性能調(diào)優(yōu)的能力也是一個(gè)很重要的能力。談到性能調(diào)優(yōu)不得不讓人首先想到的就是java虛擬機(jī)(JVM)相關(guān)的問(wèn)題了。下面將會(huì)分幾個(gè)專(zhuān)題介紹一下jvm相關(guān)的理論知識(shí)。

一、JVM內(nèi)存模型

在Java中,JVM內(nèi)存模型主要分為堆、程序計(jì)數(shù)器、方法區(qū)、虛擬機(jī)棧和本地方法棧。

圖1 JVM內(nèi)存模型

1.堆(Java Heap)

? ? ? ? 對(duì)于java應(yīng)用程序來(lái)說(shuō),Java堆是虛擬機(jī)所管理的內(nèi)存中最大的一塊區(qū)域。Java堆是所有線程共享的一內(nèi)存區(qū)域。Java中幾乎所有的對(duì)象實(shí)例都在Java堆中分配內(nèi)存。

? ? ? ? 值得注意的一點(diǎn)是并不是所有的Java堆都是按照“新生代”,“老年代”等概念進(jìn)行對(duì)象的管理的,但是業(yè)界主流的HotSpot虛擬機(jī)內(nèi)部的垃圾收集邏輯都是基于這種分代回收來(lái)設(shè)計(jì)的。所以本文所描述的內(nèi)容都是基于HotSpot虛擬機(jī)進(jìn)行的。

? ? ? ?眾所周知,堆被劃分為新生代和老年代,新生代又被進(jìn)一步劃分為Eden和Survivor區(qū),最后Survivor由From Survivor和To Survivor組成。而隨著java版本的更新,Java6、Java7和Java8對(duì)于堆內(nèi)部的結(jié)構(gòu)也有相應(yīng)的變化。其中在Java6版本中,永久代在非堆內(nèi)存區(qū);到了Java7版本,永久代的靜態(tài)變量和運(yùn)行時(shí)常量池被合并到了堆中;而到了Java8,永久代被元空間取代了。結(jié)構(gòu)如下圖所示:

圖2 堆內(nèi)存結(jié)構(gòu)

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

? ? ? ?程序計(jì)數(shù)器是一塊比較小的內(nèi)存空間,它的作用是可以當(dāng)做當(dāng)前線程所執(zhí)行的字節(jié)碼的行號(hào)指示器,字節(jié)碼解釋器工作時(shí)就是通過(guò)這個(gè)計(jì)數(shù)器的值來(lái)選取下一條需要執(zhí)行的的字節(jié)碼指令。由于Java虛擬機(jī)在運(yùn)行過(guò)程中是多線程輪流切換執(zhí)行的,所以為了能夠找到線程切換后的執(zhí)行位置,每一個(gè)線程都需要維護(hù)一個(gè)獨(dú)立的程序計(jì)數(shù)器,所以程序計(jì)數(shù)器是線程私有的。

3.Java虛擬機(jī)棧

? ? ? ? 虛擬機(jī)棧描述的是Java方法執(zhí)行的線程內(nèi)存模型,每一個(gè)方法被執(zhí)行的時(shí)候,在棧中都會(huì)創(chuàng)建一個(gè)棧幀用來(lái)存儲(chǔ)方法中的局部變量表、操作數(shù)棧、動(dòng)態(tài)鏈接、方法出口等信息。每一個(gè)方法被調(diào)用的過(guò)程就對(duì)應(yīng)一個(gè)棧幀在虛擬機(jī)中入棧到出棧的過(guò)程。所以Java虛擬機(jī)棧也是線程私有的。在虛擬機(jī)棧中局部變量表存儲(chǔ)著基本數(shù)據(jù)類(lèi)型(boolean、byte、char、short、int、float、long、double)、對(duì)象引用(reference)和returnAddress類(lèi)型,其中對(duì)象引用并不是對(duì)象本身,可能是一個(gè)指向?qū)ο笃鹗嫉刂返囊弥羔樢部赡苁且粋€(gè)指向代表對(duì)象的句柄或者是其他與對(duì)象有關(guān)的位置。

4.方法區(qū)

? ? ? ? 與Java堆類(lèi)似的,方法區(qū)也是各個(gè)線程共享的內(nèi)存區(qū)域,它用來(lái)存放被虛擬機(jī)加載的類(lèi)型信息、常量、靜態(tài)信息、即時(shí)編譯器編譯后的代碼緩存等信息,其中方法區(qū)有一個(gè)別名也叫“永久代”,其實(shí)這個(gè)說(shuō)法是不合理的,因?yàn)閮H僅HotSpot根據(jù)分代回收才喜歡把方法區(qū)稱(chēng)之為永久代。隨著Java版本的迭代,到了Java7的時(shí)候,HotSpot已經(jīng)將字符串常量池和靜態(tài)變量移植到Java堆中去進(jìn)行維護(hù)了。而由于方法區(qū)也存在OOM的問(wèn)題,但是方法區(qū)的大小設(shè)定又很難有相關(guān)的經(jīng)驗(yàn)可談,所以到了Java8以后,直接將方法區(qū)剩下的內(nèi)容移植到元空間中存取,從此就再也沒(méi)有方法區(qū)的概念了。

5.運(yùn)行時(shí)常量池

? ? ? ?運(yùn)行時(shí)常量池是方法區(qū)的一部分,經(jīng)過(guò)編譯行程的class文件中除了有類(lèi)的版本、字段、方法、接口等描述信息以外,還有一項(xiàng)信息是常量池表,用來(lái)存放編譯器生成的各種字面量和符號(hào)引用,這部分內(nèi)容經(jīng)過(guò)類(lèi)加載以后就存放在方法去中的運(yùn)行時(shí)常量池中。

下面舉一個(gè)??,看一下在方法調(diào)用過(guò)程中以及對(duì)象和方法是如何進(jìn)行存儲(chǔ)和工作的。

代碼塊

JVM在運(yùn)行期間主要進(jìn)行了如下的操作:

(1)根據(jù)JVM配置參數(shù)分配堆、棧以及方法區(qū)的內(nèi)存大小,申請(qǐng)方法中所有的變量和對(duì)象對(duì)應(yīng)的空間。class 文件加載、驗(yàn)證、準(zhǔn)備以及解析,其中準(zhǔn)備階段會(huì)為類(lèi)的靜態(tài)變量分配內(nèi)存,初始化為系統(tǒng)的初始值。

圖3

(2)完成上一個(gè)步驟后,將會(huì)進(jìn)行最后一個(gè)初始化階段。在這個(gè)階段中,JVM 首先會(huì)執(zhí)行構(gòu)造器?方法,編譯器會(huì)在.java 文件被編譯成.class 文件時(shí),收集所有類(lèi)的初始化代碼,包括靜態(tài)變量賦值語(yǔ)句、靜態(tài)代碼塊、靜態(tài)方法,收集在一起成為?() 方法。

圖4

(3)執(zhí)行方法。啟動(dòng) main 線程,執(zhí)行 main 方法,開(kāi)始執(zhí)行第一行代碼。此時(shí)堆內(nèi)存中會(huì)創(chuàng)建一個(gè) student 對(duì)象,對(duì)象引用 student 就存放在棧中。此時(shí)在棧中就有了關(guān)于student對(duì)象實(shí)例的引用。同時(shí)在堆中會(huì)申請(qǐng)一塊內(nèi)存區(qū)域來(lái)存放student對(duì)象的數(shù)據(jù)。而棧中的引用會(huì)指向堆中的實(shí)際數(shù)據(jù)。

圖5

(4)此時(shí)再次創(chuàng)建一個(gè) JVMCase 對(duì)象,調(diào)用 sayHello 非靜態(tài)方法,sayHello 方法屬于對(duì)象 JVMCase,此時(shí) sayHello 方法入棧,并通過(guò)棧中的 student 引用調(diào)用堆中的 Student 對(duì)象;之后,調(diào)用靜態(tài)方法 print,print 靜態(tài)方法屬于 JVMCase 類(lèi),是從靜態(tài)方法中獲取,之后放入到棧中,也是通過(guò) student 引用調(diào)用堆中的 student 對(duì)象。

圖6

以上便是整個(gè)main方法在執(zhí)行過(guò)程中JVM各模塊的主要操作。

最后:打一個(gè)小廣告,后續(xù)的文章會(huì)在微信公眾號(hào)“程序員之家QAQ”推送,歡迎大家搜索關(guān)注~~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容