掃二維碼與項(xiàng)目經(jīng)理溝通
我們?cè)谖⑿派?4小時(shí)期待你的聲音
解答本文疑問/技術(shù)咨詢/運(yùn)營咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流
作者:Ccww 2019-12-18 15:13:33
云計(jì)算
虛擬化 Java堆是java虛擬機(jī)所管理內(nèi)存中最大的一塊內(nèi)存空間,處于物理上不連續(xù)的內(nèi)存空間,只要邏輯連續(xù)即可,主要用于存放各種類的實(shí)例對(duì)象。

一. JVM內(nèi)存區(qū)域的劃分
1.1 java虛擬機(jī)運(yùn)行時(shí)數(shù)據(jù)區(qū)
java虛擬機(jī)運(yùn)行時(shí)數(shù)據(jù)區(qū)分布圖:
其中,堆(Heap)和JVM棧是程序運(yùn)行的關(guān)鍵,因?yàn)椋?/p>
那為什么要把堆和棧區(qū)分出來呢?棧中不是也可以存儲(chǔ)數(shù)據(jù)嗎?
1.2 堆(Heap)和JVM棧:
1.2.1 堆(Heap)
Java堆是java虛擬機(jī)所管理內(nèi)存中最大的一塊內(nèi)存空間,處于物理上不連續(xù)的內(nèi)存空間,只要邏輯連續(xù)即可,主要用于存放各種類的實(shí)例對(duì)象。該區(qū)域被所有線程共享,在虛擬機(jī)啟動(dòng)時(shí)創(chuàng)建,用來存放對(duì)象的實(shí)例,幾乎所有的對(duì)象以及數(shù)組都在這里分配內(nèi)存(棧上分配、標(biāo)量替換優(yōu)化技術(shù)的例外)。
在 Java 中,堆被劃分成兩個(gè)不同的區(qū)域:新生代 ( Young )、老年代 ( Old )。新生代 ( Young ) 又被劃分為三個(gè)區(qū)域:Eden、From Survivor(S0)、To Survivor(S1)。如圖所示:
堆的內(nèi)存布局:
這樣劃分的目的是為了使jvm能夠更好的管理內(nèi)存中的對(duì)象,包括內(nèi)存的分配以及回收。 而新生代按eden和兩個(gè)survivor的分法,是為了
1.2.2 堆棧相關(guān)的參數(shù)
Note: 每次GC 后會(huì)調(diào)整堆的大小,為了防止動(dòng)態(tài)調(diào)整帶來的性能損耗,一般設(shè)置-Xms、-Xmx 相等。
新生代的三個(gè)設(shè)置參數(shù):-Xmn,-XX:NewSize,-XX:NewRatio的優(yōu)先級(jí):
?1).最高優(yōu)先級(jí): -XX:NewSize=1024m和-XX:MaxNewSize=1024m
? 2).次高優(yōu)先級(jí): -Xmn1024m (默認(rèn)等效效果是:-XX:NewSize==-XX:MaxNewSize==1024m)
? 3).最低優(yōu)先級(jí):-XX:NewRatio=2
??推薦使用的是-Xmn參數(shù),原因是這個(gè)參數(shù)很簡(jiǎn)潔,相當(dāng)于一次性設(shè)定NewSize和MaxNewSIze,而且兩者相等。
1.3 jvm對(duì)象
1.3.1 創(chuàng)建對(duì)象的方式
各個(gè)方式的實(shí)質(zhì)操作如下:
1.3.2 jvm對(duì)象分配
在虛擬機(jī)層面上創(chuàng)建對(duì)象的步驟:
1.3.3 對(duì)象分配內(nèi)存方式
分配對(duì)象內(nèi)存,有兩種分配方式,指針碰撞和空閑列表:
1)如果內(nèi)存是規(guī)整的,那么虛擬機(jī)將采用的是指針碰撞法(Bump The Pointer)來為對(duì)象分配內(nèi)存。意思是所有用過的內(nèi)存在一邊,空閑的內(nèi)存在另外一邊,中間放著一個(gè)指針作為分界點(diǎn)的指示器,分配內(nèi)存就僅僅是把指針向空閑那邊挪動(dòng)一段與對(duì)象大小相等的距離罷了。如果垃圾收集器選擇的是Serial、ParNew這種基于壓縮算法的,虛擬機(jī)采用這種分配方式。一般使用帶有compact(整理)過程的收集器時(shí),使用指針碰撞。
2)如果內(nèi)存不是規(guī)整的,已使用的內(nèi)存和未使用的內(nèi)存相互交錯(cuò),那么虛擬機(jī)將采用的是空閑列表法來為對(duì)象分配內(nèi)存。意思是虛擬機(jī)維護(hù)了一個(gè)列表,記錄上哪些內(nèi)存塊是可用的,再分配的時(shí)候從列表中找到一塊足夠大的空間劃分給對(duì)象實(shí)例,并更新列表上的內(nèi)容。這種分配方式成為“空閑列表(Free List)”。
Note: 選擇哪種分配方式由Java堆是否規(guī)整決定,而Java堆是否規(guī)整又由所采用的垃圾收集器是否帶有壓縮整理功能決定。
1.3.4 那什么樣的對(duì)象能夠進(jìn)入老年代(Old)
1.4 內(nèi)存分配與回收策略
對(duì)象優(yōu)先在Eden分配,大多數(shù)情況下,對(duì)象在新生代Eden區(qū)中分配,當(dāng)Eden區(qū)沒有足夠的空間進(jìn)行分配時(shí),虛擬機(jī)將發(fā)起一次Minor GC;虛擬機(jī)提供了-XX:PrintGCDetails參數(shù),發(fā)生垃圾回收時(shí)打印內(nèi)存回收日志,并且在進(jìn)程退出時(shí)輸出當(dāng)前內(nèi)存各區(qū)域的分配情況。
大對(duì)象直接進(jìn)入老年代,所謂的大對(duì)象就是指,需要大量連續(xù)內(nèi)存空間的java對(duì)象,最典型的大對(duì)象就是那種很長的字符串及數(shù)組。虛擬機(jī)提供了一個(gè)-XX:PretenureSizeThreshold參數(shù),令大于這個(gè)設(shè)置值得對(duì)象直接在老年代中分配(這樣做的目的是避免在Eden區(qū)及兩個(gè)Survivor之間發(fā)生大量的內(nèi)存拷貝)
長期存活的對(duì)象將直接進(jìn)入老年代,對(duì)象年齡計(jì)數(shù)器。-XX:MaxTenuringThreshold
動(dòng)態(tài)對(duì)象年齡判定,虛擬機(jī)并不總是要求對(duì)象的年齡必須達(dá)到MaxTenuringThreshold才能晉升老年代,如果在Survivor空間中相同年齡所有對(duì)象大小的總和大于Survivor空間的一半,年齡大于或等于該年齡的對(duì)象就可以直接進(jìn)入老年代,無需等到MaxTenuringThreshold中要求的年齡
空間分配擔(dān)保,在發(fā)生Minor GC時(shí)(前),虛擬機(jī)會(huì)檢測(cè)之前每次晉升到老年代的平均大小(因?yàn)楫?dāng)次會(huì)有多少對(duì)象會(huì)存活是無法確定的,所以取之前的平均值/經(jīng)驗(yàn)值)是否大于老年代的剩余空間大小,如果大于,則改為直接進(jìn)行一次Full GC。如果小于,則查看HandlePromotionFailure設(shè)置是否允許擔(dān)保失敗;如果允許,那只會(huì)進(jìn)行Minor GC;如果不允許,則也要改為進(jìn)行一次Full GC。取平均值進(jìn)行比較其實(shí)仍然是一種動(dòng)態(tài)概率手段,也就是說如果某次Minor GC存活后的對(duì)象突增,遠(yuǎn)遠(yuǎn)高于平均值的話,依然會(huì)導(dǎo)致?lián)J?Handle Promotion Failure),這樣會(huì)觸發(fā)Full GC。
2.1 引用二 垃圾回收算法分類
2.2 GC Root的對(duì)象
2.3 標(biāo)記-清除(Mark—Sweep)
被譽(yù)為現(xiàn)代垃圾回收算法的思想基礎(chǔ)。
標(biāo)記-清除算法采用從根集合進(jìn)行掃描,對(duì)存活的對(duì)象對(duì)象標(biāo)記,標(biāo)記完畢后,再掃描整個(gè)空間中未被標(biāo)記的對(duì)象,進(jìn)行回收,如上圖所示。標(biāo)記-清除算法不需要進(jìn)行對(duì)象的移動(dòng),并且僅對(duì)不存活的對(duì)象進(jìn)行處理,在存活對(duì)象比較多的情況下極為高效,但由于標(biāo)記-清除算法直接回收不存活的對(duì)象,因此會(huì)造成內(nèi)存碎片。
2.4 復(fù)制算法(Copying)
該算法的提出是為了克服句柄的開銷和解決堆碎片的垃圾回收。建立在存活對(duì)象少,垃圾對(duì)象多的前提下。此算法每次只處理正在使用中的對(duì)象,因此復(fù)制成本比較小,同時(shí)復(fù)制過去后還能進(jìn)行相應(yīng)的內(nèi)存整理,不會(huì)出現(xiàn)碎片問題。但缺點(diǎn)也是很明顯,就是需要兩倍內(nèi)存空間。
它開始時(shí)把堆分成 一個(gè)對(duì)象 面和多個(gè)空閑面, 程序從對(duì)象面為對(duì)象分配空間,當(dāng)對(duì)象滿了,基于copying算法的垃圾 收集就從根集中掃描活動(dòng)對(duì)象,并將每個(gè)活動(dòng)對(duì)象復(fù)制到空閑面(使得活動(dòng)對(duì)象所占的內(nèi)存之間沒有空閑洞),這樣空閑面變成了對(duì)象面,原來的對(duì)象面變成了空閑面,程序會(huì)在新的對(duì)象面中分配內(nèi)存。一種典型的基于coping算法的垃圾回收是stop-and-copy算法,它將堆分成對(duì)象面和空閑區(qū)域面,在對(duì)象面與空閑區(qū)域面的切換過程中,程序暫停執(zhí)行。
2.5 標(biāo)記-整理(或標(biāo)記-壓縮算法,Mark-Compact,又或者叫標(biāo)記清除壓縮MarkSweepCompact)
此算法是結(jié)合了“標(biāo)記-清除”和“復(fù)制算法”兩個(gè)算法的優(yōu)點(diǎn)。避免了“標(biāo)記-清除”的碎片問題,同時(shí)也避免了“復(fù)制”算法的空間問題。
標(biāo)記-整理算法采用標(biāo)記-清除算法一樣的方式進(jìn)行對(duì)象的標(biāo)記,但在清除時(shí)不同,在回收不存活的對(duì)象占用的空間后,會(huì)將所有的存活對(duì)象往左端空閑空間移動(dòng),并更新對(duì)應(yīng)的指針。標(biāo)記-整理算法是在標(biāo)記-清除算法的基礎(chǔ)上,又進(jìn)行了對(duì)象的移動(dòng),因此成本更高,但是卻解決了內(nèi)存碎片的問題。在基于Compacting算法的收集器的實(shí)現(xiàn)中,一般增加句柄和句柄表。
2.6 分代回收策略(Generational Collecting)
基于這樣的事實(shí):不同的對(duì)象的生命周期是不一樣的。因此,不同生命周期的對(duì)象可以采取不同的回收算法,以便提高回收效率。
新生代由于其對(duì)象存活時(shí)間短,且需要經(jīng)常gc,因此采用效率較高的復(fù)制算法,其將內(nèi)存區(qū)分為一個(gè)eden區(qū)和兩個(gè)suvivor區(qū),默認(rèn)eden區(qū)和survivor區(qū)的比例是8:1,分配內(nèi)存時(shí)先分配eden區(qū),當(dāng)eden區(qū)滿時(shí),使用復(fù)制算法進(jìn)行g(shù)c,將存活對(duì)象復(fù)制到一個(gè)survivor區(qū),當(dāng)一個(gè)survivor區(qū)滿時(shí),將其存活對(duì)象復(fù)制到另一個(gè)區(qū)中,當(dāng)對(duì)象存活時(shí)間大于某一閾值時(shí),將其放入老年代。老年代和永久代因?yàn)槠浯婊顚?duì)象時(shí)間長,因此使用標(biāo)記清除或標(biāo)記整理算法
總結(jié):
新生代:復(fù)制算法(新生代回收的頻率很高,每次回收的耗時(shí)很短,為了支持高頻率的新生代回收,虛擬機(jī)可能使用一種叫做卡表(Card Table)的數(shù)據(jù)結(jié)構(gòu),卡表為一個(gè)比特位集合,每個(gè)比特位可以用來表示老年代的某一區(qū)域中的所有對(duì)象是否持有新生代對(duì),
2.7 垃圾回收器
垃圾回收器的任務(wù)是識(shí)別和回收垃圾對(duì)象進(jìn)行內(nèi)存清理,不同代可使用不同的收集器:
總結(jié):
三. GC的執(zhí)行機(jī)制
Java 中的堆(deap) 也是 GC 收集垃圾的主要區(qū)域。由于對(duì)象進(jìn)行了分代處理,因此垃圾回收區(qū)域、時(shí)間也不一樣。GC有兩種類型:Scavenge GC(Minor GC)和Full GC(Major GC):
3.1 觸發(fā)Full GC執(zhí)行的場(chǎng)景
3.2 Young GC觸發(fā)條件
3.3 新生對(duì)象GC收回流程
基于大多數(shù)新生對(duì)象都會(huì)在GC中被收回的假設(shè)。新生代的GC 使用復(fù)制算法,(將年輕代分為3部分,主要是為了生命周期短的對(duì)象盡量留在年輕代。老年代主要存放生命周期比較長的對(duì)象,比如緩存)。可能經(jīng)歷過程:
3.4 GC日志
GC日志相關(guān)參數(shù):
案例分析:
-XX:+PrintGCApplicationStoppedTime -XX:+PrintGCApplicationConcurrentTime一起使用
Application time: 0.3440086 seconds Total time for which application threads were stopped: 0.0620105 seconds Application time: 0.2100691 seconds Total time for which application threads were stopped: 0.0890223 seconds
得知應(yīng)用程序在前344毫秒中是在處理實(shí)際工作的,然后將所有線程暫停了62毫秒,緊接著又工作了210ms,然后又暫停了89ms。
2796146K->2049K(1784832K)] 4171400K->2049K(3171840K), [Metaspace: 3134K->3134K(1056768K)], 0.0571841 secs] [Times: user=0.02 sys=0.04, real=0.06 secs]Total time for which application threads were stopped: 0.0572646 seconds, Stopping threads took: 0.0000088 seconds
應(yīng)用線程被強(qiáng)制暫停了57ms來進(jìn)行垃圾回收。其中又有8ms是用來等待所有的應(yīng)用線程都到達(dá)安全點(diǎn)。
只要設(shè)置-XX:+PrintGCDetails 就會(huì)自動(dòng)帶上-verbose:gc和-XX:+PrintGC
33.125: [GC [DefNew: 3324K->152K(3712K), 0.0025925 secs] 3324K->152K(11904K), 0.0031680 secs]100.667: [Full GC [Tenured: 0K->210K(10240K), 0.0149142 secs] 4603K->210K(19456K), [Perm : 2999K->2999K(21248K)], 0.0150007 secs] [Times: user=0.01 sys=0.00, real=0.02 secs]
3.5 減少GC開銷的措施
從代碼上:
從JVM參數(shù)上調(diào)優(yōu)上:
3.6 內(nèi)存溢出分類
四. 總結(jié)-JVM調(diào)優(yōu)相關(guān)
4.1 調(diào)優(yōu)目的
4.2 JVM性能調(diào)優(yōu)所處的層次
4.3 JVM調(diào)優(yōu)流程
4.4 性能監(jiān)控工具
調(diào)優(yōu)的最終目的都是為了令應(yīng)用程序使用最小的硬件消耗來承載更大的吞吐。jvm的調(diào)優(yōu)也不例外,jvm調(diào)優(yōu)主要是針對(duì)垃圾收集器的收集性能優(yōu)化,令運(yùn)行在虛擬機(jī)上的應(yīng)用能夠使用更少的內(nèi)存以及延遲獲取更大的吞吐量。

我們?cè)谖⑿派?4小時(shí)期待你的聲音
解答本文疑問/技術(shù)咨詢/運(yùn)營咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流