Java虛擬機(jī)
Oracle有兩款實(shí)現(xiàn)了 Java SE
的產(chǎn)品:Java SE Development Kit(JDK)
和 Java SE Runtime Environment(JRE)
瑞侮。
JDK
是 JRE
的超集,包含了 JRE
的所有內(nèi)容明未,以及開發(fā)應(yīng)用程序所需的編譯器和調(diào)試器等工具弄跌。 JRE
提供了函數(shù)庫峻呛、Java Virtual Machine(JVM)
和其它用來運(yùn)行Java應(yīng)用程序的組件畔规。
Java內(nèi)存區(qū)域
程序計(jì)數(shù)器
程序計(jì)數(shù)器是一塊較小的內(nèi)存空間鲫寄,他的作用可以看做是當(dāng)前線程所執(zhí)行的字節(jié)碼的行號指示器瓜喇。由于 JVM
的多線程是通過線程輪流切換并分配處理器執(zhí)行時(shí)間來實(shí)現(xiàn)的,因此每個(gè)線程都有一個(gè)獨(dú)立的程序計(jì)數(shù)器乒融,用于線程切換后能恢復(fù)到正確的執(zhí)行位置掰盘。如果線程執(zhí)行的是Java方法摄悯,這個(gè)計(jì)數(shù)器記錄的是正在執(zhí)行的虛擬機(jī)字節(jié)碼指令的地址赞季。如果正在執(zhí)行的是 Native方法
,這個(gè)計(jì)數(shù)器的值為 undefined
奢驯。此區(qū)域是唯一一個(gè)在Java虛擬機(jī)規(guī)范中沒有規(guī)定 OutOfMemoryError
情況的區(qū)域申钩。
Java虛擬機(jī)棧
- 虛擬機(jī)棧描述的是Java方法執(zhí)行的動態(tài)內(nèi)存模型,調(diào)用方法即創(chuàng)建棧幀并入棧瘪阁,方法執(zhí)行完畢棧幀出棧撒遣。
- 棧幀:每個(gè)方法執(zhí)行,都會創(chuàng)建一個(gè)棧幀管跺,用于存儲局部變量表义黎,操作數(shù)棧,動態(tài)鏈接豁跑,方法出口等廉涕。每一個(gè)方法從調(diào)用到執(zhí)行完成的過程,就是一個(gè)棧幀在虛擬機(jī)棧中入棧到出棧的過程艇拍。
- 局部變量表:存放編譯器可知的各種基本數(shù)據(jù)類型狐蜕,引用類型,returnAddress類型卸夕。對象是在堆內(nèi)存中創(chuàng)建的层释,局部變量表存放的是對象的引用,其大小是不會改變的贡羔。因此局部變量表的內(nèi)存空間在編譯期完成分配后廉白,方法需要在幀分配多少內(nèi)存是固定的乖寒。
- 虛擬機(jī)棧異常:如果線程請求的棧深度超過虛擬機(jī)允許的深度,將拋出
StackOverFlowError
異常宵统;如果虛擬機(jī)棧允許擴(kuò)展,在擴(kuò)展時(shí)無法申請到足夠的內(nèi)存马澈,就會拋出OutOfMemoryError
異常。
本地方法棧
Java虛擬機(jī)棧為虛擬機(jī)執(zhí)行Java方法服務(wù)痊班,本地方法棧則為虛擬機(jī)使用到的 Native方法
服務(wù)。在 Hotspot虛擬機(jī)
的實(shí)現(xiàn)中是把本地方法棧和虛擬機(jī)棧合二為一的涤伐。與虛擬機(jī)棧一樣馒胆,本地方法棧也會拋出 StackOverFlowError
異常和 OutOfMemoryError
異常。
Java堆
堆是線程共享的數(shù)據(jù)運(yùn)行時(shí)區(qū)域凝果,幾乎所有的對象實(shí)例以及數(shù)組都要在堆上分配內(nèi)存祝迂。堆是垃圾收集器管理的主要區(qū)域。Java堆可以處于物理上不連續(xù)的內(nèi)存空間中器净。如果在堆中沒有內(nèi)存完成實(shí)例分配型雳,并且堆也無法再擴(kuò)展時(shí),將會拋出 OutOfMemoryError
異常山害。
方法區(qū)
方法區(qū)存儲虛擬機(jī)加載的類信息(類的版本纠俭、字段、方法浪慌、接口)冤荆、常量、靜態(tài)變量权纤、即時(shí)編譯器編譯后的代碼等數(shù)據(jù)钓简。于 Hotspot虛擬機(jī)
來說,將方法區(qū)納入GC管理范圍妖碉,這樣就不必單獨(dú)管理方法區(qū)的內(nèi)存涌庭,所以就有了相對于新生代和老年代的永久代一說。
運(yùn)行時(shí)常量池
運(yùn)行時(shí)常量池(JDK6在方法區(qū)欧宜,JDK7在Java堆)用來存放編譯器生成的各種字面量以及符號引用(類加載之后進(jìn)入運(yùn)行時(shí)常量池)坐榆。運(yùn)行期間也能將新的常量放入池中。當(dāng)常量池?zé)o法再申請到內(nèi)存時(shí)冗茸,將會拋出 OutOfMemoryError
異常席镀。
直接內(nèi)存
Java NIO
使用 Native函數(shù)庫
直接分配堆外內(nèi)存匹中,然后通過一個(gè)存儲在Java堆中的 DirectByteBuffer
對象作為這塊內(nèi)存的引用進(jìn)行操作。通過避免在 Java堆
和 Native堆
中來回復(fù)制數(shù)據(jù)來提高性能豪诲。直接內(nèi)存大小不受虛擬機(jī)參數(shù)控制顶捷,如果各個(gè)內(nèi)存區(qū)域總和大于物理內(nèi)存限制,就會出現(xiàn) OutOfMemoryError
異常屎篱。
對象
對象的創(chuàng)建
內(nèi)存分配策略
根據(jù) Java 堆
是否規(guī)整可以判斷使用哪種內(nèi)存分配策略服赎。
- 指針碰撞:堆內(nèi)存中的空閑空間十分的規(guī)整,使用與未使用的空間全部為連續(xù)重虑,分配內(nèi)存只需移動指針秦士。
- 空閑列表:針對堆內(nèi)存中的空間零散的存在,虛擬機(jī)維護(hù)著一個(gè)列表隧土,記錄那些內(nèi)存未使用曹傀。
線程安全性
對象創(chuàng)建在虛擬機(jī)中是十分頻繁的行為,在并發(fā)環(huán)境下需要考慮線程安全揖曾。
-
CAS失敗重試
:通過樂觀鎖實(shí)現(xiàn)線程安全亥啦。 -
TLAB(Thread Local Allocation Buffer)
:本地線程分配緩沖练链,內(nèi)存為每個(gè)線程分配一個(gè)TLAB區(qū)域
,每個(gè)線程要?jiǎng)?chuàng)建對象時(shí)先在這個(gè)區(qū)域中創(chuàng)建届吁,當(dāng)原來的空間不足時(shí)再通過線程同步獲取一塊新的區(qū)域绿鸣。
對象設(shè)置
將對象的哈希嗎、GC年齡信息等存放在對象頭中亮蛔,執(zhí)行 <init>
方法擎厢。
對象的結(jié)構(gòu)
對象頭(Header)
- 自身運(yùn)行時(shí)數(shù)據(jù):哈希碼、GC分代年齡芬探、鎖狀態(tài)標(biāo)志、線程持有的鎖哩簿、偏向線程ID酝静、偏向時(shí)間戳等。
- 類型指針:對象指向它的類的元數(shù)據(jù)的指針全跨,虛擬機(jī)通過這個(gè)指針來確定這個(gè)對象是哪個(gè)類的實(shí)例亿遂。如果對象是一個(gè)數(shù)組,對象頭中還必須有一塊記錄數(shù)組長度的數(shù)據(jù)挪钓。
實(shí)例數(shù)據(jù)(Instance Data)
實(shí)例數(shù)據(jù)是對象真正存儲的有效信息耳舅,也是程序代碼中所定義的各種類型的字段內(nèi)容。
對齊填充(Padding)
HotSpot虛擬機(jī)
要求對象的起始地址必須是8字節(jié)的整數(shù)倍馏予,也就是對象的大小必須是8字節(jié)的整數(shù)倍盔性。而對象頭部分正好是8字節(jié)的倍數(shù)(1倍或者2倍),因此蛹尝,當(dāng)對象實(shí)例數(shù)據(jù)部分沒有對齊的時(shí)候突那,就需要通過對齊填充來補(bǔ)全构眯。
對象的訪問定位
Java程序通過棧上的 reference
數(shù)據(jù)來操作堆上的具體對象,由于 reference
類型在Java虛擬機(jī)規(guī)范中只規(guī)定了一個(gè)指向?qū)ο蟮囊梦皲觯唧w用何種方式去定位、引用堆中對象的具體位置翘悉,取決于虛擬機(jī)的實(shí)現(xiàn)居触,目前主要有 使用句柄
和 直接指針
兩種方式。
- 使用句柄:引用類型指向堆中一塊區(qū)域(句柄池)制市,此區(qū)域保存了實(shí)例對象的地址(對象被移動時(shí)維護(hù)句柄中的指針數(shù)據(jù)弊予,無需改變
reference
本身)。 - 直接指針:從引用類型直接指向內(nèi)存區(qū)域(速度更快误褪,節(jié)省了一次指針定位帶來的開銷)碾褂。
垃圾回收
在Java堆上分配一個(gè)內(nèi)存給實(shí)例對象時(shí),此時(shí)在虛擬機(jī)棧上引用型變量就會存放這個(gè)實(shí)例對象的起始地址嘀略。當(dāng)線程銷毀后乓诽,其在虛擬機(jī)棧上的內(nèi)存自然會被回收,也就是說虛擬機(jī)棧上的這塊內(nèi)存不在虛擬機(jī)GC范圍內(nèi)逮壁。
垃圾對象判定算法
- 引用計(jì)數(shù)法:在對象中添加一個(gè)引用計(jì)數(shù)器粮宛,當(dāng)有地方引用這個(gè)對象時(shí)巍杈,引用計(jì)數(shù)器的值+1扛伍,當(dāng)引用失效時(shí),計(jì)數(shù)器的值-1鳖宾。垃圾回收器遇到計(jì)數(shù)器為0的對象時(shí)就會回收。但是當(dāng)堆內(nèi)存中的對象相互引用渔肩,而外部不存在對這些對象的引用時(shí)拇惋,計(jì)數(shù)器值不為0,無法判定回收蓉坎。
- 可達(dá)性分析法:通過一系列名為
GC Roots
的對象(虛擬機(jī)棧蛉艾、方法區(qū)類屬性所引用的對象衷敌、方法區(qū)中常量所引用的對象、本地方法棧中引用的對象)作為起始點(diǎn)罐监,從這些節(jié)點(diǎn)開始向下搜索瞒爬,搜索所走過的路徑稱為引用鏈(Reference Chain)
,當(dāng)一個(gè)對象到GC Roots沒有任何引用鏈相連時(shí)矢空,則此對象證明是不可用的禀横,將被判定為可回收對象。
Java引用
- 強(qiáng)引用:類似
Obejct obj = new Object()
酿箭,只要強(qiáng)引用還在趾娃,垃圾收集器就永遠(yuǎn)不會收集被引用的對象。 - 軟引用:還有用但并非必須的對象妇蛀。在系統(tǒng)發(fā)生內(nèi)存溢出之前,會將軟引用關(guān)聯(lián)的對象進(jìn)行回收评架,如果回收后還沒有足夠的內(nèi)存,才會拋出內(nèi)存異常上祈。
- 弱引用:垃圾收集器工作時(shí)挣磨,無論當(dāng)前內(nèi)存是否足夠,都會回收被弱引用關(guān)聯(lián)的對象塘砸。
- 虛引用:一個(gè)對象是否有虛引用的存在晤锥,完全不會對其生產(chǎn)時(shí)間構(gòu)成影響。也無法通過虛引用來取得一個(gè)對象實(shí)例女轿。為一個(gè)對象設(shè)置虛引用關(guān)聯(lián)的唯一目的是能在這個(gè)對象被垃圾收集時(shí)收到一個(gè)系統(tǒng)通知壕翩。
finalize()
將對象回收至少要經(jīng)歷兩次標(biāo)記過程,如果在可達(dá)性分析中發(fā)現(xiàn)對象沒有與 GC Roots
的引用鏈北救,那它將會被第一次標(biāo)記并被進(jìn)行一次篩選珍策,篩選的條件是此對象是否有必要執(zhí)行 finalize()
方法(當(dāng)前對象沒有覆蓋此方法或者已經(jīng)執(zhí)行過此方法宅倒,則虛擬機(jī)認(rèn)為“沒有必要執(zhí)行”),虛擬機(jī)不會承諾等待此方法執(zhí)行結(jié)束蹭劈。如果在 finalize()
方法中成功與引用鏈上的人一個(gè)對象建立關(guān)聯(lián)唠亚,則對象不會被回收。
如何回收
回收策略
- 標(biāo)記-清除算法:標(biāo)記可達(dá)對象,在清除階段回收并沒有被標(biāo)記為可達(dá)的對象所占用的內(nèi)存空間,并將原來的可達(dá)標(biāo)記刪除前酿。但是效率不高且會造成內(nèi)存碎片化鹏溯。
- 復(fù)制算法:標(biāo)記待回收內(nèi)存和不需回收的內(nèi)存,將不需回收的內(nèi)存復(fù)制到新的內(nèi)存區(qū)域肺孵,這樣舊的內(nèi)存區(qū)域就可以全部回收颜阐,而新的內(nèi)存區(qū)域是連續(xù)的。其缺點(diǎn)是需要損失部分系統(tǒng)內(nèi)存用于復(fù)制,但是可以避免產(chǎn)生內(nèi)存碎片。實(shí)例創(chuàng)建時(shí)通常發(fā)生在
Eden空間
项炼,發(fā)生Minor GC
后芒率,會將Eden和其中一個(gè)Survivor空間
不需回收的對象內(nèi)存復(fù)制到另一個(gè)Survivor空間
中(HotSpot
虛擬機(jī)中Eden空間
和Survivor空間
的比例為8:1)篙顺,如果反復(fù)幾次復(fù)制有對象一直存活,則會將相應(yīng)的對象內(nèi)存移至老年代腋寨。
- 標(biāo)記-整理算法:是老年代中的垃圾回收算法萄窜,標(biāo)記過后撒桨,將不用回收的對象內(nèi)存壓縮到空間的一端,再對另一端的內(nèi)存空間進(jìn)行垃圾回收穗泵。這樣既避免了復(fù)制算法帶來的效率問題谜疤,也避免了內(nèi)存碎片化的問題现诀。
垃圾收集器
垃圾收集器是垃圾回收算法的具體實(shí)現(xiàn)仔沿。
Serial垃圾收集器
Serial垃圾收集器
是最基本尺棋、發(fā)展歷史最悠久的收集器。
- 采用復(fù)制算法成福,針對新生代
- 單線程垃圾回收荆残,執(zhí)行時(shí)必須暫停所有工作線程,直到完成
ParNew垃圾收集器
ParNew垃圾收集器
是Serial收集器的多線程版本握侧。
Parallel Scavenge垃圾收集器
Parallel Scavenge收集器
的目標(biāo)是達(dá)到一個(gè)可控制的吞吐量(CPU用于運(yùn)行用戶代碼的時(shí)間與CPU消耗的總時(shí)間的比值)品擎,即減少垃圾收集時(shí)間萄传,讓用戶代碼獲得更長的運(yùn)行時(shí)間蜜猾。
- 采用復(fù)制算法,針對新生代
- 多線程垃圾回收
Parallel Scavenge收集器
提供兩個(gè)參數(shù)用于精確控制吞吐量: - -XX:MaxGCPauseMillis:最大垃圾收集停頓時(shí)間(ms)衍菱,設(shè)置稍小會縮短停頓時(shí)間肩豁,但也可能會造成頻繁發(fā)生垃圾回收,使得吞吐量下降琼锋。
- -XX:GCTimeRatio:垃圾收集時(shí)間占總時(shí)間的比率祟昭,0 < n < 100的整數(shù)
CMS(Concurrent Mark Sweep)垃圾收集器
CMS收集器是一款真正意義上的并發(fā)收集器,實(shí)現(xiàn)了讓垃圾收集線程與用戶線程(基本上)同時(shí)工作谜叹。適用于與用戶交互較多的場景。
- 針對老年代
- 基于標(biāo)記-清除算法
- 以獲取最短回收停頓時(shí)間為目標(biāo)
G1垃圾收集器
- 充分利用多CPU叉谜、多核環(huán)境下的硬件優(yōu)勢踩萎,可以使垃圾收集和用戶程序同時(shí)進(jìn)行。
- 分代收集董栽,能夠獨(dú)立管理整個(gè)GC堆企孩,采用不同方式處理不同時(shí)期的對象。整個(gè)堆被劃分為多個(gè)大小相等的
不連續(xù)獨(dú)立區(qū)域(Region)
擒抛,新生代和老年代不再是物理隔離补疑,它們都是一部分Region的集合。 - 從整體看基于標(biāo)記-整理算法诊胞,從局部看(兩個(gè)Region間)是基于復(fù)制算法锹杈,不會產(chǎn)生內(nèi)存碎片,有利于長時(shí)間運(yùn)行邪码。
- 可預(yù)測的停頓咬清,可以明確指定M毫秒時(shí)間片內(nèi),垃圾收集消耗的時(shí)間不超過N毫秒喻圃。
- 適用于服務(wù)端粪滤,針對大內(nèi)存,多處理器的機(jī)器肆汹。
內(nèi)存分配
對象的內(nèi)存分配主要在堆上分配(JIT編譯優(yōu)化后可能在棧上分配),在新生代的Eden空間中分配昂勉,如果啟用了本地線程分配緩沖,則線程優(yōu)先在TLAB上分配村象。少數(shù)情況下對象會直接分配在老年代中厚者。
內(nèi)存分配策略
- 優(yōu)先分配到Eden空間:大多數(shù)情況下迫吐,對象在新生代
Eden區(qū)域
中分配。當(dāng)Eden區(qū)域
沒有足夠空間時(shí)將會發(fā)起一次Minor GC
熙宇。 - 大對象直接分配到老年代:所謂大對象是指需要大量連續(xù)空間的Java對象溉浙,直接在老年代分配空間就避免了大對象在新生代各個(gè)區(qū)域間反復(fù)復(fù)制。
- 長期存活的對象分配到老年代:虛擬機(jī)給每個(gè)對象定義了一個(gè)對象年齡計(jì)數(shù)器烈拒,如果對象在
Eden區(qū)域
出生且經(jīng)過一次Minor GC
后仍然存活广鳍,并且能被Survivor空間
容納,對象年齡就會被設(shè)為1吨铸,此后對象每熬過一次Minor GC
祖秒,其年齡就會增加1,當(dāng)年齡增加到一定程度(默認(rèn)為15歲)房维,就會晉升到老年代中抬纸。 - 動態(tài)對象年齡判斷:
Survivor空間
中相同年齡的所有對象大小的總和大于Survivor空間
的一半,則年齡大于等于該年齡的對象可以直接進(jìn)入老年代阿趁。 - 空間分配擔(dān)保:有可能在
Minor GC
執(zhí)行后仍有大量對象在新生代存活,就需要老年代進(jìn)行分配擔(dān)保脖阵,將Survivor空間
無法容納的對象晉升到老年代,老年代要進(jìn)行這樣的擔(dān)保呜呐,前提是老年代中有容納這些對象的足夠空間纷铣。因此在進(jìn)行Minor GC
前,虛擬機(jī)會檢查老年代最大連續(xù)可用內(nèi)存是否大于新生代所有對象總空間,如果條件成立槐秧,則Minor GC
可以確保是安全的,如果不成立虛擬機(jī)則查看HandlePromotionFailure
參數(shù)判斷是否允許擔(dān)保失敗颠通。如果允許顿锰,則會繼續(xù)檢查老年代的最大連續(xù)空間是否大于歷次晉升到老年代對象的平均大小启搂,如果大于將嘗試一次Minor GC
,如果小于或HandlePromotionFailure
參數(shù)不允許則改為進(jìn)行一次Full GC
牢撼,發(fā)生擔(dān)保失敗也會重新進(jìn)行一次Full GC
疑苫。空間分配擔(dān)焙扯蹋可以避免Full GC
過于頻繁挺勿。