目錄
JVM內存模型
1、棧
Java棧是與每一個線程關聯的,JVM在創建每一個線程的時候,會分配一定的棧空間給線程。存儲局部變量、引用、方法、返回值等。
StackOverflowError:如果在線程執行的過程中,棧空間不夠用,那麼JVM就會拋出此異常,這種情況一般是死遞歸造成的。
2、堆
Java中堆是由所有的線程共享的一塊內存區域,堆用來保存各種JAVA對象,比如數組,線程對象等。
2.1、堆的分代
JVM堆一般分爲三個部分:
Young:年輕代
Young區被劃分爲三部分,Eden區和兩個大小嚴格相同的Survivor區,其中Survivor區間中,某一時刻只有其中一個是被使用的,另外一個留做垃圾收集時複製對象用,在Young區間變滿的時候,minor GC就會將存活的對象移到空閒的Survivor區間中,根據JVM的策略,在經過幾次垃圾收集後,任然存活於Survivor的對象將被移動到Tenured區間。
Tenured:年老代
Tenured區主要保存生命週期長的對象,一般是一些老的對象,當一些對象在Young複製轉移一定的次數以後,對象就會被轉移到Tenured區,一般如果系統中用了application級別的緩存,緩存中的對象往往會被轉移到這一區間。
Perm:持久代
Perm代主要保存class,method,filed等對象,這部門的空間一般不會溢出。
java.lang.OutOfMemoryError PermGen space 的錯誤,造成這個錯誤的很大原因就有可能是每次都重新部署,但是重新部署後,類的class沒有被卸載掉,這樣就造成了大量的class對象保存在了perm中,這種情況下,一般重新啓動應用服務器可以解決問題。
2.2、堆的大小
-Xmx:指定JVM堆得最大內存,在JVM啓動以後,會分配-Xmx參數指定大小的內存給JVM,但是不一定全部使用,JVM會根據-Xms參數來調節真正用於JVM的內存
-Xms :指定了JVM初始啓動以後初始化內存
-Xmx -Xms之差就是三個Virtual空間的總大小(年輕代、年老代、持久代)
3、棧與堆的比較:
棧是運行時的單位,而堆是存儲的單位。
棧解決程序的運行問題,即程序如何執行,或者說如何處理數據;堆解決的是數據存儲的問題,即數據怎麼放、放在哪兒。在Java中一個線程就會相應有一個線程棧與之對應,這點很容易理解,因爲不同的線程執行邏輯有所不同,因此需要一個獨立的線程棧。而堆則是所有線程共享的。
對象存放在堆中,對象引用和基本類型存放在棧中;對象的屬性存放在堆中,對象的方法存在棧中。
程序運行永遠都是在棧中進行的,因而參數傳遞時,只存在傳遞基本類型和對象引用的問題。不會直接傳對象本身。
在JVM中,靜態屬性保存在Stack指令內存區,動態屬性保存在Heap數據內存區
JVM垃圾回收機制
分代垃圾回收
不同的對象生命週期不同。與業務信息有關的對象生命週期較長,如Http請求中的session對象、線程、socket連接。程序運行過程中的臨時變量生命週期較短,如String對象。
把不同生命週期的對象放在不同代上,不同代上採用最合適它的垃圾回收方式進行回收。JVM中共劃分爲三個代:年輕代、年老代和持久代,其中持久代主要存放Java類的類信息,與垃圾收集要收集的Java對象關係不大,年輕代和年老代的劃分是對垃圾收集影響較大的。
年輕代:存放所有新生成的對象;
年老代:在年輕代中經歷了N次垃圾回收仍然存活的對象,將被放到年老代中,故都是一些生命週期較長的對象;
持久代:用於存放靜態文件,如Java類、方法等。(持久代對垃圾回收沒有顯著影響,但是有些應用可能動態生成或者調用一些class,例如Hibernate等,在這種時候需要設置一個比較大的持久代空間來存放這些運行過程中新增的類。)
新生代的垃圾收集器命名爲“minor gc”,老生代的GC命名爲Full Gc 或者Major GC.其中用System.gc()強制執行的是Full Gc.
判斷對象是否需要回收的方法有兩種:
1.引用計數
引用計數存儲對特定對象的所有引用數,也就是說,當應用程序創建引用以及引用超出範圍時,jvm必須適當增減引用數。當某對象的引用數爲0時,便可以進行垃圾收集。
2.對象引用遍歷
早期的jvm使用引用計數,現在大多數jvm採用對象引用遍歷。對象引用遍歷從一組對象開始,沿着整個對象圖上的每條鏈接,遞歸確定可到達(reachable)的對象。如果某對象不能從這些根對象的一個(至少一個)到達,則將它作爲垃圾收集。在對象遍歷階段,gc必須記住哪些對象可以到達,以便刪除不可到達的對象,這稱爲標記(marking)對象。
下一步,gc要刪除不可到達的對象。刪除時,有些gc只是簡單的掃描堆棧,刪除未標記的未標記的對象,並釋放它們的內存以生成新的對象,這叫做清除(sweeping)。這種方法的問題在於內存會分成好多小段,而它們不足以用於新的對象,但是組合起來卻很大。因此,許多gc可以重新組織內存中的對象,並進行壓縮(compact),形成可利用的空間。
爲此,gc需要停止其他的活動活動。這種方法意味着所有與應用程序相關的工作停止,只有gc運行。結果,在響應期間增減了許多混雜請求。另外,更復雜的gc不斷增加或同時運行以減少或者清除應用程序的中斷。有的gc使用單線程完成這項工作,有的則採用多線程以增加效率。
幾種垃圾回收機制
1.標記-清除收集器
這種收集器首先遍歷對象圖並標記可到達的對象,然後掃描堆棧以尋找未標記對象並釋放它們的內存。這種收集器一般使用單線程工作並停止其他操作。
2.標記-壓縮收集器
有時也叫標記-清除-壓縮收集器,與標記-清除收集器有相同的標記階段。在第二階段,則把標記對象複製到堆棧的新域中以便壓縮堆棧。這種收集器也停止其他操作。
3.複製收集器
這種收集器將堆棧分爲兩個域,常稱爲半空間。每次僅使用一半的空間,jvm生成的新對象則放在另一半空間中。gc運行時,它把可到達對象複製到另一半空間,從而壓縮了堆棧。這種方法適用於短生存期的對象,持續複製長生存期的對象則導致效率降低。
4.增量收集器
增量收集器把堆棧分爲多個域,每次僅從一個域收集垃圾。這會造成較小的應用程序中斷。
5.分代收集器
這種收集器把堆棧分爲兩個或多個域,用以存放不同壽命的對象。jvm生成的新對象一般放在其中的某個域中。過一段時間,繼續存在的對象將獲得使用期並轉入更長壽命的域中。分代收集器對不同的域使用不同的算法以優化性能。
6.併發收集器
併發收集器與應用程序同時運行。這些收集器在某點上(比如壓縮時)一般都不得不停止其他操作以完成特定的任務,但是因爲其他應用程序可進行其他的後臺操作,所以中斷其他處理的實際時間大大降低。
7.並行收集器
並行收集器使用某種傳統的算法並使用多線程並行的執行它們的工作。在多cpu機器上使用多線程技術可以顯著的提高java應用程序的可擴展性。