您好,登錄后才能下訂單哦!
本篇內(nèi)容介紹了“Java內(nèi)存分配與回收機制是什么”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠?qū)W有所成!
下圖是Java虛擬機運行時的內(nèi)存示意圖:
從圖中我們可以看到Java內(nèi)存總共分為6個部分:
程序計數(shù)器:每條線程都有一個獨立的程序計數(shù)器,計數(shù)器可以看作是當前線程所執(zhí)行的字節(jié)碼的行號指示器。字節(jié)碼解釋器工作時,就是通過改變這個計數(shù)器的值來選取下一條所需執(zhí)行的字節(jié)碼指令、分支、循環(huán)、跳轉(zhuǎn)、異常處理,線程恢復等基礎功能都需要依賴這個計數(shù)器完成。
Java虛擬機棧:虛擬機棧是線程私有的,生命周期與線程相同。虛擬機棧為Java方法執(zhí)行描述內(nèi)存模型,每個方法在執(zhí)行的同時會創(chuàng)建一個棧幀用于存儲局部變量表、操作數(shù)棧、動態(tài)鏈接、方法出口等信息。每一個方法從調(diào)用直至執(zhí)行完成的過程,就對應一個棧幀在虛擬機棧中入棧到出棧的過程。
本地方法棧:與虛擬機棧發(fā)揮的作用相似。區(qū)別是虛擬機棧為執(zhí)行Java方法服務,本地方法棧為Native方法服務。
堆:所有線程共享的區(qū)域。在虛擬機啟動時創(chuàng)建,所有的對象實例幾乎都在堆上分配。Java堆還可以細分為:新生代和老年代,再細致一點有Eden空間、From Survivor空間、To Survivor空間。不過無論如何劃分,存儲的都是對象實例,進一步劃分的目的是為了更好的回收內(nèi)存,或者更快的分配內(nèi)存。
方法區(qū):方法區(qū)是各個線程共享的內(nèi)存區(qū)域,主要用于存儲已被虛擬機加載的類信息、常量、靜態(tài)變量、即時編譯后的代碼等數(shù)據(jù)。這塊區(qū)域與Java堆一樣不需要連續(xù)的內(nèi)存和可以選擇固定大小或可擴展外,還可以選擇不實現(xiàn)垃圾收集。這區(qū)域的內(nèi)存回收目標主要是針對常量池的回收和對類型的卸載,垃圾收集行為在這個區(qū)域較少出現(xiàn)。
運行時常量池:運行時常量池是方法區(qū)的一部分。Class文件中除了有類的版本、字段、方法、接口等描述信息外,還有一項信息是常量池,用于存放編譯期生成的各種字面符和符號引用,這部分內(nèi)容在類加載后進入方法區(qū)的運行時常量池中存放。
直接內(nèi)存:直接內(nèi)存也稱堆外內(nèi)存,它不是虛擬機運行時數(shù)據(jù)區(qū)的一部分。JDK1.4后引入NIO類,是一種基于通道(Channel)與緩沖區(qū)(Buffer)的I/O方式,它可以使用Native函數(shù)庫直接在堆外分配內(nèi)存,然后通過存儲在Java堆中的DirectByteBuffer對象作為引用對這塊內(nèi)存進行操作。這樣能夠顯著提高性能,避免Java堆和Native堆中來回復制數(shù)據(jù)。
所以通過表格的形式概括如下:
數(shù)據(jù)區(qū)域 概括 線程共享 程序計數(shù)器 當前線程所執(zhí)行的字節(jié)碼的行號指示器 否 虛擬機棧 為Java方法執(zhí)行創(chuàng)建棧幀存儲局部變量、操作數(shù)棧、動態(tài)鏈接、方法出口等信息 否 本地方法棧 與虛擬機棧類似,為Native方法服務 否 堆 存放對象實例 是 方法區(qū) 存儲虛擬機已加載的類信息、常量、靜態(tài)變量、即時編譯后的代碼等數(shù)據(jù) 是 運行時常量池 方法區(qū)的一部分,存放編譯期生成的字面量和符號引用 是 直接內(nèi)存 被分配在堆外的內(nèi)存,性能高,不受Java堆的大小限制 是 二.對象的創(chuàng)建與內(nèi)存布局
1.對象的創(chuàng)建
Java對象的創(chuàng)建
上圖是對象創(chuàng)建的完整流程圖,接下來做詳細說明。
當虛擬機收到new指令后,檢查這個指令的參數(shù)是否能在常量池中定位到一個類的符號引用,并且檢查這個符號引用所代表的類是否已被加載、解析和初始化過。如果沒有,必須先執(zhí)行類加載過程。
在類加載完成后可以確定對象分配所需要的空間。如果Java堆中內(nèi)存是絕對規(guī)整的,用過的內(nèi)存放一邊,空閑的內(nèi)存放另一邊,中間放著一個指針作為分界點的指示器,那分配內(nèi)存就只是把指針向空閑空間方向挪動一段與對象大小相等的距離,這種分配方式稱為"指針碰撞"。如果Java堆中內(nèi)存不是規(guī)整的,空閑內(nèi)存與使用過的內(nèi)存是相互交錯的,虛擬機必須維護一個列表,記錄哪些內(nèi)存塊是可用的,在分配的時候從列表中找出足夠的空間分配給對象實例,并更新列表上的記錄,這種分配方式稱為"空閑列表"。采用哪種分配方式通常由虛擬機的垃圾收集器是否帶有壓縮整理功能決定。
劃分可用空間時,還需考慮為對象實例分配空間時是否是線程安全的。要保證線程安全,有兩種方案。一種是對分配內(nèi)存空間的動作進行同步處理,實際上虛擬機采用CAS配上失敗重試的方式保證更新操作的原子性。另一種是把內(nèi)存分配的動作按照線程劃分在不同空間中進行,每個線程在Java堆中預先分配一小塊內(nèi)存,稱為本地線程分配緩沖(Thread Local Allocation Buffer , TLAB)。哪個線程要分配內(nèi)存,就在哪個線程的TLAB上分配,只有TLAB用完并分配新的TLAB時,才需要同步鎖定。
內(nèi)存分配完成后,虛擬機對分配到的內(nèi)存空間都初始化為零值(不包括對象頭),保證對象的實例字段在Java代碼中可以不賦初始值就可以直接使用。
虛擬機將對象的信息放入對象的對象頭中。
執(zhí)行構(gòu)造函數(shù)
2.對象的內(nèi)存布局
對象的內(nèi)存布局總共分為三個部分:
對象頭中主要包括兩部分信息:
一部分用于存儲對象自身的運行時數(shù)據(jù),如哈希碼、GC分代年齡、鎖狀態(tài)標志、線程持有的鎖、偏向線程ID、偏向時間戳等。
另一部分是類型指針,即對象指向它的類元數(shù)據(jù)的指針,虛擬機通過這個指針來確定這個對象是哪個類的實例。如果對象是Java數(shù)組,那在對象頭中還必須有一塊記錄數(shù)組長的數(shù)據(jù)。
實例數(shù)據(jù)部分是對象真正存儲的有效信息,也是程序代碼中定義的各種類型的字段內(nèi)容。從父類繼承下來的,在子類中定義的都需要記錄下來。
對齊填充僅僅起到占位符的作用。HotSpot VM的自動內(nèi)存管理系統(tǒng)要求對象起始地址是8字節(jié)的整數(shù)倍,所以對象大小必須是8字節(jié)的整數(shù)倍。當對象實例數(shù)據(jù)部分沒有對齊時,需要通過對齊填充來補
在此我向大家推薦一個Java高級群 :725633148 里面會分享一些資深架構(gòu)師錄制的視頻錄像:(有Spring,MyBatis,Netty源碼分析,高并發(fā)、高性能、分布式、微服務架構(gòu)的原理,JVM性能優(yōu)化、分布式架構(gòu)、面試資料)等這些成為架構(gòu)師必備的知識體系 進群馬上免費領取,目前受益良多!
1.對象存活判定
Java虛擬機通過可達性分析來判定對象是否存活。這個算法的基本思想是通過一系列稱為"GC Roots"的對象作為起始點,從這些節(jié)點向下搜索,搜索走過的路徑稱為引用鏈,當一個對象到GC Roots沒有與任何引用鏈相連時,則該對象是不可用的。
如圖,object5,object6,object7雖然互有關聯(lián),但是GC Roots是不可達的,所以它們被判定是可回收的對象。
另外值得一提的是引用計數(shù)算法,引用計數(shù)法是通過給對象一個引用計數(shù)器,每當有一個地方引用它時,計數(shù)器值就加一;引用失效時,計數(shù)器值就減一;任何時刻計數(shù)器為0的對象就是不可能再被使用的。引用計數(shù)器效率高、實現(xiàn)簡單。但是很難解決對象間相互循環(huán)引用的問題,主流Java虛擬機幾乎都不再使用引用計數(shù)法來管理內(nèi)存。
可達性分析示意圖
即使在可達性分析算法中不可達的對象,也不一定會立即被回收。一個對象被回收,至少要經(jīng)歷兩次標記過程。
如果對象在進行可達性分析后沒有與GC Roots相連的引用鏈,那它將會被第一次標記并進行一次篩選。篩選的條件是此對象是否有必要執(zhí)行finalize()方法。當對象沒有覆蓋finalize()方法,或finalize()方法已被虛擬機調(diào)用過,虛擬機將這兩種情況視為"沒有必要執(zhí)行"。
如果這個對象判定為有必要執(zhí)行finalize()方法,那么這個對象會放置在F-Queue隊列中,稍后由虛擬機自動建立、低優(yōu)先級的Finalizer線程去執(zhí)行finalize()方法。GC對F-Queue中的對象進行第二次小規(guī)模標記,如果對象重新與引用鏈上的任何一個對象建立關聯(lián),那么第二次標記時它將被移除"即將回收"的集合。否則對象就真的要被回收了。
Finalize方法
2.方法區(qū)回收判定
方法區(qū)的回收主要包括兩部分內(nèi)容:廢棄常量和無用的類。
廢棄常量的回收與回收Java堆中的對象類似。
判斷無用的類的條件必須滿足三個條件:
該類所有實例已經(jīng)被回收。
加載該類的ClassLoader已被回收。
該類對應的java.lang.Class對象沒有在任何地方被引用,也無法通過反射訪問該類。
3.垃圾收集算法
標記-清除算法(Mark-Sweep):
算法分為"標記"和"清除"兩個階段:首先標記出需要回收的對象,在標記完成后統(tǒng)一回收被標記的對象。它主要不足有兩個:一是效率問題,標記和清除兩個過程效率都不高。二是空間問題,標記清除后會產(chǎn)生大量不連續(xù)內(nèi)存碎片,碎片太多可能導致要分配較大對象時,無法找到足夠的內(nèi)存空間不得不提前觸發(fā)一次垃圾收集動作。
標記-清除
復制算法:
復制算法將可用內(nèi)存按容量劃分為大小相等的兩塊,每次只使用其中一塊。當一塊內(nèi)存用完了,將存活的對象復制到另一塊上面,然后把已使用的內(nèi)存空間一次清理掉。這樣使得每次都是對整個半?yún)^(qū)進行內(nèi)存回收,內(nèi)存分配時也就不用考慮內(nèi)存碎片等情況,只要移動堆頂指針,按順序分配內(nèi)存即可,實現(xiàn)簡單,運行高效。只是這種算法將內(nèi)存縮小為原來的一半,代價較高。
復制算法
標記-整理算法(Mark-Compact):
標記過程與"標記-清除"算法一樣,但后續(xù)不是直接對可回收對象進行清理,而是讓所有存活的對象都向一端移動,然后直接清理掉端邊界以外的內(nèi)存
標記-整理算法
4.分代收集算法
商業(yè)虛擬機的垃圾收集都采用分代收集算法,根據(jù)對象存活周期將內(nèi)存劃分為幾塊。Java堆分為新生代和老年代,這樣可以根據(jù)年代特點采用適當?shù)氖占惴?。新生代中每次垃圾收集都有大批對象死去,那就選用復制算法。老年代對象存活率高,沒有額外空間進行分配擔保,適合使用"標記-清理"或"標記-整理"算法來回收。
4.內(nèi)存分配與回收策略
對象優(yōu)先在Eden分區(qū):
大多數(shù)情況下,對象在新生代Eden區(qū)中分配。當Eden區(qū)沒有足夠空間分配時,虛擬機發(fā)起一次Minor GC。GC后對象嘗試放入Survivor空間,如果Survivor空間無法放入對象時,只能通過空間分配擔保機制提前轉(zhuǎn)移到老年代。
大對象直接進入老年代:
大對象指需要大量連續(xù)內(nèi)存空間的Java對象。虛擬機提供-XX:PretenureSizeThreshold參數(shù),如果大于這個設置值對象則直接分配在老年代。這樣可以避免新生代中的Eden區(qū)及兩個Survivor區(qū)發(fā)生大量內(nèi)存復制。
長期存活的對象進入老年代:
虛擬機會給每個對象定義一個對象年齡計數(shù)器。如果對象在Eden出生并且經(jīng)過一次Minor GC后任然存活,且能夠被Survivor容納,將被移動到Survivor空間中,并且對象年齡設為1.每次Minor GC后對象任然存活在Survivor區(qū)中,年齡就加一,當年齡到達-XX:MaxTenuringThreshold參數(shù)設定的值時,將會移動到老年代。
動態(tài)年齡判斷:
虛擬機不是永遠要求對象的年齡必須達到-XX:MaxTenuringThreshold設定的值才會將對象移動到老年代去。如果Survivor中相同年齡所有對象大小的總和大于Survivor空間的一半,年齡大于或等于該年齡的對象可以直接進入老年代。
空間分配擔保:
在Minor GC前,虛擬機會檢查老年代最大可用連續(xù)空間是否大于新生代所有對象總空間,如果條件成立,那么Minor GC是成立的。如果不成立,虛擬機查看HandlePromotionFailure設置值是否允許擔保失敗。如果允許,那么會繼續(xù)檢查老年代最大可用連續(xù)空間是否大于歷次移動到老年代對象的平均大小,如果大于,將嘗試一次Minor GC。如果小于,或者HandlePromotionFailure設置值不允許冒險,那將進行一次Full GC。
新生代GC(Minor GC):發(fā)生在新生代的垃圾收集動作,因為Java對象大多朝生夕死,所以Minor GC非常頻繁,回收速度也較快。
老年代GC(Major GC/Full GC):發(fā)生在老年代的垃圾收集動作。出現(xiàn)Major GC,經(jīng)常會伴隨至少一次Minor GC。Major GC的速度一般比Minor GC慢10倍以上。
“Java內(nèi)存分配與回收機制是什么”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關的知識可以關注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實用文章!
免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權內(nèi)容。