JVM內存模型及垃圾收集

一 JVM內存模型

1.1 Java棧

Java棧是與每一個線程關聯的,JVM在創建每一個線程的時候,會分配一定的棧空間給線程。它主要用來存儲線程執行過程中的局部變量,方法的返回值,以及方法調用上下文。棧空間隨着線程的終止而釋放。如果在線程執行的過程中,棧空間不夠用,那麼JVM就會拋出此異常:StackOverflowError,這種情況一般是死遞歸造成的。

1.2 堆

Java中堆是由所有的線程共享的一塊內存區域,堆用來保存各種JAVA對象,比如數組,線程對象等。

下圖是我比較喜歡的一張java內存架構圖


還有一張常見的圖如下

不管如何,表達的意思都是一個意思
1.2.1 Generation

JVM一般又可以分爲以下三部分:

◆ Perm

Perm代主要保存class,method,filed對象,這部分的空間一般不會溢出,除非一次性加載了很多的類,不過在涉及到熱部署的應用服務器的時候,有時候會遇到java.lang.OutOfMemoryError : PermGen space 的錯誤,造成這個錯誤的很大原因就有可能是每次都重新部署,但是重新部署後,類的class沒有被卸載掉,這樣就造成了大量的class對象保存在了perm中,這種情況下,一般重新啓動應用服務器可以解決問題。

◆ Tenured

Tenured區主要保存生命週期長的對象,一般是一些老的對象,當一些對象在Young複製轉移一定的次數以後,對象就會被轉移到Tenured區,一般如果系統中用了application級別的緩存,緩存中的對象往往會被轉移到這一區間。

◆ Young

Young區被劃分爲三部分,Eden區和兩個大小嚴格相同的Survivor區,其中Survivor區間中,某一時刻只有其中一個是被使用的,另外一個留做垃圾收集時複製對象用,在Eden區間變滿的時候,minor GC就會將存活的對象移到空閒的Survivor區間中,根據JVM的策略,在經過幾次垃圾收集後,任然存活於Survivor的對象將被移動到Tenured區間。注:1.From Survivor和To survivor的角色是不斷的變化的,同一時間只有一塊空間處於使用狀態,這個空間就叫做From Survivor區,當複製一次後角色就發生了變化。2 如果複製的過程中發現To survivor空間已經滿了,那麼就直接複製到old generation.3比較大的對象也會直接複製到Old generation,在開發中,我們應該儘量避免這種情況的發生。

1.2.2 Sizing the Generations

JVM提供了相應的參數來對內存大小進行配置。正如上面描述,JVM中堆被分爲了3個大的區間,同時JVM也提供了一些選項對Young,Tenured的大小進行控制。

◆ Total Heap

-Xms :指定了JVM初始啓動以後初始化堆內存

-Xmx:指定JVM堆的最大內存,在JVM啓動以後,會分配-Xmx參數指定大小的內存給JVM,但是不一定全部使用,JVM會根據-Xms參數來調節真正用於JVM的內存

-Xmx -Xms之差就是三個Virtual空間的大小

◆ Young Generation

-XX:NewRatio=8意味着tenured 和 young的比值8:1,這樣eden+2*survivor=1/9

-XX:SurvivorRatio=32意味着eden和一個survivor的比值是32:1,這樣一個Survivor就佔Young區的1/34.

-Xmn 參數設置了年輕代的大小

◆ Perm Generation

-XX:PermSize=16M 非堆內存起始值

-XX:MaxPermSize=64M 非堆內存最大值

Thread Stack 線程棧值

-XX:Xss=128K

二 垃圾收集

2.1 垃圾收集策略(這邊主要介紹分代GC策略)

先簡單說下內存分配(申請)過程

1 JVM會試圖爲相關Java對象在Eden中初始化一塊內存區域;
2 當Eden空間足夠時,內存申請結束。否則到下一步;
3 JVM試圖釋放對Eden中所有不活躍的對象minor collection,釋放後若Eden空間仍然不足以放入新對象,則試圖將部分Eden中活躍對象放入Survivor區;
4 Survivor區被用來作爲Eden及OLD的中間交換區域,當OLD區空間足夠時,Survivor區的對象會被移到Old區,否則會被保留在Survivor區;
5 當OLD區空間不夠時,JVM會在OLD區進行major collection;
6 完全垃圾收集後,若Survivor及OLD區仍然無法存放從Eden複製過來的部分對象,導致JVM無法在Eden區爲新對象創建內存區域,則出現”Out of memory錯誤”

GC的執行時要耗費一定的CPU資源和時間的,在JDK1.2以後,JVM引入了分代收集的策略,其中對新生代採用”Mark-Compact”策略,而對老生代採用了“Mark-Sweep”的策略。用較高的頻率對年輕的對象(young generation)進行掃描和回收,這種叫做minor collection,Minor collection的過程就是將eden和在用survivor space中的活對象copy到空閒survivor space中。對象在young generation裏經歷了一定次數的minor collection後,年紀大了,就會被移到old generation中,稱爲tenuring。而對老對象(old generation)的檢查回收頻率要低很多,稱爲”Full Gc 或者Major GC”.其中用System.gc()強制執行的是Full Gc。這樣就不需要每次GC都將內存中所有對象都檢查一遍,以便讓出更多的系統資源供應用系統使用。GC不會在主程序運行期對PermGen Space進行清理,所以如果你的應用中有很多CLASS的話,就很可能出現PermGen Space錯誤。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章