java内存分配和垃圾回收,Java内存分配与垃圾回收
1.JVM管理的內存包含下圖所示的幾個運行時數據區域,其中方法區和堆為線程共享的數據區域,程序計數器,虛擬機棧以及本地方法棧為線程私有的數據區域。
程序計數器:可以看做是當前線程所執行的字節碼的行號指示器,告訴字節碼解釋器該讀取哪條指令
虛擬機棧:生命周期和線程相同,每個方法在執行的同時都會創建一個棧幀,用于存儲局部變量表,操作數棧,動態鏈接,方法出口等信息,每一個方法從調用到完成的過程就對應了一個棧幀在虛擬機中入棧到出棧的過程。棧中存放了編譯器可知的各種基本數據類型和對象引用。
本地方法棧:與虛擬機棧十分類似,區別在于本地方法棧是為Native方法服務。
堆:堆中存放對象實例。幾乎所有的對象實例都在這里分配。堆是垃圾回收器管理的主要區域。從內存回收的角度來看,由于現有垃圾回收器基本都采用分代收集算法,所以堆可以細分為新生代和老年代,再細致一點可以分為:Eden空間,From Survivor 空間,To Survivor空間。
方法區:用于存儲被虛擬機加載的類信息,常量,靜態變量,即時編譯器編譯后的代碼等數據。(HotSpot虛擬機的將GC分代收集擴展至方法區,使用永久代來實現方法區。因此存在把方法區看成永久代)。
運行時常量池:方法區的一部分,用于存放編譯器生成的各種字面量和符號引用。
2.對于字符串,其對象的引用存儲在棧中,如果是編譯期已經創建好(直接用雙引號定義的)的就存儲在常量池中,如果是運行期(new出來的)才能確定的就存儲在堆中 。對于equals相等的字符串,在常量池中永遠只有一份,在堆中有多份。對于通過new產生一個字符串時,會先去常量池中查找是否已經有了此對象,如果沒有則在常量池中創建一個此字符串對象,然后堆中再創建一個常量池中此對象的拷貝對象。例如:String s = new String(“xyz”);如果常量池中原來沒有”xyz”,就會產生兩個對象。
3.如何判斷對象已死:
(1)引用計數法:給每個對象添加一個引用計數器,有引用計數器+1,引用失效計數器-1,計數器為0則對象不可用。缺點是無法解決循環引用的問題。
(2)可達性分析法:當一個對象到GC Roots沒有任何引用鏈相連(類似樹的葉子節點到根節點之間斷開連接),則此對象不可用。
4.即使在可達性分析算法中不可達的對象也不是非死不可的,這時候他們暫時處于‘緩刑‘階段,要真正宣告一個對象的死亡至少要經歷兩次標記過程:
如果對象在可達性分析時發現不可達,將會被第一次標記并且進行一次篩選,篩選的條件是此對象是否有必要執行finalize方法(當對象沒有重寫父類的 finalize 方法或者 finalize 方法已經被調用過,虛擬機就認為沒必要執行finalize方法,值得注意的是,對象的finalize方法只會被調用一次),如果這個對象被判定為有必要執行 finalize 方法,那么這個對象將會被放置到一個叫做F-Queue 的隊列中,并在稍后由一個虛擬機自動建立的,低優先級的finalizer線程去執行它(虛擬機出發finalize方法,但不一定會等待它執行完成)。稍后GC將對 F-Queue 中的對象進行第二次小規模的標記,如果對象仍被標記為回收(如果對象在第二次標記之前與其他可達對象建立了引用關系,則可以‘逃過一劫‘),則該對象基本上就真的被回收了。
5.垃圾回收一般發生在堆上,然而方法區也可能發生垃圾回收。在堆中,尤其是新生代中,常規應用進行一次垃圾收集一般可以回收 70%~ 95%的空間,而永久代垃圾收集效率遠低于此。永久代的垃圾收集主要收集主要回收兩部分內容:廢棄常量或者無用的類。判斷常量不可用較簡單(沒有任何對象引用常量池中的這個常量),而判定一個類是無用的類則條件嚴苛的多:
(1).該類的所有實例均被回收
(2).加載該類的 ClassLoader 已經被回收
(1).該類對應的 java.lang.Class 對象沒有在任何地方被使用,無法再任何地方通過反射訪問該類的方法。
6.垃圾回收算法:
標記-清除算法:分為標記,清除兩個階段,首先標記出所有需要回收的對象,標記完成后統一回收所有被標記的對象。此算法有兩個不足:一是兩個過程的效率都不高,二是標記清除后會產生大量不連續的內存碎片(當需要分配大對象時無法找到足夠的連續內存而不得不提前觸發另一次垃圾回收動作)。
復制算法:將可用內存分為大小相等的兩塊,每次只使用其中的一塊,當一塊的內存用完了就把存活的對象復制到另一塊,然后將已使用的內存空間一次清理掉。這種算法簡單高效,但是不足在于將內存縮小了一般。
標記-整理算法:標記過程和標記-清除算法一樣,但是后續不是直接清除內存,而是讓存活的對象都向一邊移動,然后清除掉邊界意外的內存。
分代收集算法:根據對象存活周期的不同將內存劃分為幾塊,一般是把 Java 堆分為新生代和老年代。現在的商業虛擬機普遍使用復制算法回收新生代,由于新生代的對象絕大部分都是“朝生夕死”的,所以并不需要按1:1的比例劃分內存空間,而是分為較大的Eden區和兩個較小的Survivor區,每次使用Eden和一塊Survivor,GC時將存活的對象復制到另外一塊Survivor(From Survivor),最后清理掉剛才使用的Eden和Survivor(To Survivor).HotSpot虛擬機默認Eden和Survivor比例為8:1,也就是每次新生代中可用的內存空間為整個新生代的90%(80%+10%)。當Survivor(To Survivor)內存不夠用時,需要依賴其他內存進行分配擔保(老年代)。復制算法在對象存活率較高時效率低下而且如果不想浪費50%內存還需要額外的空間驚醒分配擔保,因此不適合老年代這種存在大量存活對象的區域,所以一般使用標記-整理算法回收老年代。
7.內存分配與回收策略:
1.Java 體系中所提倡的自動內存管理最終可以歸結為兩個問題:給對象分配內存以及回收分配給對象的內存。對象主要分配在新生代的Eden區上,少數情況下也可能直接分配在老年代中。
(1)大對數情況下,對象在新生代Eden區中分配,當Eden區沒有足夠的空間進行分配時,虛擬機將會發起一次 Minor GC.
(2).大對象直接進入老年代,所謂的大對象是指需要大量連續內存空間的對象,最典型的大對象就是那種很長的字符串以及數組
(3)長期存活的對象將進入老年代。如果對象在Eden出生經過第一次 Minor GC 后仍然存活,并且能被Survivor容納的話,將被移動到Survivor空間中,并且對象年齡設為1,對象每熬過一次 Minor GC,年齡就增加1歲,當他的年齡增加到一定程度(默認15歲)就會晉升到老年代中。虛擬機并不是永遠要求對象的年齡達到 Max Tenuring Threshhold 才能晉升老年代,如果在Survivor空間中相同年齡所有對象的大小的總和等于Survivor空間的一般,年齡大于等于該年齡的對象就可以直接進入老年代,無需等到Max Tenuring Threshhold 中要求的年齡。
(4)在發生 Minor GC 之前,虛擬機會先檢查老年代最帶可用的連續空間是是否大于新生代所有對象總空間,如果這個條件成立,那么 Minor GC 可以確保是安全的。
原文:http://www.cnblogs.com/pepper7/p/7108431.html
總結
以上是生活随笔為你收集整理的java内存分配和垃圾回收,Java内存分配与垃圾回收的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 中国人2020年平均初婚年龄28.67岁
- 下一篇: 股价再次大涨!“万亿宁王”三喜临门