??Java 中的堆是 JVM 所管理的最大的一塊內存空間,主要用於存放各種類的實例對象。
??在 Java 中,堆被劃分成兩個不同的區域:新生代 ( Young )、老年代 ( Old )。新生代 ( Young ) 又被劃分為三個區域:Eden、From Survivor、To Survivor。
??這樣劃分的目的是為了使 JVM 能夠更好的管理堆內存中的對象,包括內存的分配以及回收。
??堆的內存模型大致為:
??新生代:Young Generation,主要用來存放新生的對象。
??老年代:Old Generation或者稱作Tenured Generation,主要存放應用程序聲明周期長的內存對象。
永久代:(方法區,不屬於java堆,另一個別名為“非堆Non-Heap”但是一般查看PrintGCDetails都會帶上PermGen區)是指內存的永久保存區域,主要存放Class和Meta的信息,Class在被 Load的時候被放入PermGen space區域. 它和和存放Instance的Heap區域不同,GC(Garbage Collection)不會在主程序運行期對PermGen space進行清理,所以如果你的應用會加載很多Class的話,就很可能出現PermGen space錯誤。
??堆大小 = 新生代 + 老年代。其中,堆的大小可以通過參數 –Xms、-Xmx 來指定。
??默認的,新生代 ( Young ) 與老年代 ( Old ) 的比例的值為 1:2 ( 該值可以通過參數 –XX:NewRatio 來指定 ),即:新生代 ( Young ) = 1/3 的堆空間大小。老年代 ( Old ) = 2/3 的堆空間大小。其中,新生代 ( Young ) 被細分為 Eden 和 兩個 Survivor 區域,這兩個 Survivor 區域分別被命名為 from 和 to,以示區分。
??默認的,Edem : from : to = 8 : 1 : 1 ( 可以通過參數 –XX:SurvivorRatio 來設定 ),即: Eden = 8/10 的新生代空間大小,from = to = 1/10 的新生代空間大小。
??JVM 每次只會使用 Eden 和其中的一塊 Survivor 區域來為對象服務,所以無論什麼時候,總是有一塊 Survivor 區域是空閒著的。
??因此,新生代實際可用的內存空間為 9/10 ( 即90% )的新生代空間。
??很多人認為方法區(或者HotSpot虛擬機中的永久代[PermGen])是沒有垃圾收集的,java虛擬機規范中確實說過可以不要求虛擬機在方法區實現垃圾收集,而且在方法去中進行垃圾收集的“性價比”一般比較低:在堆中,尤其是在新生代中,常規應用進行一次垃圾收集一般可以回收70%-95%的空間,而永久代的垃圾收集效率遠低於此。
??永久代的垃圾收集主要回收兩部分內容:廢棄的常量和無用的類。
??廢棄的常量:回收廢棄常量與回收java堆中的對象非常類似。以常量池字面量的回收為例,加入一個字符串“abc”已經進入了常量池中,但是當前系統沒有任何一個String對象是叫做”abc”的,換句話說,就是有任何String對象應用常量池中的”abc”常量,也沒有其他地方引用了這個字面量,如果這時發生內存回收,而且必要的話,這個“abc”常量就會被系統清理出常量池。常量池中的其他類(接口)、方法、字段的符號引用也與此類似。(注:jdk1.7及其之後的版本已經將常量池從永久代中移出)
??無用的類:類需要同時滿足下面3個條件才能算是“無用的類”:
??虛擬機可以對滿足上述3個條件的無用類進行回收,這裡說的僅僅是”可以“,而並不和對象一樣,不使用了就必然會回收。是否對類進行回收,HotSpot虛擬機提供了-Xnoclassgc(關閉CLASS的垃圾回收功能,就是虛擬機加載的類,即便是不使用,沒有實例也不會回收。)參數進行控制。
在大量使用反射、動態代理、CGlib等ByteCode框架、動態生成JSP以及OSGi這類頻繁自定義ClassLoader的場景都需要虛擬機具備類卸載的功能,以保證永久代不會溢出。
??Java 中的堆也是 GC 收集垃圾的主要區域。GC 分為兩種:Minor GC、Full GC ( 或稱為 Major GC )。
??Minor GC 是發生在新生代中的垃圾收集動作,所采用的是復制算法。
??新生代幾乎是所有 Java 對象出生的地方,即 Java 對象申請的內存以及存放都是在這個地方。Java 中的大部分對象通常不需長久存活,具有朝生夕滅的性質。
??當一個對象被判定為 “死亡” 的時候,GC 就有責任來回收掉這部分對象的內存空間。新生代是 GC 收集垃圾的頻繁區域。
??當對象在 Eden ( 包括一個 Survivor 區域,這裡假設是 from 區域 ) 出生後,在經過一次 Minor GC 後,如果對象還存活,並且能夠被另外一塊 Survivor 區域所容納( 上面已經假設為 from 區域,這裡應為 to 區域,即 to 區域有足夠的內存空間來存儲 Eden 和 from 區域中存活的對象 ),則使用復制算法將這些仍然還存活的對象復制到另外一塊 Survivor 區域 ( 即 to 區域 ) 中,然後清理所使用過的 Eden 以及 Survivor 區域 ( 即 from 區域 ),並且將這些對象的年齡設置為1,以後對象在 Survivor 區每熬過一次 Minor GC,就將對象的年齡 + 1,當對象的年齡達到某個值時 ( 默認是 15 歲,可以通過參數 -XX:MaxTenuringThreshold 來設定 ),這些對象就會成為老年代。
??但這也不是一定的,對於一些較大的對象 ( 即需要分配一塊較大的連續內存空間 ) 則是直接進入到老年代。虛擬機提供了一個-XX:PretenureSizeThreshold參數,令大於這個設置值的對象直接在老年代分配。這樣做的目的是避免在Eden區及兩個Survivor區之間發生大量的內存復制(新生代采用復制算法收集內存)。
??為了能夠更好的適應不同的程序的內存狀況,虛擬機並不是永遠地要求對象的年齡必須達到了MaxTenuringThreshold才能晉升老年代,如果在Survivor空間中相同年齡所有對象大小的總和大於Survivor空間的一半,年齡大於或等於該年齡的對象可以直接進入老年代,無需等到MaxTenuringThreshold中要求的年齡。
??Full GC 是發生在老年代的垃圾收集動作,所采用的是“標記-清除”或者“標記-整理”算法。
??現實的生活中,老年代的人通常會比新生代的人 “早死”。堆內存中的老年代(Old)不同於這個,老年代裡面的對象幾乎個個都是在 Survivor 區域中熬過來的,它們是不會那麼容易就 “死掉” 了的。因此,Full GC 發生的次數不會有 Minor GC 那麼頻繁,並且做一次 Full GC 要比進行一次 Minor GC 的時間更長。
??在發生MinorGC之前,虛擬機會先檢查老年代最大可用的連續空間是否大於新生代所有對象總空間,如果這個條件成立,那麼MinorGC可以確保是安全的。如果不成立,則虛擬機會查看HandlePromotionFailure設置值是否允許擔保失敗。如果允許,那麼會繼續檢查老年代最大可用的連續空間是否大於歷次晉升到老年代對象的平均大小,如果大於,將嘗試這進行一次MinorGC,盡管這次MinorGC是有風險的;如果小於,或者HandlePromptionFailure設置不允許冒險,那這是也要改為進行一次FullGC.
??另外,標記-清除算法收集垃圾的時候會產生許多的內存碎片 ( 即不連續的內存空間 ),此後需要為較大的對象分配內存空間時,若無法找到足夠的連續的內存空間,就會提前觸發一次 GC 的收集動作。
首先看一下如下代碼:
package jvm;
public class PrintGCDetails
{
public static void main(String[] args)
{
Object obj = new Object();
System.gc();
System.out.println();
obj = new Object();
obj = new Object();
System.gc();
System.out.println();
}
}
??設置JVM參數為-XX:+PrintGCDetails,執行結果如下:
[GC [PSYoungGen: 1019K->568K(28672K)] 1019K->568K(92672K), 0.0529244 secs] [Times: user=0.00 sys=0.00, real=0.06 secs]
{博主自定義注解:[GC [新生代: MinorGC前新生代內存使用->MinorGC後新生代內存使用(新生代總的內存大小)] MinorGC錢JVM堆內存使用的大小->MinorGC錢JVM堆內存使用的大小(堆的可用內存大小), MinorGC總耗時] [Times: 用戶耗時=0.00 系統耗時=0.00, 實際耗時=0.06 secs] }
[Full GC [PSYoungGen: 568K->0K(28672K)] [ParOldGen: 0K->478K(64000K)] 568K->478K(92672K) [PSPermGen: 2484K->2483K(21504K)], 0.0178331 secs] [Times: user=0.01 sys=0.00, real=0.02 secs]
{博主自定義注解:[Full GC [PSYoungGen: 568K->0K(28672K)] [老年代: FullGC前老年代內存使用->FullGC後老年代內存使用(老年代總的內存大小)] FullGC前JVM堆內存使用的大小->FullGC後JVM堆內存使用的大小(堆的可用內存大小) [永久代: 2484K->2483K(21504K)], 0.0178331 secs] [Times: user=0.01 sys=0.00, real=0.02 secs]}
[GC [PSYoungGen: 501K->64K(28672K)] 980K->542K(92672K), 0.0005080 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC [PSYoungGen: 64K->0K(28672K)] [ParOldGen: 478K->479K(64000K)] 542K->479K(92672K) [PSPermGen: 2483K->2483K(21504K)], 0.0133836 secs] [Times: user=0.05 sys=0.00, real=0.01 secs]
Heap
PSYoungGen total 28672K, used 1505K [0x00000000e0a00000, 0x00000000e2980000, 0x0000000100000000)
eden space 25088K, 6% used [0x00000000e0a00000,0x00000000e0b78690,0x00000000e2280000)
from space 3584K, 0% used [0x00000000e2600000,0x00000000e2600000,0x00000000e2980000)
to space 3584K, 0% used [0x00000000e2280000,0x00000000e2280000,0x00000000e2600000)
ParOldGen total 64000K, used 479K [0x00000000a1e00000, 0x00000000a5c80000, 0x00000000e0a00000)
object space 64000K, 0% used [0x00000000a1e00000,0x00000000a1e77d18,0x00000000a5c80000)
PSPermGen total 21504K, used 2492K [0x000000009cc00000, 0x000000009e100000, 0x00000000a1e00000)
object space 21504K, 11% used [0x000000009cc00000,0x000000009ce6f2d0,0x000000009e100000)
注:你可以用JConsole或者Runtime.getRuntime().maxMemory(),Runtime.getRuntime().totalMemory(), Runtime.getRuntime().freeMemory()來查看Java中堆內存的大小。
??再看一個例子:
package jvm;
public class PrintGCDetails2
{
/**
* -Xms60m -Xmx60m -Xmn20m -XX:NewRatio=2 ( 若 Xms = Xmx, 並且設定了 Xmn,
* 那麼該項配置就不需要配置了 ) -XX:SurvivorRatio=8 -XX:PermSize=30m -XX:MaxPermSize=30m
* -XX:+PrintGCDetails
*/
public static void main(String[] args)
{
new PrintGCDetails2().doTest();
}
public void doTest()
{
Integer M = new Integer(1024 * 1024 * 1); // 單位, 兆(M)
byte[] bytes = new byte[1 * M]; // 申請 1M 大小的內存空間
bytes = null; // 斷開引用鏈
System.gc(); // 通知 GC 收集垃圾
System.out.println();
bytes = new byte[1 * M]; // 重新申請 1M 大小的內存空間
bytes = new byte[1 * M]; // 再次申請 1M 大小的內存空間
System.gc();
System.out.println();
}
}
??運行結果:
[GC [PSYoungGen: 2007K->568K(18432K)] 2007K->568K(59392K), 0.0059377 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]
[Full GC [PSYoungGen: 568K->0K(18432K)] [ParOldGen: 0K->479K(40960K)] 568K->479K(59392K) [PSPermGen: 2484K->2483K(30720K)], 0.0223249 secs] [Times: user=0.01 sys=0.00, real=0.02 secs]
[GC [PSYoungGen: 3031K->1056K(18432K)] 3510K->1535K(59392K), 0.0140169 secs] [Times: user=0.05 sys=0.00, real=0.01 secs]
[Full GC [PSYoungGen: 1056K->0K(18432K)] [ParOldGen: 479K->1503K(40960K)] 1535K->1503K(59392K) [PSPermGen: 2486K->2486K(30720K)], 0.0119497 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]
Heap
PSYoungGen total 18432K, used 163K [0x00000000fec00000, 0x0000000100000000, 0x0000000100000000)
eden space 16384K, 1% used [0x00000000fec00000,0x00000000fec28ff0,0x00000000ffc00000)
from space 2048K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x0000000100000000)
to space 2048K, 0% used [0x00000000ffc00000,0x00000000ffc00000,0x00000000ffe00000)
ParOldGen total 40960K, used 1503K [0x00000000fc400000, 0x00000000fec00000, 0x00000000fec00000)
object space 40960K, 3% used [0x00000000fc400000,0x00000000fc577e10,0x00000000fec00000)
PSPermGen total 30720K, used 2493K [0x00000000fa600000, 0x00000000fc400000, 0x00000000fc400000)
object space 30720K, 8% used [0x00000000fa600000,0x00000000fa86f4f0,0x00000000fc400000)
??從打印結果可以看出,堆中新生代的內存空間為 18432K ( 約 18M ),eden 的內存空間為 16384K ( 約 16M),from / to survivor 的內存空間為 2048K ( 約 2M)。
??這裡所配置的 Xmn 為 20M,也就是指定了新生代的內存空間為 20M,可是從打印的堆信息來看,新生代怎麼就只有 18M 呢? 另外的 2M 哪裡去了?
??別急,是這樣的。新生代 = eden + from + to = 16 + 2 + 2 = 20M,可見新生代的內存空間確實是按 Xmn 參數分配得到的。
??而且這裡指定了 SurvivorRatio = 8,因此,eden = 8/10 的新生代空間 = 8/10 * 20 = 16M。from = to = 1/10 的新生代空間 = 1/10 * 20 = 2M。
??堆信息中新生代的 total 18432K 是這樣來的: eden + 1 個 survivor = 16384K + 2048K = 18432K,即約為 18M。
??因為 jvm 每次只是用新生代中的 eden 和 一個 survivor,因此新生代實際的可用內存空間大小為所指定的 90%。
??因此可以知道,這裡新生代的內存空間指的是新生代可用的總的內存空間,而不是指整個新生代的空間大小。
??另外,可以看出老年代的內存空間為 40960K ( 約 40M ),堆大小 = 新生代 + 老年代。因此在這裡,老年代 = 堆大小 - 新生代 = 60 - 20 = 40M。
??最後,這裡還指定了 PermSize = 30m,PermGen 即永久代 ( 方法區 ),它還有一個名字,叫非堆,主要用來存儲由 jvm 加載的類文件信息、常量、靜態變量等。
-XX:+
堆設置
-Xms :初始堆大小
-Xmx :最大堆大小
-Xmn:新生代大小。通常為 Xmx 的 1/3 或 1/4。新生代 = Eden + 2 個 Survivor 空間。實際可用空間為 = Eden + 1 個 Survivor,即 90%
-XX:NewSize=n :設置年輕代大小
-XX:NewRatio=n: 設置年輕代和年老代的比值。如:為3,表示年輕代與年老代比值為1:3,年輕代占整個年輕代年老代和的1/4
-XX:SurvivorRatio=n :年輕代中Eden區與兩個Survivor區的比值。注意Survivor區有兩個。如:3,表示Eden:Survivor=3:2,一個Survivor區占整個年輕代的1/5
-XX:PermSize=n 永久代(方法區)的初始大小
-XX:MaxPermSize=n :設置永久代大小
-Xss 設定棧容量;對於HotSpot來說,雖然-Xoss參數(設置本地方法棧大小)存在,但實際上是無效的,因為在HotSpot中並不區分虛擬機和本地方法棧。
-XX:PretenureSizeThreshold (該設置只對Serial和ParNew收集器生效) 可以設置進入老生代的大小限制
-XX:MaxTenuringThreshold=1(默認15)垃圾最大年齡 如果設置為0的話,則年輕代對象不經過Survivor區,直接進入年老代. 對於年老代比較多的應用,可以提高效率.如果將此值設置為一個較大值,則年輕代對象會在Survivor區進行多次復制,這樣可以增加對象再年輕代的存活 時間,增加在年輕代即被回收的概率
該參數只有在串行GC時才有效.
收集器設置
-XX:+UseSerialGC :設置串行收集器
-XX:+UseParallelGC :設置並行收集器
-XX:+UseParallelOldGC :設置並行年老代收集器
-XX:+UseConcMarkSweepGC :設置並發收集器
垃圾回收統計信息
-XX:+PrintHeapAtGC GC的heap詳情
-XX:+PrintGCDetails GC詳情
-XX:+PrintGCTimeStamps 打印GC時間信息
-XX:+PrintTenuringDistribution 打印年齡信息等
-XX:+HandlePromotionFailure 老年代分配擔保(true or false)
-Xloggc:gc.log 指定日志的位置
並行收集器設置
-XX:ParallelGCThreads=n :設置並行收集器收集時使用的CPU數。並行收集線程數。
-XX:MaxGCPauseMillis=n :設置並行收集最大暫停時間
-XX:GCTimeRatio=n :設置垃圾回收時間占程序運行時間的百分比。公式為1/(1+n)
並發收集器設置
-XX:+CMSIncrementalMode :設置為增量模式。適用於單CPU情況。
-XX:ParallelGCThreads=n :設置並發收集器年輕代收集方式為並行收集時,使用的CPU數。並行收集線程數。
其他
-XX:PermSize=10M和-XX:MaxPermSize=10M限制方法區大小。
-XX:MaxDirectMemorySize=10M指定DirectMemory(直接內存)容量,如果不指定,則默認與JAVA堆最大值(-Xmx指定)一樣。
-XX:+HeapDumpOnOutOfMemoryError 可以讓虛擬機在出現內存溢出異常時Dump出當前的內存堆轉儲快照(.hprof文件)以便時候進行分析(比如Eclipse Memory Analysis)。