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