您好,登錄后才能下訂單哦!
這篇文章主要講解了“Java虛擬機的內存管理方式”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“Java虛擬機的內存管理方式”吧!
一說到內存管理,首先需要了解它的內存模型。虛擬機的內存模型在jdk1.8之后有了一些變化,我們分開來看,請看下圖:
由圖我們可以看出,jdk每個版本都會有新生代和老年代,唯一不同的是小于1.8的版本為永久代,而大于等于1.8的版本去掉了永久代,轉為元空間(Meta Space)。
永久代也就是存儲的數據區里面的方法區,如果程序在運行中發生PermSpace溢出,則說明永久代內存不夠,需要調整JVM參數增加永久代內存空間。
jdk1.8以后出現了MetaSpace,它和永久代不同的是,它的內存空間是動態擴展的,當然我們也可以設置MaxMetadaSpace來設置最大元空間內存數量,也就是在1.8以后設置PermSize是無效的。
本文主要講解jdk1.8以前得內存管理機制
在現代編程語言中,對垃圾回收算法主要有兩種方式:引用計數器和可達性分析。
引用計數的原理大致是這樣的:為每一個創建的對象設置一個引用計數,每當對象被引用一次后,引用計數加1,當對象引用失效時,引用計數減1,當引用計數為0是說明沒有任何對象引用了,即可釋放該對象。
引用計數的一個弊端是,他無法解決對象間相互引用的問題,比如下面這段代碼:
public class RefrenceCountingGC { private Object instance = null; public static void main(String[] args) { RefrenceCountingGC gc1 = new RefrenceCountingGC(); RefrenceCountingGC gc2 = new RefrenceCountingGC(); gc1.instance = gc2; gc2.instance = gc1; gc1 = null; gc2 = null; System.gc(); } }
兩個對象始終處于相互引用階段,因為引用計數永遠無法為0,因此就不能自動釋放它。
為了解決引用計數出現的這些問題,可達性分析算法出現了。
在主流的編程語言中,java和c#都是通過可達性分析來判定對象是否存活的。
它的原理大致是:通過一系列的被稱為“GC Roots”的對象作為起始點,然后從這些節點開始向下搜索,搜索的路徑連成的一條線,我們稱之為“引用鏈”,當前一個對象到GC Roots沒有任何引用鏈,即我們說的這個對象不可達時,則說明該對象是可以被回收的,通過下圖可以更好的理解:
當一個對象不可達時,并不能代碼這個對象就能馬上被回收,他會處于死緩狀態,而一個對象真正要回收時,至少需要經歷兩次標記。第一次標記的前提條件是,看該對象是否覆蓋了finalize()方法或者finalize()方法被虛擬機調用過,如果覆蓋了finalize()方法并且虛擬機還沒有調用過,這時會標記它,該對象還有機會存活,方法很多,比如在finalize()方法內存引用該對象。
如果進行第二次回收時,由于虛擬機已經調用過finalize()方法,就不會再調用他了,這時該對象就會真正宣告死亡了。
請看下面這段代碼:
public class GCRoot { private static GCRoot instance = null; @Override protected void finalize() throws Throwable { super.finalize(); System.out.println("finalized執行"); instance = this; } public static void main(String[] args) throws Exception{ instance = new GCRoot(); instance = null; System.gc(); Thread.sleep(500); if(null != instance){ System.out.println("對象拯救成功!"); }else{ System.out.println("對象被釋放!"); } instance = null; System.gc(); if(null != instance){ System.out.println("對象拯救成功!"); }else{ System.out.println("對象被釋放!"); } } }
運行結果: finalized執行 對象拯救成功! 對象被釋放!
jdk1.2之前的引用很簡單,這里我們不探討,我們主要探討jdk1.2之后的引用。
java中將引用分為了:強引用、軟引用、弱引用和虛引用。
強引用在java程序中最常見的一種引用類型,類似Object o = new Object()這類引用,只要強引用還在,垃圾回收器就永遠不會回收它。
軟引用通常用來描述一些可以用但非必須的對象,在內存溢出之前會先回收掉軟引用相關聯的對象,如果回收后內存依然不夠,則才會拋出內存溢出異常。
弱引用用來描述一些非必須的對象,但是它的強度比軟引用還有弱一些。被弱引用關聯的對象只能存活到下次垃圾回收器工作之前,當垃圾回收器開始工作時,無論當前內存是否足夠,都會回收掉被弱引用關聯的對象。
虛引用是最弱的一種引用類型,為對像設置虛引用關系的唯一目的就是能在這個對象被垃圾回收之時收到一個系統通知。
這是最基礎的一種垃圾收集算法,后續所有的算法都是在這個算法的基礎上進行擴展。
通過算法的名字大致能夠看出,該算法分為了“標記”和“清除”兩個階段:首先需要標記出需要回收的所有對象,待標記完成后清除掉所有標記過的對象。這個算法的不足主要有兩個:一是性能問題,標記和清除兩個階段的性能都不高,二是標記清除后會產品不連續的大量內存碎片,內存碎片太多會導致下一次在需要分配占用大量內存的對象時,無法找到足夠的連續碎片而不得不再一次觸發垃圾收集動作。
為了解決效率問題,復制算法出現了。這種算法會將可用內存區域劃分為大小相同的兩塊,當需要垃圾回收時,會先將可用的對象復制到另一個內存區域,從而將當前區域一次性清除。這樣做的好處是每次都將一整塊內存區域清除掉,從而避免了大量的內存碎片出現。
目前主流的商用虛擬機大多是采用復制算法來回收新生代。
當對象的存活率較高時采用復制算法,效率就會很低,因此對于老年代一般不采用復制算法。
鑒于這種問題,一種稱之為“標記-整理”算法的思路出現了。它和“標記-清除”算法一樣,都需要先進行標記,但是它不會簡單一次性清除標記的對象,而是將所有存活對象都移動到另一端,然后清除掉邊界外的對象。
分代收集其實就是將內存劃分為幾塊,比如將java堆劃分為新生代和老年代,根據不用年代采取最適合的算法,所以目前主流商用虛擬機都是采用這種方式。
感謝各位的閱讀,以上就是“Java虛擬機的內存管理方式”的內容了,經過本文的學習后,相信大家對Java虛擬機的內存管理方式這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。