新聞中心
一文理解JVM虛擬機(內(nèi)存、垃圾回收、性能優(yōu)化)解決面試中遇到問題
作者:Ccww 2019-12-18 15:13:33
云計算
虛擬化 Java堆是java虛擬機所管理內(nèi)存中最大的一塊內(nèi)存空間,處于物理上不連續(xù)的內(nèi)存空間,只要邏輯連續(xù)即可,主要用于存放各種類的實例對象。

一. JVM內(nèi)存區(qū)域的劃分
1.1 java虛擬機運行時數(shù)據(jù)區(qū)
java虛擬機運行時數(shù)據(jù)區(qū)分布圖:
- JVM棧(Java Virtual Machine Stacks): Java中一個線程就會相應(yīng)有一個線程棧與之對應(yīng),因為不同的線程執(zhí)行邏輯有所不同,因此需要一個獨立的線程棧,因此棧存儲的信息都是跟當前線程(或程序)相關(guān)信息的,包括局部變量、程序運行狀態(tài)、方法返回值、方法出口等等。每一個方法被調(diào)用直至執(zhí)行完成的過程,就對應(yīng)著一個棧幀在虛擬機棧中從入棧到出棧的過程。
- 堆(Heap): 堆是所有線程共享的,主要是存放對象實例和數(shù)組。處于物理上不連續(xù)的內(nèi)存空間,只要邏輯連續(xù)即可
- 方法區(qū)(Method Area): 屬于共享內(nèi)存區(qū)域,存儲已被虛擬機加載的類信息、常量、靜態(tài)變量、即時編譯器編譯后的代碼等數(shù)據(jù)
- 常量池(Runtime Constant Pool): 它是方法區(qū)的一部分,用于存放編譯期生成的各種字面量和符號引用。
- 本地方法棧(Native Method Stacks):
其中,堆(Heap)和JVM棧是程序運行的關(guān)鍵,因為:
- 棧是運行時的單位(解決程序的運行問題,即程序如何執(zhí)行,或者說如何處理數(shù)據(jù)),而堆是存儲的單位(解決的是數(shù)據(jù)存儲的問題,即數(shù)據(jù)怎么放、放在哪兒)。
- 堆存儲的是對象。棧存儲的是基本數(shù)據(jù)類型和堆中對象的引用;(參數(shù)傳遞的值傳遞和引用傳遞)
那為什么要把堆和棧區(qū)分出來呢?棧中不是也可以存儲數(shù)據(jù)嗎?
- 從軟件設(shè)計的角度看,棧代表了處理邏輯,而堆代表了數(shù)據(jù),分工明確,處理邏輯更為清晰體現(xiàn)了“分而治之”以及“隔離”的思想。
- 堆與棧的分離,使得堆中的內(nèi)容可以被多個棧共享(也可以理解為多個線程訪問同一個對象)。這樣共享的方式有很多收益:提供了一種有效的數(shù)據(jù)交互方式(如:共享內(nèi)存);堆中的共享常量和緩存可以被所有棧訪問,節(jié)省了空間。
- 棧因為運行時的需要,比如保存系統(tǒng)運行的上下文,需要進行地址段的劃分。由于棧只能向上增長,因此就會限制住棧存儲內(nèi)容的能力。而堆不同,堆中的對象是可以根據(jù)需要動態(tài)增長的,因此棧和堆的拆分,使得動態(tài)增長成為可能,相應(yīng)棧中只需記錄堆中的一個地址即可。
- 堆和棧的結(jié)合完美體現(xiàn)了面向?qū)ο蟮脑O(shè)計。當我們將對象拆開,你會發(fā)現(xiàn),對象的屬性即是數(shù)據(jù),存放在堆中;而對象的行為(方法)即是運行邏輯,放在棧中。因此編寫對象的時候,其實即編寫了數(shù)據(jù)結(jié)構(gòu),也編寫的處理數(shù)據(jù)的邏輯。
1.2 堆(Heap)和JVM棧:
1.2.1 堆(Heap)
Java堆是java虛擬機所管理內(nèi)存中最大的一塊內(nèi)存空間,處于物理上不連續(xù)的內(nèi)存空間,只要邏輯連續(xù)即可,主要用于存放各種類的實例對象。該區(qū)域被所有線程共享,在虛擬機啟動時創(chuàng)建,用來存放對象的實例,幾乎所有的對象以及數(shù)組都在這里分配內(nèi)存(棧上分配、標量替換優(yōu)化技術(shù)的例外)。
在 Java 中,堆被劃分成兩個不同的區(qū)域:新生代 ( Young )、老年代 ( Old )。新生代 ( Young ) 又被劃分為三個區(qū)域:Eden、From Survivor(S0)、To Survivor(S1)。如圖所示:
堆的內(nèi)存布局:
這樣劃分的目的是為了使jvm能夠更好的管理內(nèi)存中的對象,包括內(nèi)存的分配以及回收。 而新生代按eden和兩個survivor的分法,是為了
- 有效空間增大,eden+1個survivor;
- 有利于對象代的計算,當一個對象在S0/S1中達到設(shè)置的XX:MaxTenuringThreshold值后,會將其挪到老年代中,即只需掃描其中一個survivor。如果沒有S0/S1,直接分成兩個區(qū),該如何計算對象經(jīng)過了多少次GC還沒被釋放。
- 兩個Survivor區(qū)可解決內(nèi)存碎片化
1.2.2 堆棧相關(guān)的參數(shù)
Note: 每次GC 后會調(diào)整堆的大小,為了防止動態(tài)調(diào)整帶來的性能損耗,一般設(shè)置-Xms、-Xmx 相等。
新生代的三個設(shè)置參數(shù):-Xmn,-XX:NewSize,-XX:NewRatio的優(yōu)先級:
?1).最高優(yōu)先級: -XX:NewSize=1024m和-XX:MaxNewSize=1024m
? 2).次高優(yōu)先級: -Xmn1024m (默認等效效果是:-XX:NewSize==-XX:MaxNewSize==1024m)
? 3).最低優(yōu)先級:-XX:NewRatio=2
??推薦使用的是-Xmn參數(shù),原因是這個參數(shù)很簡潔,相當于一次性設(shè)定NewSize和MaxNewSIze,而且兩者相等。
1.3 jvm對象
1.3.1 創(chuàng)建對象的方式
各個方式的實質(zhì)操作如下:
1.3.2 jvm對象分配
在虛擬機層面上創(chuàng)建對象的步驟:
1.3.3 對象分配內(nèi)存方式
分配對象內(nèi)存,有兩種分配方式,指針碰撞和空閑列表:
1)如果內(nèi)存是規(guī)整的,那么虛擬機將采用的是指針碰撞法(Bump The Pointer)來為對象分配內(nèi)存。意思是所有用過的內(nèi)存在一邊,空閑的內(nèi)存在另外一邊,中間放著一個指針作為分界點的指示器,分配內(nèi)存就僅僅是把指針向空閑那邊挪動一段與對象大小相等的距離罷了。如果垃圾收集器選擇的是Serial、ParNew這種基于壓縮算法的,虛擬機采用這種分配方式。一般使用帶有compact(整理)過程的收集器時,使用指針碰撞。
2)如果內(nèi)存不是規(guī)整的,已使用的內(nèi)存和未使用的內(nèi)存相互交錯,那么虛擬機將采用的是空閑列表法來為對象分配內(nèi)存。意思是虛擬機維護了一個列表,記錄上哪些內(nèi)存塊是可用的,再分配的時候從列表中找到一塊足夠大的空間劃分給對象實例,并更新列表上的內(nèi)容。這種分配方式成為“空閑列表(Free List)”。
Note: 選擇哪種分配方式由Java堆是否規(guī)整決定,而Java堆是否規(guī)整又由所采用的垃圾收集器是否帶有壓縮整理功能決定。
1.3.4 那什么樣的對象能夠進入老年代(Old)
1.4 內(nèi)存分配與回收策略
對象優(yōu)先在Eden分配,大多數(shù)情況下,對象在新生代Eden區(qū)中分配,當Eden區(qū)沒有足夠的空間進行分配時,虛擬機將發(fā)起一次Minor GC;虛擬機提供了-XX:PrintGCDetails參數(shù),發(fā)生垃圾回收時打印內(nèi)存回收日志,并且在進程退出時輸出當前內(nèi)存各區(qū)域的分配情況。
大對象直接進入老年代,所謂的大對象就是指,需要大量連續(xù)內(nèi)存空間的java對象,最典型的大對象就是那種很長的字符串及數(shù)組。虛擬機提供了一個-XX:PretenureSizeThreshold參數(shù),令大于這個設(shè)置值得對象直接在老年代中分配(這樣做的目的是避免在Eden區(qū)及兩個Survivor之間發(fā)生大量的內(nèi)存拷貝)
長期存活的對象將直接進入老年代,對象年齡計數(shù)器。-XX:MaxTenuringThreshold
動態(tài)對象年齡判定,虛擬機并不總是要求對象的年齡必須達到MaxTenuringThreshold才能晉升老年代,如果在Survivor空間中相同年齡所有對象大小的總和大于Survivor空間的一半,年齡大于或等于該年齡的對象就可以直接進入老年代,無需等到MaxTenuringThreshold中要求的年齡
空間分配擔保,在發(fā)生Minor GC時(前),虛擬機會檢測之前每次晉升到老年代的平均大小(因為當次會有多少對象會存活是無法確定的,所以取之前的平均值/經(jīng)驗值)是否大于老年代的剩余空間大小,如果大于,則改為直接進行一次Full GC。如果小于,則查看HandlePromotionFailure設(shè)置是否允許擔保失敗;如果允許,那只會進行Minor GC;如果不允許,則也要改為進行一次Full GC。取平均值進行比較其實仍然是一種動態(tài)概率手段,也就是說如果某次Minor GC存活后的對象突增,遠遠高于平均值的話,依然會導(dǎo)致?lián)J?Handle Promotion Failure),這樣會觸發(fā)Full GC。
2.1 引用二 垃圾回收算法分類
2.2 GC Root的對象
2.3 標記-清除(Mark—Sweep)
被譽為現(xiàn)代垃圾回收算法的思想基礎(chǔ)。
標記-清除算法采用從根集合進行掃描,對存活的對象對象標記,標記完畢后,再掃描整個空間中未被標記的對象,進行回收,如上圖所示。標記-清除算法不需要進行對象的移動,并且僅對不存活的對象進行處理,在存活對象比較多的情況下極為高效,但由于標記-清除算法直接回收不存活的對象,因此會造成內(nèi)存碎片。
2.4 復(fù)制算法(Copying)
該算法的提出是為了克服句柄的開銷和解決堆碎片的垃圾回收。建立在存活對象少,垃圾對象多的前提下。此算法每次只處理正在使用中的對象,因此復(fù)制成本比較小,同時復(fù)制過去后還能進行相應(yīng)的內(nèi)存整理,不會出現(xiàn)碎片問題。但缺點也是很明顯,就是需要兩倍內(nèi)存空間。
它開始時把堆分成 一個對象 面和多個空閑面, 程序從對象面為對象分配空間,當對象滿了,基于copying算法的垃圾 收集就從根集中掃描活動對象,并將每個活動對象復(fù)制到空閑面(使得活動對象所占的內(nèi)存之間沒有空閑洞),這樣空閑面變成了對象面,原來的對象面變成了空閑面,程序會在新的對象面中分配內(nèi)存。一種典型的基于coping算法的垃圾回收是stop-and-copy算法,它將堆分成對象面和空閑區(qū)域面,在對象面與空閑區(qū)域面的切換過程中,程序暫停執(zhí)行。
2.5 標記-整理(或標記-壓縮算法,Mark-Compact,又或者叫標記清除壓縮MarkSweepCompact)
此算法是結(jié)合了“標記-清除”和“復(fù)制算法”兩個算法的優(yōu)點。避免了“標記-清除”的碎片問題,同時也避免了“復(fù)制”算法的空間問題。
標記-整理算法采用標記-清除算法一樣的方式進行對象的標記,但在清除時不同,在回收不存活的對象占用的空間后,會將所有的存活對象往左端空閑空間移動,并更新對應(yīng)的指針。標記-整理算法是在標記-清除算法的基礎(chǔ)上,又進行了對象的移動,因此成本更高,但是卻解決了內(nèi)存碎片的問題。在基于Compacting算法的收集器的實現(xiàn)中,一般增加句柄和句柄表。
2.6 分代回收策略(Generational Collecting)
基于這樣的事實:不同的對象的生命周期是不一樣的。因此,不同生命周期的對象可以采取不同的回收算法,以便提高回收效率。
新生代由于其對象存活時間短,且需要經(jīng)常gc,因此采用效率較高的復(fù)制算法,其將內(nèi)存區(qū)分為一個eden區(qū)和兩個suvivor區(qū),默認eden區(qū)和survivor區(qū)的比例是8:1,分配內(nèi)存時先分配eden區(qū),當eden區(qū)滿時,使用復(fù)制算法進行g(shù)c,將存活對象復(fù)制到一個survivor區(qū),當一個survivor區(qū)滿時,將其存活對象復(fù)制到另一個區(qū)中,當對象存活時間大于某一閾值時,將其放入老年代。老年代和永久代因為其存活對象時間長,因此使用標記清除或標記整理算法
總結(jié):
新生代:復(fù)制算法(新生代回收的頻率很高,每次回收的耗時很短,為了支持高頻率的新生代回收,虛擬機可能使用一種叫做卡表(Card Table)的數(shù)據(jù)結(jié)構(gòu),卡表為一個比特位集合,每個比特位可以用來表示老年代的某一區(qū)域中的所有對象是否持有新生代對,
2.7 垃圾回收器
垃圾回收器的任務(wù)是識別和回收垃圾對象進行內(nèi)存清理,不同代可使用不同的收集器:
- 新生代收集器使用的收集器:Serial、ParNew、Parallel Scavenge;
- 老年代收集器使用的收集器:Serial Old(MSC)、Parallel Old、CMS。
總結(jié):
- Serial old和新生代的所有回收器都能搭配;也可以作為CMS回收器的備用回收器;
- CMS只能和新生代的Serial和ParNew搭配,而且ParNew是CMS默認的新生代回收器;
- 并行(Parallel):指多條垃圾收集線程并行工作,但此時用戶線程仍然處于等待狀態(tài)
- 并發(fā)(Concurrent):指用戶線程和垃圾收集線程同時執(zhí)行(但不一定是并行的,可能是交替執(zhí)行),用戶程序繼續(xù)運行,而垃圾收集程序運行在另外的CPU上。
三. GC的執(zhí)行機制
Java 中的堆(deap) 也是 GC 收集垃圾的主要區(qū)域。由于對象進行了分代處理,因此垃圾回收區(qū)域、時間也不一樣。GC有兩種類型:Scavenge GC(Minor GC)和Full GC(Major GC):
- Scavenge GC(Minor GC): 一般情況下,當新對象生成(age=0),并且在Eden申請空間失敗時,就會觸發(fā)Scavenge GC,對Eden區(qū)域進行GC,清除非存活對象,并且把尚且存活的對象移動到Survivor區(qū)(age+1)。然后整理(其實是復(fù)制過去就順便整理了)Survivor的兩個區(qū)。這種方式的GC是對年輕代的Eden區(qū)進行,不會影響到年老代。因為大部分對象都是從Eden區(qū)開始的,同時Eden區(qū)不會分配的很大,所以Eden區(qū)的GC會頻繁進行。因而,一般在這里需要使用速度快、效率高的算法(即復(fù)制-清理算法),使Eden去能盡快空閑出來。Java 中的大部分對象通常不需長久存活,具有朝生夕滅的性質(zhì)。
- Full GC:對整個堆進行整理,包括Young、Tenured和Perm。Full GC因為需要對整個對進行回收,所以比Scavenge GC要慢,因此應(yīng)該盡可能減少Full GC的次數(shù)。在對JVM調(diào)優(yōu)的過程中,很大一部分工作就是對于FullGC的調(diào)節(jié)。
3.1 觸發(fā)Full GC執(zhí)行的場景
3.2 Young GC觸發(fā)條件
3.3 新生對象GC收回流程
基于大多數(shù)新生對象都會在GC中被收回的假設(shè)。新生代的GC 使用復(fù)制算法,(將年輕代分為3部分,主要是為了生命周期短的對象盡量留在年輕代。老年代主要存放生命周期比較長的對象,比如緩存)??赡芙?jīng)歷過程:
- 對象創(chuàng)建時,一般在Eden區(qū)完成內(nèi)存分配(有特殊);
- 當Eden區(qū)滿了,再創(chuàng)建對象,會因為申請不到空間,觸發(fā)minorGC,進行young(eden+1survivor)區(qū)的垃圾回收;
- minorGC時,Eden和survivor A不能被GC回收且年齡沒有達到閾值(tenuring threshold)的對象,會被放入survivor B,始終保證一個survivor是空的;
- 當做第3步的時候,如果發(fā)現(xiàn)survivor滿了,將這些對象copy到old區(qū)(分配擔保機制);或者survivor并沒有滿,但是有些對象已經(jīng)足夠Old,也被放入Old區(qū) XX:MaxTenuringThreshold;(回顧下對象進入老年代的情況)
- 直接清空eden和survivor A;
- 當Old區(qū)被放滿的之后,進行fullGC。
3.4 GC日志
GC日志相關(guān)參數(shù):
- -XX:+PrintGC:輸出GC日志
- -XX:+PrintGCDetails:輸出GC的詳細日志
- -XX:+PrintGCTimeStamps:輸出GC的時間戳(以基準時間的形式)
- -XX:+PrintGCApplicationStoppedTime:打印垃圾回收期間程序暫停的時間
- -XX:+PrintGCApplicationConcurrentTime:打印每次垃圾回收前,程序未中斷的執(zhí)行時間
- -XX:+PrintHeapAtGC:在進行GC的前后打印出堆的信息
- -XX:+PrintTLAB:查看TLAB空間的使用情況
- -XX:PrintTenuingDistribution:查看每次minor GC后新的存活周期的閾值
- -XX:PrintReferenceFC:用來跟蹤系統(tǒng)內(nèi)的(softReference)軟引用,(weadReference)弱引用,(phantomReference)虛引用,顯示引用過程
案例分析:
-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毫秒中是在處理實際工作的,然后將所有線程暫停了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)用線程被強制暫停了57ms來進行垃圾回收。其中又有8ms是用來等待所有的應(yīng)用線程都到達安全點。
只要設(shè)置-XX:+PrintGCDetails 就會自動帶上-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]
- 最前面的數(shù)字“33.125:”和“100.667:”代表了GC發(fā)生的時間,這個數(shù)字的含義是從Java虛擬機啟動以來經(jīng)過的秒數(shù)。
- GC日志開頭的“[GC”和“[Full GC”說明了這次垃圾收集的停頓類型,而不是用來區(qū)分新生代GC還是老年代GC的。如果有“Full”,說明這次GC是發(fā)生了Stop-The-World的。
- 接下來的“[DefNew”、“[Tenured”、“[Perm”表示GC發(fā)生的區(qū)域,這里顯示的區(qū)域名稱與使用的GC收集器是密切相關(guān)的,例如上面樣例所使用的Serial收集器中的新生代名為“Default New Generation”,所以顯示的是“[DefNew”。如果是ParNew收集器,新生代名稱就會變?yōu)椤癧ParNew”,意為“Parallel New Generation”。如果采用Parallel Scavenge收集器,那它配套的新生代稱為“PSYoungGen”,老年代和永久代同理,名稱也是由收集器決定的。
- 后面方括號內(nèi)部的“3324K->152K(3712K)”含義是“GC前該內(nèi)存區(qū)域已使用容量-> GC后該內(nèi)存區(qū)域已使用容量 (該內(nèi)存區(qū)域總?cè)萘?”。而在方括號之外的“3324K->152K(11904K)”表示“GC前Java堆已使用容量 -> GC后Java堆已使用容量 (Java堆總?cè)萘?”。
- 再往后,“0.0025925 secs”表示該內(nèi)存區(qū)域GC所占用的時間,單位是秒。有的收集器會給出更具體的時間數(shù)據(jù)
- [Full GC 283.736: [ParNew: 261599K->261599K(261952K), 0.0000288 secs] 新生代收集器ParNew的日志也會出現(xiàn)“[Full GC”(這一般是因為出現(xiàn)了分配擔保失敗之類的問題,所以才導(dǎo)致STW)。如果是調(diào)用System.gc()方法所觸發(fā)的收集,那么在這里將顯示“[Full GC (System)”。
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)主要是針對垃圾收集器的收集性能優(yōu)化,令運行在虛擬機上的應(yīng)用能夠使用更少的內(nèi)存以及延遲獲取更大的吞吐量。
分享標題:一文理解JVM虛擬機(內(nèi)存、垃圾回收、性能優(yōu)化)解決面試中遇到問題
轉(zhuǎn)載注明:http://m.5511xx.com/article/dpijhop.html


咨詢
建站咨詢
