JVM 內存管理和JVM 垃圾回收

JVM 內存結構由堆、棧、本地方法棧、方法區等部分組成,結構圖如下所示:


1、堆

JAVA_OPTS="

-server 

-Xms5G  最大堆內存

-Xmx5G  初始堆內存

"

所有通過new 創建對象內存都在堆中分配,其大小可以通過-Xms -Xmx 來控制,堆被劃分新生代和舊生代,新生代又被進一步劃分爲Eded和Survivor,最後Survivor 由FromSpace 和ToSpace組成

-Xmn 新生代內存大小

 新生代,新建的對象都是用新生代分配內存,Eden 空間不做的時候,會把存活的對象轉移到Survivor 中新生代大小可以由-Xmn 來控制,也可以用-XX:SurvivorRatio(存活比例)來控制Eden 和Survivor(存活)的比例舊生代。用於存放新生代多次回收仍然存活的對象

2、棧

每個線程執行每個方法的時候都會在棧中申請一個棧幀每個棧幀包括局部變量和操作數棧,用於存放此方法調用過程中的臨時變量,參數和中間結果。

3)、本地方法棧

-XX:PermSize=500M持久代

存放了要加載的類信息,靜態變量、final(決定性)類型的常量。屬性和方法信息,JVM 用持久代(

PermanetGeneration) 用來存放方法區,可通過-XX:PermSize=500M 、-XX:MaxPermSize=500M 來指定最小值和最大值

 

JVM 垃圾回收機制

 JVM 分別對新生代和舊生代採用不同的垃圾回收機制

新生代通常存活時間較短,因此基於copying 算法拉進行回收,所謂copying 算法就是掃描出存活對象,並複製到一塊新的完全未使用的空間,對於新生代,就是eden 和fromspace 或tospace 之間copy 新生代採用空閒指針的方式來控制GC 觸發指針保持最後一個分的對象在新生代 區間的位置,當新的對象要分配內存時,用於檢測空間是否足夠,不夠就觸發GC 當連續分配對象時,對象會逐漸從eden 到survivor 到最後的舊生代用javavisualVM 來查看,能明顯觀察到新生代滿了後,會把對象轉移到舊生代,然後清空繼續裝載舊生代也滿了之後就會報outmemory 的異常如下圖:

在執行機制上,JVM 提供了串行GC(SerialGC)、並行回收GC(ParallelScavenge) 和並行GC(ParNew)

  1. 串行GC

    在整個掃描和複製過程中採用單線程的方式來進行,使用於單CPU 、新生代空間較小及對暫時時間要求不是非常高的應用上,是cline 級別默認的GC 方式,可以通過-XX:UseSerialGC(連續的)來強制指定。

  2. 並行回收GC

    在整個掃描和複製過程採用多線程的方式進行,適用於多cpu 對暫時要求較短的應用上,是server級默認採用的GC 方式可用-XX:UseParallelGC (並行的)來強制指定,用-XX:ParallelGCThreads=4來指定線程數

  3. 並行GC

    與舊生代的併發GC 配合使用

    舊生代的GC 舊生代與新生代不同,對象存活時間較長,比較穩定,因此採用標記(Mark)算法來進行回收,所謂標記就是掃描出來存活的對象,然後在進行回收未標記的對象, 回收後對用空出的空間要麼進行合併,要麼標記出來便於下次進行分配,總之要減少內存碎片帶來的效率損耗。

    在執行機制上JVM 提供了串行GC(SerialMSC) 、並行(parallelMSC) 和併發GC(CMS)具體算法細節還有待進一步深入研究。

以上各種GC機制是需要組合使用的,指定方式由下表所示:

GC機制組合使用

GC機制組合使用

[root@order bin]# cat setenv.sh 

#!/bin/sh

export JAVA_HOME=/usr/local/jdk1.7.0_60/

export JRE_HOME=/usr/local/jdk1.7.0_60/

JAVA_OPTS="-server

-Xms5G                                                             ## 堆內存初始值              

-Xmx5G                                                             ##堆內存最大值

-Xmn2G                                                             ##新生代空間

-Dsun.java2d.noddraw=true                              ##如果硬件加速已經被enable,可以通過這個選項來提高Swing GUI速度,默認值爲false

-XX:PermSize=500M                                         ## 表示非堆區初始內存分配大小,其縮寫爲permanent size(持久化內存)                            

-XX:MaxPermSize=500M                                  ##-XX:MaxPermSize:表示對非堆區分配的內存的最大上限。                            

-Xss256k                                                           ##設置每個線程的堆棧大小

-XX:MaxTenuringThreshold=0                          ##在新生代中對象存活次數(經過MinorGC次數)後仍然存活,就會晉升到舊生代

-XX:+UseParNewGC                                        ##設置年輕代多線程收集,可與CMS 收集同時使用。在serial 基礎上實現的多線程收集器 

-XX:+UseConcMarkSweepGC                          ##併發標記清楚(CMS)收集器:cms 收集器 也被稱爲短暫停頓併發收集器。它是對年老代進行垃圾收集的。因爲是多線程進行垃圾收回,可與減少停頓時間

-XX:+UseCMSCompactAtFullCollection           ## 表示觸發FGC 之後進行壓縮,因爲CMS 默認不壓縮空間的

-XX:CMSFullGCsBeforeCompaction=0            ##,在上一次CMS併發GC執行過後,到底還要再執行多少次full GC纔會做壓縮。默認是0,也就是在默認配置下每次CMS GC頂不住了而要轉入full GC的時候都會做壓縮。 把CMSFullGCsBeforeCompaction配置爲10,就會讓上面說的第一個條件變成每隔10次真正的full GC才做一次壓縮(而不是每10次CMS併發GC就做一次壓縮,目前VM裏沒有這樣的參數)。這會使full GC更少做壓縮,也就更容易使CMS的old gen受碎片化問題的困擾。

-XX:+CMSClassUnloadingEnabled                  ##如果你啓用了CMSClassUnloadingEnabled ,垃圾回收會清理持久代,移除不再使用的classes。這個參數只有在 UseConcMarkSweepGC  也啓用的情況下才有用

-XX:-CMSParallelRemarkEnabled                    ##降低標記停頓  表示並行remark           

-XX:CMSInitiatingOccupancyFraction=90         ##使用cms作爲垃圾回收,使用70%後開始CMS收集,爲了保證不出現promotion failed(見下面介紹)錯誤

-XX:SoftRefLRUPolicyMSPerMB=0                  ##在最後一次引用時,軟可達對象將保持一定的時間 等待多少秒

-XX:LargePageSizeInBytes=128M                   ##設置用於Java堆的大頁面尺寸

-XX:+UseFastAccessorMethods                       ##原始類型的快速優化

-XX:+UseCMSInitiatingOccupancyOnly            ##使用手動定義初始化定義開始CMS收集

-XX:+PrintClassHistogram                                ##在垃圾收集之前執行

-XX:+PrintGCDetails                                         ##GC 輸出

 -XX:+PrintGCTimeStamps                               ##GC 輸出

 -XX:+PrintHeapAtGC                                       ##打印GC前後的詳細堆棧信息

-Xloggc:${CATALINA_HOME}/logs/gc.log"       ##把相關日誌信息記錄到文件以便分析.與上面幾個配合使用

要了解Java垃圾收集機制,先理解JVM內存模式是非常重要的。今天我們將會了解JVM內存的各個部分、如何監控以及垃圾收集調優。

Java(JVM)內存模型

正如你從上面的圖片看到的,JVM 內存被分成多個獨立的部分,廣泛的說,JVM 堆內存被分爲兩部分---年輕代(young Generation)和老年代(old Generation)

年輕代

年輕代是所有新對象產生的地方,當年輕代內存空間被用完時,這個垃圾回收叫做MinorGC 年輕代,被分爲3個部分---Enden 區和兩個Survivor區

年輕代空間的要點:

        大多數新建的對象都位於Eden區

        當Eden區 被對象填滿時,就會執行Minor GC並吧所有存活下來的對象轉移到其中一個survivor 區

        Minor GC 同樣會檢查存活下來的對象,並把它們轉移另一個survivor區,這樣在一段時間內,總會有一個空的survivor 區

        經過多次GC週期後,任然存活下來的對象會被轉移到年老代內存空間,通常這是在年輕代有資格提升到年老代前通過設定年齡閾值來完成的


年老代

    年老代內存裏包含長期存活的對象和經過多次Minor GC 後依然存活下來的對象,通常會在老年代內存被佔滿時進行垃圾回收。老年代的垃圾收集叫做Major GC 。Major GC 會發給更多的時間

Stop the World 事件

    所有的垃圾收集都是“Stop the World” 事件,因爲所有的應用線程都會停下來直到操作完成(所以叫 Stop the World)

    因爲年輕代裏的對象都是一些臨時(short-lived)對象,執行Minor GC 非常快,所以應用不會受到(“Stop the World”)影響。

   由於Major GC 會檢查所有存活的對象因此會花費更長的時間,應該儘量減少Major GC 會在垃圾回收期間讓你的應用反應遲鈍,所以如果你有一個需要快速響應發生多次Major GC,你就會看到超時錯誤。

 垃圾回收時間取決於垃圾回收策略,這就是爲什麼有必要去監控垃圾收集和對垃圾收集進行調優,從而避免要求快速響應的應用出現超時錯誤。

永久代

永久代或者“Perm Gen”包含了JVM需要的應用元數據,這些元數據描述了在應用裏使用的類和方法。注意,永久代不是Java堆內存的一部分。

永久代存放JVM運行時使用的類。永久代同樣包含了Java SE庫的類和方法。永久代的對象在full GC時進行垃圾收集。

方法區

方法區是永久代空間的一部分,並用來存儲類型信息(運行時常量和靜態變量)和方法代碼和構造函數代碼。

內存池

如果JVM實現支持,JVM內存管理會爲創建內存池,用來爲不變對象創建對象池。字符串池就是內存池類型的一個很好的例子。內存池可以屬於堆或者永久代,這取決於JVM內存管理的實現。

運行時常量池

運行時常量池是每個類常量池的運行時代表。它包含了類的運行時常量和靜態方法。運行時常量池是方法區的一部分。

Java棧內存

Java棧內存用於運行線程。它們包含了方法裏的臨時數據、堆裏其它對象引用的特定數據。你可以閱讀棧內存和堆內存的區別

Java 堆內存開關

Java提供了大量的內存開關(參數),我們可以用它來設置內存大小和它們的比例。下面是一些常用的開關:

VM 開關

VM 開關描述

-Xms

設置JVM啓動時堆的初始化大小。

-Xmx

設置堆最大值。

-Xmn

設置年輕代的空間大小,剩下的爲老年代的空間大小。

-XX:PermGen

設置永久代內存的初始化大小。

-XX:MaxPermGen

設置永久代的最大值。

-XX:SurvivorRatio

提供Eden區和survivor區的空間比例。比如,如果年輕代的大小爲10m並且VM開關是-XX:SurvivorRatio=2,那麼將會保留5m內存給Eden區和每個Survivor區分配2.5m內存。默認比例是8。

-XX:NewRatio

提供年老代和年輕代的比例大小。默認值是2。

Java垃圾回收

Java垃圾回收會找出沒用的對象,把它從內存中移除並釋放出內存給以後創建的對象使用。Java程序語言中的一個最大優點是自動垃圾回收,不像其他的程序語言那樣需要手動分配和釋放內存,比如C語言。

垃圾收集器是一個後臺運行程序。它管理着內存中的所有對象並找出沒被引用的對象。所有的這些未引用的對象都會被刪除,回收它們的空間並分配給其他對象。

一個基本的垃圾回收過程涉及三個步驟:

  1. 標記:這是第一步。在這一步,垃圾收集器會找出哪些對象正在使用和哪些對象不在使用。

  2. 正常清除:垃圾收集器清會除不在使用的對象,回收它們的空間分配給其他對象。

  3. 壓縮清除:爲了提升性能,壓縮清除會在刪除沒用的對象後,把所有存活的對象移到一起。這樣可以提高分配新對象的效率。

簡單標記和清除方法存在兩個問題:

  1. 效率很低。因爲大多數新建對象都會成爲“沒用對象”。

  2. 經過多次垃圾回收週期的對象很有可能在以後的週期也會存活下來。

上面簡單清除方法的問題在於Java垃圾收集的分代回收的,而且在堆內存裏有年輕代年老代兩個區域。我已經在上面解釋了Minor GC和Major GC是怎樣掃描對象,以及如何把對象從一個分代空間移到另外一個分代空間。

Java垃圾回收類型

這裏有五種可以在應用裏使用的垃圾回收類型。僅需要使用JVM開關就可以在我們的應用裏啓用垃圾回收策略。讓我們一起來逐一瞭解:

  1. Serial GC(-XX:+UseSerialGC):Serial GC使用簡單的標記、清除、壓縮方法對年輕代和年老代進行垃圾回收,即Minor GC和Major GC。Serial GC在client模式(客戶端模式)很有用,比如在簡單的獨立應用和CPU配置較低的機器。這個模式對佔有內存較少的應用很管用。

  2. Parallel GC(-XX:+UseParallelGC):除了會產生N個線程來進行年輕代的垃圾收集外,Parallel GC和Serial GC幾乎一樣。這裏的N是系統CPU的核數。我們可以使用 -XX:ParallelGCThreads=n 這個JVM選項來控制線程數量。並行垃圾收集器也叫throughput收集器。因爲它使用了多CPU加快垃圾回收性能。Parallel GC在進行年老代垃圾收集時使用單線程。

  3. Parallel Old GC(-XX:+UseParallelOldGC):和Parallel GC一樣。不同之處,Parallel Old GC在年輕代垃圾收集和年老代垃圾回收時都使用多線程收集。

  4. 併發標記清除(CMS)收集器(-XX:+UseConcMarkSweepGC):CMS收集器也被稱爲短暫停頓併發收集器。它是對年老代進行垃圾收集的。CMS收集器通過多線程併發進行垃圾回收,儘量減少垃圾收集造成的停頓。CMS收集器對年輕代進行垃圾回收使用的算法和Parallel收集器一樣。這個垃圾收集器適用於不能忍受長時間停頓要求快速響應的應用。可使用 -XX:ParallelCMSThreads=n JVM選項來限制CMS收集器的線程數量。

  5. G1垃圾收集器(-XX:+UseG1GC) G1(Garbage First):垃圾收集器是在Java 7後纔可以使用的特性,它的長遠目標時代替CMS收集器。G1收集器是一個並行的、併發的和增量式壓縮短暫停頓的垃圾收集器。G1收集器和其他的收集器運行方式不一樣,不區分年輕代和年老代空間。它把堆空間劃分爲多個大小相等的區域。當進行垃圾收集時,它會優先收集存活對象較少的區域,因此叫“Garbage First”。你可以在Oracle Garbage-FIrst收集器文檔找到更多詳細信息。


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