想理解JVM看了這篇文章答恶,就知道了

?前言

文章很長文末有福利

?本章節(jié)屬于Java進階系列瀑粥,前面關(guān)于設(shè)計模式講解完了挣跋,有興趣的童鞋可以翻看之前的博文,后面會講解JVM的優(yōu)化狞换,整個系列會完整的講解整個java體系與生態(tài)相關(guān)的中間件知識避咆。本次將對jvm有更深入的學(xué)習(xí),我們不僅要讓程序能跑起來修噪,而且是可以跑的更快查库!可以分析解決在生產(chǎn)環(huán)境中所遇到的各種“棘手”的問題,比如運行的應(yīng)用卡住了黄琼,日志不輸出樊销,程序沒有反應(yīng),CPU負(fù)載突然升高脏款,多線程應(yīng)用下围苫,如何分配線程數(shù)量等。

2|0JVM介紹2|

1什么是JVM

? 作為java工程師撤师,對于jvm肯定不陌生剂府。JVM是Java Virtual Machine的縮寫,通俗來說也就是運行java代碼的容器剃盾。當(dāng)項目啟動時腺占,會根據(jù)jvm相關(guān)配置參數(shù)淤袜,在計算機的內(nèi)存中開啟一片空間用于運行JVM。之后java相關(guān)代碼就會被加載進JVM中運行衰伯。

?百度百科對JVM的定義:

2|2為什么要了解JVM

? 對于Java程序員來說铡羡,在虛擬機自動內(nèi)存管理機制的幫助下,不再需要為每一個new操作去寫配對的delete/free代碼嚎研,不容易出現(xiàn)內(nèi)存泄漏和內(nèi)存溢出問題蓖墅,看起來由虛擬機管理內(nèi)存一切都很美好。不過临扮,也正是因為Java程序員把控制內(nèi)存的權(quán)力交給了Java虛擬機论矾,一旦出現(xiàn)內(nèi)存泄漏和溢出方面的問題,如果不了解虛擬機是怎樣使用內(nèi)存的杆勇,那排查錯誤贪壳、修正問題將會成為一項異常艱難的工作。

3|0JVM內(nèi)存模型3|1JVM整體架構(gòu)

?由上面的圖可以看出蚜退,JVM虛擬機中主要是由三部分構(gòu)成闰靴,分別是類加載子系統(tǒng)、運行時數(shù)據(jù)區(qū)钻注、執(zhí)行引擎蚂且。

類加載子系統(tǒng)

?Java虛擬機把描述類的數(shù)據(jù)從Class文件加載到內(nèi)存,并對數(shù)據(jù)進行校驗幅恋、轉(zhuǎn)換解析和初始化杏死,最終形成可以被虛擬機直接使用的Java類型。

運行時數(shù)據(jù)區(qū)

?Java虛擬機在執(zhí)行Java程序的過程中會把它所管理的內(nèi)存劃分為若干個不同的數(shù)據(jù)區(qū)域捆交。這些區(qū)域有各自的用途淑翼,以及創(chuàng)建和銷毀的時間,有的區(qū)域隨著虛擬機進程的啟動而一直存在品追,有些區(qū)域則是依賴用戶線程的啟動和結(jié)束而建立和銷毀比被。

執(zhí)行引擎

?執(zhí)行引擎用于執(zhí)行JVM字節(jié)碼指令液荸,主要有兩種方式酥筝,分別是解釋執(zhí)行和編譯執(zhí)行做鹰,區(qū)別在于,解釋執(zhí)行是在執(zhí)行時翻譯成虛擬機指令執(zhí)行泞莉,而編譯執(zhí)行是在執(zhí)行之前先進行編譯再執(zhí)行哪雕。解釋執(zhí)行啟動快,執(zhí)行效率低戒财。編譯執(zhí)行热监,啟動慢,執(zhí)行效率高饮寞。垃圾回收器就是自動管理運行數(shù)據(jù)區(qū)的內(nèi)存孝扛,將無用的內(nèi)存占用進行清除列吼,釋放內(nèi)存資源。

本地方法庫苦始、本地庫接口

?在jdk的底層中寞钥,有一些實現(xiàn)是需要調(diào)用本地方法完成的(使用c或c++寫的方法),就是通過本地庫接口調(diào)用完成的陌选。比如:System.currentTimeMillis()方法理郑。

3|2運行時數(shù)據(jù)區(qū)

? 運行時數(shù)據(jù)區(qū)是jvm中最為重要的部門。也是我們在調(diào)優(yōu)時需要重點關(guān)注的區(qū)域咨油,下面我們一起了解下這個部分的具體內(nèi)容您炉。

?根據(jù)《Java虛擬機規(guī)范》中的規(guī)定,在運行時數(shù)據(jù)區(qū)將內(nèi)存分為方法區(qū)(Method Area)役电、Java堆區(qū)(Java

Heap)赚爵、Java虛擬機棧(Java Virtual Machine Stack)、程序計數(shù)器(Program Counter Register)法瑟、本地方法

棧(Native Method Stacks)冀膝。

3|3程序計數(shù)器

? 程序計數(shù)器(Program Counter Register)是一塊較小的內(nèi)存空間,它可以看作是當(dāng)前線程所執(zhí)行的字節(jié)碼的行號指示器霎挟。字節(jié)碼解釋器工作時就是通過改變這個計數(shù)器的值來選取下一條需要執(zhí)行的字節(jié)碼指令窝剖,它是程序控制流的指示器,分支酥夭、循環(huán)赐纱、跳轉(zhuǎn)、異常處理采郎、線程恢復(fù)等基礎(chǔ)功能都需要依賴這個計數(shù)器來完成千所。

? 由于Java虛擬機的多線程是通過線程輪流切換狂魔、分配處理器執(zhí)行時間的方式來實現(xiàn)的蒜埋,在任何一個確定的時刻,一個處理器(對于多核處理器來說是一個內(nèi)核)都只會執(zhí)行一條線程中的指令最楷。因此整份,為了線程切換后能恢復(fù)到正確的執(zhí)行位置,每條線程都需要有一個獨立的程序計數(shù)器籽孙,各條線程之間計數(shù)器互不影響烈评,獨立存儲,我們稱這類內(nèi)存區(qū)域為“線程私有”的內(nèi)存犯建。

3|4java虛擬機棧

? 與程序計數(shù)器一樣讲冠,Java虛擬機棧也是線程私有的,它的生命周期與線程相同适瓦。Java虛擬機棧描述的是Java方法執(zhí)行的線程內(nèi)存模型:每個方法被執(zhí)行的時候竿开,Java虛擬機都會同步創(chuàng)建一個棧幀谱仪,用于存儲局部變量表、操作數(shù)棧否彩、動態(tài)連接疯攒、方法出口等信息。每一個方法被調(diào)用直至執(zhí)行完畢的過程列荔,就對應(yīng)著一個棧幀在虛擬機棧中從入棧到出棧的過程敬尺。

局部變量表

局部變量表是一組變量值的存儲空間,用于存放方法參數(shù)和方法內(nèi)部定義的局部變量贴浙。

在Class文件中砂吞,方法的Code屬性的max_locals數(shù)據(jù)項中確定了該方法所需分配的局部變量表的最大容量。

該表以變量槽(Variable Slot)為最小單位崎溃,一個slot可以存放32位以內(nèi)的數(shù)據(jù)呜舒,比如:boolean、byte笨奠、

char袭蝗、short、int般婆、float等數(shù)據(jù)到腥,如果存儲long、double類型數(shù)據(jù)蔚袍,需要占用2個solt乡范。

虛擬機通過索引定位的方式使用局部變量表,索引值的范圍是從0開始至局部變量表最大的變量槽數(shù)量啤咽。

如果訪問的是32位數(shù)據(jù)類型的變量晋辆,索引N就代表了使用第N個變量槽,如果訪問的是64位數(shù)據(jù)類型的變量宇整,則說明會同時使用第N和N+1兩個變量槽瓶佳。

局部變量表中第0位索引的變量槽默認(rèn)是用于傳遞方法所屬對象實例的引用,在方法中可以通過關(guān)鍵字“this”來訪問到這個隱含的參數(shù)鳞青。其余參數(shù)則按照參數(shù)表順序排列霸饲,占用從1開始的局部變量槽,參數(shù)表分配完畢后臂拓,再根據(jù)方法體內(nèi)部定義的變量順序和作用域分配其余的變量槽厚脉。

操作數(shù)棧

操作數(shù)棧也常被稱為操作棧,它是一個先進后出棧胶惰。

操作數(shù)棧的最大深度也在編譯的時候被寫入到Code屬性的max_stacks數(shù)據(jù)項之中傻工。

操作數(shù)棧的每一個元素都可以是包括long和double在內(nèi)的任意Java數(shù)據(jù)類型。32位數(shù)據(jù)類型所占的棧容量為1,64位數(shù)據(jù)類型所占的棧容量為2中捆。

方法剛剛開始執(zhí)行的時候威鹿,這個方法的操作數(shù)棧是空的,在方法的執(zhí)行過程中轨香,會有各種字節(jié)碼指令往操作數(shù)棧中寫入和提取內(nèi)容忽你,也就是出棧和入棧操作。

操作數(shù)棧中元素的數(shù)據(jù)類型必須與字節(jié)碼指令的序列嚴(yán)格匹配臂容,例如iadd指令科雳,不能出現(xiàn)一個long和一個float使用iadd命令相加的情況。

動態(tài)連接

每個棧幀都包含一個指向運行時常量池中該棧幀所屬方法的引用脓杉,持有這個引用是為了支持方法調(diào)用過程中

的動態(tài)連接糟秘。

Class文件的常量池中存有大量的符號引用,字節(jié)碼中的方法調(diào)用指令就以常量池里指向方法的符號引用作為

參數(shù)球散。這些符號引用一部分會在類加載階段或者第一次使用的時候就被轉(zhuǎn)化為直接引用尿赚,這種轉(zhuǎn)化被稱為靜

態(tài)解析。另外一部分將在每一次運行期間都轉(zhuǎn)化為直接引用蕉堰,這部分就稱為動態(tài)連接凌净。

方法出口

當(dāng)一個方法開始執(zhí)行后,只有兩種方式退出這個方法屋讶。

第一種方式是執(zhí)行引擎遇到任意一個方法返回的字節(jié)碼指令冰寻,這時候可能會有返回值傳遞給上層的方法調(diào)用

者,方法是否有返回值以及返回值的類型將根據(jù)遇到何種方法返回指令來決定皿渗,這種退出方法的方式稱為“正

常調(diào)用完成”斩芭。

另外一種退出方式是在方法執(zhí)行的過程中遇到了異常,并且這個異常沒有在方法體內(nèi)得到妥善處理乐疆。無論是

Java虛擬機內(nèi)部產(chǎn)生的異常划乖,還是代碼中使用throw字節(jié)碼指令產(chǎn)生的異常,只要在本方法的異常表中沒有搜

索到匹配的異常處理器挤土,就會導(dǎo)致方法退出琴庵,這種退出方法的方式稱為“異常調(diào)用完成”。這種方法的返回是不

會給它的上層調(diào)用者提供任何返回值的耕挨。

無論采用何種退出方式细卧,在方法退出之后尉桩,都必須返回到最初方法被調(diào)用時的位置筒占,程序才能繼續(xù)執(zhí)行,方

法返回時可能需要在棧幀中保存一些信息蜘犁,用來幫助恢復(fù)它的上層主調(diào)方法的執(zhí)行狀態(tài)翰苫。

方法退出的過程實際上等同于把當(dāng)前棧幀出棧,因此退出時可能執(zhí)行的操作有:恢復(fù)上層方法的局部變量表

和操作數(shù)棧,把返回值(如果有的話)壓入調(diào)用者棧幀的操作數(shù)棧中奏窑,調(diào)整PC計數(shù)器的值以指向方法調(diào)用指

令后面的一條指令等导披。

圖解

? 以 int i = 1; 這樣代碼為例,看看虛擬機棧的執(zhí)行

3|5本地方法棧

? 本地方法棧(Native Method Stacks)與虛擬機棧所發(fā)揮的作用是非常相似的埃唯,其區(qū)別只是虛擬機棧為虛擬機執(zhí)行Java方法(也就是字節(jié)碼)服務(wù)撩匕,而本地方法棧則是為虛擬機使用到的本地(Native)方法服務(wù)。

3|6Java堆區(qū)

?Java堆是被所有線程共享的一塊內(nèi)存區(qū)域墨叛,在虛擬機啟動時創(chuàng)建止毕。此內(nèi)存區(qū)域的唯一目的就是存放對象實例,Java世界里“幾乎”所有的對象實例都在這里分配內(nèi)存漠趁。

?需要注意的是扁凛,《Java虛擬機規(guī)范》并沒有對堆進行細(xì)致的劃分,所以對于堆的講解要基于具體的虛擬機闯传,我們以使用最多的HotSpot虛擬機為例進行講解谨朝。

?Java堆是垃圾收集器管理的內(nèi)存區(qū)域,因此它也被稱作“GC堆”甥绿,這就是我們做JVM調(diào)優(yōu)的重點區(qū)域部分字币。

jdk1.7中堆內(nèi)存的劃分

Young 年輕區(qū)(代)

Young區(qū)被劃分為三部分,Eden區(qū)和兩個大小嚴(yán)格相同的Survivor區(qū)共缕,其中纬朝,Survivor區(qū)間中,某一時刻只有其中一個是被使用的骄呼,另外一個留做垃圾收集時復(fù)制對象用共苛,在Eden區(qū)間變滿的時候,GC就會將存活的對象移到空閑的Survivor區(qū)間中蜓萄,根據(jù)JVM的策略隅茎,在經(jīng)過幾次垃圾收集后,任然存活于Survivor的對象將被移動到Tenured區(qū)間嫉沽。

Tenured 年老區(qū)

Tenured區(qū)主要保存生命周期長的對象辟犀,一般是一些老的對象,當(dāng)一些對象在Young復(fù)制轉(zhuǎn)移一定的次數(shù)以后绸硕,對象就會被轉(zhuǎn)移到Tenured區(qū)堂竟,一般如果系統(tǒng)中用了application級別的緩存,緩存中的對象往往會被轉(zhuǎn)移到這一區(qū)間玻佩。

Perm 永久區(qū)

Perm代主要保存class,method,filed對象出嘹,這部份的空間一般不會溢出,除非一次性加載了很多的類咬崔,不過在涉及到熱部署的應(yīng)用服務(wù)器的時候税稼,有時候會遇到j(luò)ava.lang. OutOfMemoryError : PermGen space 的誤烦秩,造成這個錯誤的很大原因就有可能是每次都重新部署,但是重新部署后郎仆,類的class沒有被卸載掉只祠,這樣就造成了大量的class對象保存在了perm中,這種情況下扰肌,一般重新啟動應(yīng)用服務(wù)器可以解決問題抛寝。

Virtual區(qū):

最大內(nèi)存和初始內(nèi)存的差值,就是Virtual區(qū)曙旭。

jdk1.8中堆內(nèi)存的劃分

由上圖可以看出墩剖,jdk1.8的內(nèi)存模型是由2部分組成,年輕代+ 年老代夷狰。

年輕代:Eden + 2*Survivor

年老代:OldGen

在jdk1.8中變化最大的Perm區(qū)岭皂,用Metaspace(元數(shù)據(jù)空間)進行了替換。

需要特別說明的是:Metaspace所占用的內(nèi)存空間不是在虛擬機內(nèi)部沼头,而是在本地內(nèi)存空間中爷绘,這也是與1.7的永

久代最大的區(qū)別所在。

空間分配

如果沒有指定堆內(nèi)存大小进倍,默認(rèn)初始堆內(nèi)存為物理內(nèi)存的1/64土至,最大不超過物理內(nèi)存的1/4或1G。注意的是元空間會自動擴容猾昆,默認(rèn)情況下不收限制陶因。

為什么廢棄1.7中的永久區(qū)

官方給出的解釋是:移除永久代是為融合HotSpot JVM與 JRockit VM而做出的努力,因為JRockit沒有永久代垂蜗,不需要配置永久代楷扬。

3|7方法區(qū)

方法區(qū)(Method Area)與Java堆一樣,是各個線程共享的內(nèi)存區(qū)域贴见,它用于存儲已被虛擬機加載的類信息烘苹、

常量、靜態(tài)變量片部、即時編譯器編譯后的代碼緩存等數(shù)據(jù)镣衡。

《Java虛擬機規(guī)范》中把方法區(qū)描述為堆的一個邏輯部分,它卻有一個別名叫作“非堆”(Non-Heap)档悠,目的

是與Java堆區(qū)分開來廊鸥。

JDK8之前將HotSpot虛擬機把收集器的分代設(shè)計擴展至方法區(qū),所以可以將永久代看做是方法區(qū)辖所,JDK8之后

廢棄永久代惰说,用元空間來代替。

3|8對象的訪問

Java程序會通過棧上的reference數(shù)據(jù)來操作堆上的具體對象奴烙。

主流的訪問方式主要有使用句柄和直接指針兩種:

句柄訪問

Java堆中將可能會劃分出一塊內(nèi)存來作為句柄池助被,reference中存儲的就是對象的句柄地址剖张,而句柄中包含了對象實例數(shù)據(jù)與類型數(shù)據(jù)各自具體的地址信息.使用直接指針訪問Java堆中對象的內(nèi)存布局就必須考慮如何放置訪問類型數(shù)據(jù)的相關(guān)信息切诀,reference中存儲的直接就是對象地址揩环,如果只是訪問對象本身的話,就不需要多一次間接訪問的開銷

指針訪問

使用句柄來訪問的最大好處就是reference中存儲的是穩(wěn)定句柄地址幅虑,在對象被移動(垃圾收集時移動對象是非常普遍的行為)時只會改變句柄中的實例數(shù)據(jù)指針丰滑,而reference本身不需要被修改。使用直接指針來訪問最大的好處就是速度更快倒庵,它節(jié)省了一次指針定位的時間開銷褒墨。HotSpot虛擬機采用的是指針訪問方式實現(xiàn)。

《 想要jvm的資料的戳這里》

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末擎宝,一起剝皮案震驚了整個濱河市郁妈,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌绍申,老刑警劉巖噩咪,帶你破解...
    沈念sama閱讀 217,907評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異极阅,居然都是意外死亡胃碾,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,987評論 3 395
  • 文/潘曉璐 我一進店門筋搏,熙熙樓的掌柜王于貴愁眉苦臉地迎上來仆百,“玉大人,你說我怎么就攤上這事奔脐《碇埽” “怎么了?”我有些...
    開封第一講書人閱讀 164,298評論 0 354
  • 文/不壞的土叔 我叫張陵髓迎,是天一觀的道長栈源。 經(jīng)常有香客問我,道長竖般,這世上最難降的妖魔是什么甚垦? 我笑而不...
    開封第一講書人閱讀 58,586評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮涣雕,結(jié)果婚禮上艰亮,老公的妹妹穿的比我還像新娘。我一直安慰自己挣郭,他們只是感情好迄埃,可當(dāng)我...
    茶點故事閱讀 67,633評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著兑障,像睡著了一般侄非。 火紅的嫁衣襯著肌膚如雪蕉汪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,488評論 1 302
  • 那天逞怨,我揣著相機與錄音者疤,去河邊找鬼。 笑死叠赦,一個胖子當(dāng)著我的面吹牛驹马,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播除秀,決...
    沈念sama閱讀 40,275評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼糯累,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了册踩?” 一聲冷哼從身側(cè)響起泳姐,我...
    開封第一講書人閱讀 39,176評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎暂吉,沒想到半個月后胖秒,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,619評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡借笙,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,819評論 3 336
  • 正文 我和宋清朗相戀三年扒怖,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片业稼。...
    茶點故事閱讀 39,932評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡盗痒,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出低散,到底是詐尸還是另有隱情俯邓,我是刑警寧澤,帶...
    沈念sama閱讀 35,655評論 5 346
  • 正文 年R本政府宣布熔号,位于F島的核電站稽鞭,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏引镊。R本人自食惡果不足惜朦蕴,卻給世界環(huán)境...
    茶點故事閱讀 41,265評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望弟头。 院中可真熱鬧吩抓,春花似錦、人聲如沸赴恨。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,871評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽伦连。三九已至雨饺,卻和暖如春钳垮,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背额港。 一陣腳步聲響...
    開封第一講書人閱讀 32,994評論 1 269
  • 我被黑心中介騙來泰國打工饺窿, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人锹安。 一個月前我還...
    沈念sama閱讀 48,095評論 3 370
  • 正文 我出身青樓短荐,卻偏偏與公主長得像倚舀,于是被迫代替她去往敵國和親叹哭。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,884評論 2 354