JVM内存的那些事,你了解多少?
前言
對(duì)于C語(yǔ)言開(kāi)發(fā)的程序員來(lái)說(shuō),在內(nèi)存管理方面,必須負(fù)責(zé)每一個(gè)對(duì)象的生命周期,從有到無(wú)。
對(duì)于Java程序員你來(lái)說(shuō),在虛擬機(jī)內(nèi)存管理的幫助下,不需要為每個(gè)new對(duì)象都匹配free操作,內(nèi)存泄露和內(nèi)存溢出等問(wèn)題也不太容易出現(xiàn),不過(guò)也正是因?yàn)榘褍?nèi)存管理交給了虛擬機(jī),一旦運(yùn)行中的程序出現(xiàn)了內(nèi)存泄露問(wèn)題,給排查過(guò)程造成很大困難。所以只有理解了Java虛擬機(jī)的運(yùn)行機(jī)制,才能夠運(yùn)籌帷幄于各種代碼。本文以HotSpot為例說(shuō)說(shuō)虛擬機(jī)的那些事。
JAVA虛擬機(jī)把管理的內(nèi)存劃分為幾個(gè)不同的數(shù)據(jù)區(qū)。
Java堆
Java堆是被所有線程共享的一塊內(nèi)存區(qū)域,主要用于存放對(duì)象實(shí)例,Java虛擬機(jī)規(guī)范中有這樣一段描述:所有的對(duì)象實(shí)例和數(shù)據(jù)都要在堆上進(jìn)行分配。為對(duì)象分配內(nèi)存就是把一塊大小確定的內(nèi)存從堆內(nèi)存中劃分出來(lái),通常有兩種方法實(shí)現(xiàn):
1 、指針碰撞法
假設(shè)Java堆中內(nèi)存時(shí)完整的,已分配的內(nèi)存和空閑內(nèi)存分別在不同的一側(cè),通過(guò)一個(gè)指針作為分界點(diǎn),需要分配內(nèi)存時(shí),僅僅需要把指針往空閑的一端移動(dòng)與對(duì)象大小相等的距離。
2、空閑列表法
事實(shí)上,Java堆的內(nèi)存并不是完整的,已分配的內(nèi)存和空閑內(nèi)存相互交錯(cuò),JVM通過(guò)維護(hù)一個(gè)列表,記錄可用的內(nèi)存塊信息,當(dāng)分配操作發(fā)生時(shí),從列表中找到一個(gè)足夠大的內(nèi)存塊分配給對(duì)象實(shí)例,并更新列表上的記錄。
對(duì)象創(chuàng)建是一個(gè)非常頻繁的行為,進(jìn)行堆內(nèi)存分配時(shí)還需要考慮多線程并發(fā)問(wèn)題,可能出現(xiàn)正在給對(duì)象A分配內(nèi)存,指針或記錄還未更新,對(duì)象B又同時(shí)分配到原來(lái)的內(nèi)存,解決這個(gè)問(wèn)題有兩種方案:
1、采用CAS保證數(shù)據(jù)更新操作的原子性; 2、把內(nèi)存分配的行為按照線程進(jìn)行劃分,在不同的空間中進(jìn)行,每個(gè)線程在Java堆中預(yù)先分配一個(gè)內(nèi)存塊,稱為本地線程分配緩沖(Thread Local Allocation Buffer, TLAB);
Java棧
Java棧是線程私有的,每個(gè)線程對(duì)應(yīng)一個(gè)Java棧,每個(gè)線程在執(zhí)行一個(gè)方法時(shí)會(huì)創(chuàng)建一個(gè)對(duì)應(yīng)的棧幀(Stack Frame),棧幀負(fù)責(zé)存儲(chǔ)局部變量變量表、操作數(shù)棧、動(dòng)態(tài)鏈接和方法返回地址等信息。每個(gè)方法的調(diào)用過(guò)程,相當(dāng)于棧幀在Java棧的入棧和出棧過(guò)程。
局部變量表 用于存放方法參數(shù)和方法內(nèi)部定義的局部變量,其大小在代碼編譯期間已經(jīng)確定,在方法運(yùn)行期間不會(huì)改變。局部變量表以變量槽(Slot)為最小存儲(chǔ)單位,每個(gè)Slot能夠存放一個(gè)boolean、byte、char、shot、int、float、reference和returnAddress類型的32位數(shù)據(jù),對(duì)于64位的數(shù)據(jù)類型long和double,虛擬機(jī)會(huì)以高位對(duì)齊的方式為其分配兩個(gè)連續(xù)的Slot空間。
在方法執(zhí)行時(shí),如果是實(shí)例方法,即非static方法,局部變量表中第0位Slot默認(rèn)存放對(duì)象實(shí)例的引用,在方法中可以通過(guò)關(guān)鍵字 this 進(jìn)行訪問(wèn),方法參數(shù)按照參數(shù)列表順序,從第1位Slot開(kāi)始分配,方法內(nèi)部變量則按照定義順序進(jìn)行分配其余的Slot。
classtest{publicintcalc(inta,intb, String operation){? ? ? ? operation ="+";returna + b;? ? }publicvoidmain(String args[]){? ? ? ? calc(100,200,"+");? ? }}
對(duì)應(yīng)的局部變量表如下:
使用 javap -c 命令查看方法calc的字節(jié)碼
其中iload1和iload2分別從局部變量表中的第1位和第2位中加載數(shù)據(jù)。
方法區(qū)
方法區(qū)和Java堆一樣,是所有線程共享的內(nèi)存區(qū)域,用于存放已被虛擬機(jī)加載的類信息、常量、靜態(tài)變量和即時(shí)編譯器編譯后的代碼等數(shù)據(jù)。
運(yùn)行時(shí)常量池是方法區(qū)的一部分,用于存放編譯期間生成的各種字面常量和符號(hào)引用。
指令計(jì)數(shù)器
指令計(jì)數(shù)器是線程私有的,每個(gè)線程都有獨(dú)立的指令計(jì)數(shù)器,計(jì)數(shù)器記錄著虛擬機(jī)正在執(zhí)行的字節(jié)碼指令的地址,分支、循環(huán)、跳轉(zhuǎn)、異常處理和線程恢復(fù)等操作都依賴這個(gè)計(jì)數(shù)器完成。如果線程執(zhí)行的是native方法,這個(gè)計(jì)數(shù)器則為空。
對(duì)象的內(nèi)存布局
對(duì)象在內(nèi)存中布局可以分成三塊區(qū)域:對(duì)象頭、實(shí)例數(shù)據(jù)和對(duì)齊填充。
1、對(duì)象頭
對(duì)象頭包括兩部分信息:運(yùn)行時(shí)數(shù)據(jù)和類型指針,如果對(duì)象是一個(gè)數(shù)組,還需要一塊用于記錄數(shù)組長(zhǎng)度的數(shù)據(jù)。
1.1、運(yùn)行時(shí)數(shù)據(jù)包括哈希碼(HashCode)、GC分代年齡、鎖狀態(tài)標(biāo)志、線程持有的鎖、偏向鎖ID和偏向時(shí)間戳等,這部分?jǐn)?shù)據(jù)在32位和64位虛擬機(jī)中的長(zhǎng)度分別為32bit和64bit,官方稱為”Mark Word”。Mark Word被設(shè)計(jì)成非固定的數(shù)據(jù)結(jié)構(gòu),以實(shí)現(xiàn)在有限空間內(nèi)保存盡可能多的數(shù)據(jù)。
32位的虛擬機(jī)中,對(duì)象未被鎖定的狀態(tài)下,Mark Word的32bit中25bit存儲(chǔ)對(duì)象的HashCode、4bit存儲(chǔ)對(duì)象分代年齡、2bit存儲(chǔ)鎖標(biāo)志位、1bit固定為0,具體如下:
其它狀態(tài)(輕量級(jí)鎖定、重量級(jí)鎖定、GC鎖定、可偏向鎖)下Mark Word的存儲(chǔ)內(nèi)容如下:
1.2、對(duì)象頭的類型指針指向該對(duì)象的類元數(shù)據(jù),虛擬機(jī)通過(guò)這個(gè)指針可以確定該對(duì)象是哪個(gè)類的實(shí)例。
2、實(shí)例數(shù)據(jù)
實(shí)例數(shù)據(jù)就是在程序代碼中所定義的各種類型的字段,包括從父類繼承的,這部分的存儲(chǔ)順序會(huì)受到虛擬機(jī)分配策略和字段在源碼中定義順序的影響。
3、對(duì)齊填充
由于HotSpot的自動(dòng)內(nèi)存管理要求對(duì)象的起始地址必須是8字節(jié)的整數(shù)倍,即對(duì)象的大小必須是8字節(jié)的整數(shù)倍,對(duì)象頭的數(shù)據(jù)正好是8的整數(shù)倍,所以當(dāng)實(shí)例數(shù)據(jù)不夠8字節(jié)整數(shù)倍時(shí),需要通過(guò)對(duì)齊填充進(jìn)行補(bǔ)全。 在互聯(lián)網(wǎng)公司面試中,架構(gòu)的底層一定是面試官會(huì)問(wèn)到的問(wèn)題,針對(duì)面試官一般會(huì)提到的問(wèn)題,我錄制了一些分布式,微服務(wù),性能優(yōu)化等技術(shù)點(diǎn)底層原理的錄像視頻,加群705194503 ?可以免費(fèi)獲取這些錄像,里面還有些分布式,微服務(wù),性能優(yōu)化,Spring,MyBatis的等源碼知識(shí)點(diǎn)的錄像視頻。這些視頻都是我找一些資深架構(gòu)師朋友一起錄制出來(lái)的,這些視頻幫助以下幾類程序員:
1.對(duì)現(xiàn)在的薪資不滿,想要跳槽,卻對(duì)自己的技術(shù)沒(méi)有信心,不知道如何面對(duì)面試官。
2.想從傳統(tǒng)行業(yè)轉(zhuǎn)行到互聯(lián)網(wǎng)行業(yè),但沒(méi)有接觸過(guò)互聯(lián)網(wǎng)技術(shù)。
3.工作1 - 5年需要提升自己的核心競(jìng)爭(zhēng)力,但學(xué)習(xí)沒(méi)有系統(tǒng)化,不知道自己接下來(lái)要學(xué)什么才是正確的,踩坑后又不知道找誰(shuí),百度后依然不知所以然。
4.工作5 - 10年無(wú)法突破技術(shù)瓶頸(運(yùn)用過(guò)很多技術(shù),在公司一直寫(xiě)著業(yè)務(wù)代碼,卻依然不懂底層實(shí)現(xiàn)原理)
如果你現(xiàn)在正處于我上述所說(shuō)的幾個(gè)階段可以加下我的群來(lái)學(xué)習(xí)。而且我也能夠提供一些面試指導(dǎo),職業(yè)規(guī)劃等建議。
轉(zhuǎn)載于:https://www.cnblogs.com/Java3272858604/p/9100361.html
總結(jié)
以上是生活随笔為你收集整理的JVM内存的那些事,你了解多少?的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: PetClinic 没有分页功能
- 下一篇: 应用前台省电秘籍——这些常见功耗雷坑不要