java虛擬機在執行java程序的過程中會把他所管理的內存划分為若干個不同的數據區域,運行時數據區分別為:程序計數器,java虛擬機棧,本地方法棧,java堆,方法區。還有一個非運行時數據區:直接內存(Direct Memory)。
1)程序計數器
每個線程創建的時候會有一個獨立的程序計數器,可以看成是線程執行的字節碼的行號指示器,不同線程之間的程序計數器互不影響,存儲於程序計數器所在的內存中,這塊內存很小。線程私有
2)java虛擬機棧
每個方法被執行的時候虛擬機都會創建一個棧幀,用於存儲局部變量表, 操作數棧,動態鏈接,方法出口等信息,一個方法被調用至結束,就對應着棧幀在虛擬機棧中從入棧到出棧的過程。線程私有
局部變量表存放了編譯器可知的各種基本數據類型:boolean,byte,char,short,int,float,long,double,對象引用類型和returnAddress類型。對象引用類型可以是一個指向對象起始地址的指針,也可以是指向一個代表對象的句柄。returnAddress類型指向一條字節碼指令地址。
3)本地方法棧
調用native方法所需要用的棧
4)java堆
java堆是虛擬機所管理的內存中最大的區域,在java虛擬機啟動的時候創建。幾乎所有的對象實例以及數組都在堆中分配。堆是垃圾收集器管理的主要對象,又稱“GC堆”,如果要細分的話,java堆還可以分為Eden,From Survivor,To Survivor等。線程共享
5)方法區
方法區用於存儲被虛擬機加載的類信息,常量,靜態變量,即時編譯器編譯的代碼等,方法區又稱“非堆”,用於與堆區分。線程共享,對於方法區的內存回收主要是對常量池的回收以及對對象的卸載。運行時常量池是方法區的一部分,用於存放編譯期生成的各種字面量以及符號引用。
直接內存(Direct Memory)並不是java虛擬機運行時數據區的一部分,也不是java虛擬機規范中定義的內存區域。JDK1.4中新加入了NIO(New Input/Output)類,引入了一種基於通道與緩沖區的I/O方式,它可以使用native函數庫直接分配對外內存,然后通過一個存儲在java堆里面的DirectByteBuffer對象作為這塊內存的引用進行操作,避免了java堆與native堆中來回復制數據。
垃圾收集器的種類有:Serial收集器,ParNew收集器,Parallel Scavenge收集器,Serial Old收集器,Parallel Old收集器,CMS收集器,G1收集器
1)Serial收集器
單線程,工作時需要暫停用戶所有線程,可以和CMS配合工作
2)ParNew收集器
多線程,工作時需要用戶暫停所有線程,可以和CMS配合工作
3)Parallel Scavenge收集器
Parallel Scavenge收集器的主要目標是達到可控制吞吐量。吞吐量就是CPU運行用戶代碼的時間占CPU消耗時間的比值,即吞吐量=運行用戶代碼的時間/(運行用戶代碼的時間+垃圾收集的時間)。-XX:MaxGCPauseMillis=M設置垃圾收集時間盡量在M毫秒完成,減低M值不一定可以提高吞吐量;-XX:GCTimeRatio=N用於設置允許最大的垃圾收集時間占總時間的1/(1+N),默認值為99
4)Serial Old收集器
Serial Old是老年代版本,可以與Parallel Scavenge收集器配合使用,或者作為CMS的后備預案
5)Parallel Old收集器
Parallel Old收集器是Parallel Scavenge收集器的老年代版本,Parallel Scavenge收集器不可以與CMS收集器配合使用,如果使用Serial Old收集器,性能會被拖累,所以可以選擇Parallel Old收集器
6)CMS收集器
CMS收集器(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時間為目標的收集器。CMS的工作步驟:初始標記,並發標記,重新標記,並發刪除。在初始標記以及重新標記的時候需要暫停用戶所有線程,但是耗時非常短。初始標記就是標記直接與GC ROOT關聯的對象,並發標記就是標記從GC ROOT可以連通的對象,重新標記就是在並發標記的過程中因為用戶線程繼續運行導致標記變更的對象的記錄,最后並發清除未被標記的對象。
CMS收集器的優點:並發收集,低停頓
CMS收集器的缺點:CMS對CPU比較敏感,默認線程數為(CPU數量+3)/4,無法處理浮動垃圾(垃圾回收過程中用戶線程沒有停止導致),標記清楚算法會產生空間碎片
7)G1收集器
G1收集器采用標記整理算法,沒有碎片;可以精確控制停頓,指定M毫秒的時間片段內,消耗在垃圾搜集上的時間不能超過N毫秒;可以在不犧牲吞吐量的情況下完成低停頓的垃圾回收,原理:把堆分成多個大小固定的獨立區域,並跟蹤垃圾的堆積程度,在后台維護一個優先級列表
垃圾收集器采用的算法:
1)標記清除算法
會產生大量的空間碎片
2)復制算法
減小了內存,把內存分成2分,一份保持完整,當另一份存儲不下的時候啟用
3)標記整理算法
4)分代搜集
把堆分成新生代以及老年代,新生代使用復制算法,老年代使用標記整理算法
java虛擬機沒有使用引用計數算法,而是使用根搜索算法來查找不可用對象。
引用計數算法:當一個對象被引用,引用計數器加1,當引用失效的時候,引用計數器減1,當引用計數器為0的時候,被認為是可收回對象。
根搜索算法:通過一系列的”GC ROOTS“對象作為起始點,從這些點向下搜索,不能喝GC ROOTS聯通的對象便是可回收對象
java中可以作為“GC ROOTS”的對象包括:虛擬機棧(棧幀中的本地變量表)中的引用對象,方法區中的類靜態屬性引用的對象,方法區中的常量引用對象,本地方法棧中JNI(Native方法)的引用對象。
引用的類型:
強引用:只要強引用還存在,GC永遠不會回收內存
軟引用:對於軟引用關聯着的對象,當要發生內存溢出異常之前,把這些對象列入回收范圍並進行第二次回收
弱引用:對於弱引用關聯着的對象,能下一次GC工作的時候,不管內存是否足夠,都會被回收
虛引用:虛引用不會影響關聯着的對象的生存時間,也無法通過虛引用來獲取對象的實例,虛引用的唯一目的就是這個對象被GC回收時收到一個系統通知
垃圾收集器的執行邏輯:
1)用根搜索算法進行第一次標記
2)對標記的對象進行一次篩選,如果對象覆蓋了finalize方法,並且沒有執行過,那么有可能在這個finalize方法中再次使用這個對象,這樣的話就把該對象從待刪除的對象隊列中刪除。finalize方法只能執行一次,保證對象循環引用的時候不可能永遠存在。
3)java虛擬機創建一個低優先級的Finalize線程去執行對象覆蓋的finalize方法,垃圾收集器對待刪除隊列再次進行標記
4)垃圾收集器把兩次標記的對象回收了
===========================================================================
下面我們來看下GC日志:

1 package com.froest.excel; 2 3 public class Test2 { 4 private static int _1M = 1024 * 1024; 5 6 /** 7 * -verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8 8 * 9 * @param args 10 */ 11 public static void main(String[] args) { 12 // TODO Auto-generated method stub 13 byte[] alloc1, alloc2, alloc3, alloc4; 14 alloc1 = new byte[2 * _1M]; 15 alloc2 = new byte[2 * _1M]; 16 alloc3 = new byte[2 * _1M]; 17 // alloc4 = new byte[4 * _1M];//發生Minor GC 18 } 19 }
上面代碼運行以后得到下面的日志:
-verbose:gc 打印垃圾收集器的執行日志
-Xms20M 最小堆的大小
-Xmx20M 最大堆的大小
-Xmn10M 年輕代的大小
-XX:SurvivorRatio=8 設置Eden與Survivor的大小比例,默認8:1
以上參數得知該程序跑的虛擬機為堆的大小為20M,年輕代為10M,老年代10M,年輕代中Eden=8M,From Survivor = To Survivor = 1M,
alloc1,alloc2,alloc3總共6M,Eden區域足夠容納,不觸犯Minor GC(年輕代垃圾回收)。
下面把alloc4注釋去掉運行:
DefNew是指垃圾收集器的類型,6487K->148K指Minor GC前后年輕代的大小,9216為年輕代的總大小,6487K->6292K指Minor GC前后jvm堆的大小,19456指jvm堆的總大小,0.0041845 secs指Minor GC耗時,user=0.00指用戶耗時,sys=0.0指系統耗時,real=0.00指Minor GC實際耗時。def new generation total 9216K,used 4408K指年輕代堆的大小為9216K,使用了4408K;其中Eden區占了8192K,使用了52%(alloc4所占的內存),from space(From Survivor,使用了14%)以及to space(to Survivor)各占1024K;tenured generation total 10240K,used 6144K指的是老年代的大小為10240K,用了6144K(alloc1,alloc2,alloc3),使用60%。
alloc1,alloc2,alloc3執行以后Eden區就有6M,最大只有8M,當alloc4=4M想要放入的時候,Eden區大小不夠,所以觸發Minor GC,發現alloc1,alloc2,alloc3都是存活對象,有發現Survivor的空間只有1M,不夠大,所以利用空間分配擔保機制,把alloc1,alloc2,alloc3存放入老年代,老年代占用60%,alloc4就放入了Eden區。
下面的2張圖可以很好的解釋各個參數(原址:http://blog.csdn.net/alivetime/article/details/6895537):
Minor GC:
Full/Marjor GC:
配置參數-XX:PretenureSizeThreshold=3M,可以使大於3M的對象直接進入老年代
如果Survivor空間相同年齡的對象的大小總和大於Survivor空間的一半,那么大於等於該年齡的對象就直接進入老年代
空間分配擔保:
每次Minor GC執行完以后,虛擬機會檢查之前晉升到老年代的平均大小是否大於老年代的剩余空間大小,如果大於,則發起一次Full/Major GC,如果小於,則查看HandlePromotionFailure值是否允許擔保失敗,如果允許,那只會進行一次Minor GC。如果不允許失敗,那么也要改為進行一次Full/Major GC
之前晉升老年代的平均大小大於老年代的剩余空間大小

1 package com.froest.excel; 2 3 public class Test2 { 4 private static int _1M = 1024 * 1024; 5 6 /** 7 * -verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8 8 * 9 * @param args 10 */ 11 public static void main(String[] args) { 12 // TODO Auto-generated method stub 13 byte[] alloc1, alloc2, alloc3, alloc4, alloc5; 14 alloc1 = new byte[2 * _1M]; 15 alloc2 = new byte[2 * _1M]; 16 alloc3 = new byte[2 * _1M]; 17 alloc4 = new byte[4 * _1M];// 發生Minor GC 18 alloc2 = null; 19 alloc3 = null; 20 alloc4 = null; 21 alloc5 = new byte[6 * _1M]; 22 } 23 }
日志如下:
[GC [DefNew: 6487K->148K(9216K), 0.0049804 secs] 6487K->6292K(19456K), 0.0050137 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC [DefNew: 4244K->4244K(9216K), 0.0000124 secs][Tenured: 6144K->2196K(10240K), 0.0047571 secs] 10388K->2196K(19456K), [Perm : 370K->370K(12288K)], 0.0048202 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
Heap
def new generation total 9216K, used 6307K [0x322f0000, 0x32cf0000, 0x32cf0000)
eden space 8192K, 77% used [0x322f0000, 0x32918fe0, 0x32af0000)
from space 1024K, 0% used [0x32bf0000, 0x32bf0000, 0x32cf0000)
to space 1024K, 0% used [0x32af0000, 0x32af0000, 0x32bf0000)
tenured generation total 10240K, used 2196K [0x32cf0000, 0x336f0000, 0x336f0000)
the space 10240K, 21% used [0x32cf0000, 0x32f151d8, 0x32f15200, 0x336f0000)
compacting perm gen total 12288K, used 370K [0x336f0000, 0x342f0000, 0x376f0000)
the space 12288K, 3% used [0x336f0000, 0x3374cb20, 0x3374cc00, 0x342f0000)
ro space 10240K, 54% used [0x376f0000, 0x37c6d9f8, 0x37c6da00, 0x380f0000)
rw space 12288K, 55% used [0x380f0000, 0x387913f8, 0x38791400, 0x38cf0000)
這段代碼總共發生了2次GC,alloc4 = new byte[4 * _1M];這個時候發生一次,因為Eden區內存不夠用了,這次內存回收會把Eden區的所有的存活的對象轉移到老年代,並把alloc4存放入Eden區,此時Eden區4M,Survivor區0M,老年區6M;第二次GC發生在最后一個alloc1 = new byte[6 * _1M],Eden區占有4M,不夠用,所以Eden區的存活對象要進入老年代,老年代之前晉升了6M,大於剩余空間2M,所以此時發生一次Full/Major GC,因為alloc2,alloc3,alloc4設置為null,所以在這次Full/Major GC中被清理了,老年代只剩下2M,新的alloc1=6M進入Eden區。
從日志中可以看到我的分析結果:Eden區占用了77%,大約6M,tenured generation占21%大約2M,為什么不是精確的相等,我的理解是垃圾收集器線程運行本身會耗費一定量的資源,所以不會嚴格的相等。
之前晉升老年代的平均大小小於老年代的剩余空間大小,並且關閉空間擔保機制-XX:-HandlePromotionFailure,但是我在機器上跑出來的日志顯示,java虛擬機並沒有進行一次Full/Major GC!!!下面是我的代碼:

1 package com.froest.excel; 2 3 public class Test2 { 4 private static int _1M = 1024 * 1024; 5 6 /** 7 * -verbose:gc -Xms20m -Xmx20m -Xmn10m -XX:SurvivorRatio=8 -XX:+PrintGCDetails 8 * -XX:-HandlePromotionFailure 9 * 10 * @param args 11 */ 12 public static void main(String[] args) { 13 // TODO Auto-generated method stub 14 byte[] alloc1, alloc2, alloc3, alloc4, alloc5; 15 alloc1 = new byte[1 * _1M]; 16 alloc2 = new byte[1 * _1M]; 17 alloc3 = new byte[1 * _1M]; 18 alloc4 = new byte[6 * _1M];// 發生Minor GC 19 alloc2 = null; 20 alloc3 = null; 21 alloc1 = null; 22 alloc5 = new byte[5 * _1M]; 23 } 24 }
GC日志:
代碼分析:
alloc1,alloc2,alloc3 分別分配1M,當alloc4=6M的時候Eden區不夠大,並且Survivor區也不夠大,直接將alloc1,alloc2,alloc3分配入老年代,alloc4分配入Eden區,alloc1,alloc2,alloc3設置為null,alloc5=5M的時候Eden區已經存有6M的alloc4,所以不夠大,要存入老年代,首先判斷之前晉升入老年代的平均大小3M小於老年代的剩余空間7M,又因為我關閉了空間擔保機制,所以應該會出現一次Full/Major GC,但是從日志來看並沒有出現Full/Major GC,還是只進行了一次Minor GC,這里不理解,哪位大牛知道請指導下,萬分感謝。繼續摸索!!看下面的2個代碼以及日志的比較,應該就是問題的答案了。
看看下面代碼,我們是否查詢到一些端倪

1 public class Test2 { 2 private static int _1M = 1024 * 1024; 3 4 /** 5 * -verbose:gc -Xms20m -Xmx20m -Xmn10m -XX:SurvivorRatio=8 -XX:+PrintGCDetails 6 * -XX:-HandlePromotionFailure 7 * 8 * @param args 9 */ 10 public static void main(String[] args) { 11 // TODO Auto-generated method stub 12 byte[] alloc1, alloc2, alloc3, alloc4, alloc5,alloc6; 13 alloc1 = new byte[1 * _1M]; 14 alloc2 = new byte[1 * _1M]; 15 alloc3 = new byte[1 * _1M]; 16 // alloc6 = new byte[1 * _1M]; 17 alloc4 = new byte[6 * _1M];// 發生Minor GC 18 alloc1 = null; 19 alloc2 = null; 20 alloc3 = null; 21 alloc4 = null; 22 alloc5 = new byte[5 * _1M]; 23 } 24 }
gc日志:
[GC [DefNew: 3415K->148K(9216K), 0.0025138 secs] 3415K->3220K(19456K), 0.0025390 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC [DefNew: 6292K->148K(9216K), 0.0004528 secs] 9364K->3220K(19456K), 0.0004733 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
def new generation total 9216K, used 5432K [0x322f0000, 0x32cf0000, 0x32cf0000)
eden space 8192K, 64% used [0x322f0000, 0x32818fe0, 0x32af0000)
from space 1024K, 14% used [0x32af0000, 0x32b151c8, 0x32bf0000)
to space 1024K, 0% used [0x32bf0000, 0x32bf0000, 0x32cf0000)
tenured generation total 10240K, used 3072K [0x32cf0000, 0x336f0000, 0x336f0000)
the space 10240K, 30% used [0x32cf0000, 0x32ff0030, 0x32ff0200, 0x336f0000)
compacting perm gen total 12288K, used 370K [0x336f0000, 0x342f0000, 0x376f0000)
the space 12288K, 3% used [0x336f0000, 0x3374cb40, 0x3374cc00, 0x342f0000)
ro space 10240K, 54% used [0x376f0000, 0x37c6d9f8, 0x37c6da00, 0x380f0000)
rw space 12288K, 55% used [0x380f0000, 0x387913f8, 0x38791400, 0x38cf0000)
把alloc6的注釋去掉,執行以后的gc日志為:
[GC [DefNew: 4439K->148K(9216K), 0.0032273 secs] 4439K->4244K(19456K), 0.0032624 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC [DefNew: 6292K->6292K(9216K), 0.0000128 secs][Tenured: 4096K->1172K(10240K), 0.0051583 secs] 10388K->1172K(19456K), [Perm : 370K->370K(12288K)], 0.0052311 secs] [Times: user=0.00 sys=0.01, real=0.01 secs]
Heap
def new generation total 9216K, used 5283K [0x322f0000, 0x32cf0000, 0x32cf0000)
eden space 8192K, 64% used [0x322f0000, 0x32818fe0, 0x32af0000)
from space 1024K, 0% used [0x32bf0000, 0x32bf0000, 0x32cf0000)
to space 1024K, 0% used [0x32af0000, 0x32af0000, 0x32bf0000)
tenured generation total 10240K, used 1172K [0x32cf0000, 0x336f0000, 0x336f0000)
the space 10240K, 11% used [0x32cf0000, 0x32e151d8, 0x32e15200, 0x336f0000)
compacting perm gen total 12288K, used 370K [0x336f0000, 0x342f0000, 0x376f0000)
the space 12288K, 3% used [0x336f0000, 0x3374cb70, 0x3374cc00, 0x342f0000)
ro space 10240K, 54% used [0x376f0000, 0x37c6d9f8, 0x37c6da00, 0x380f0000)
rw space 12288K, 55% used [0x380f0000, 0x387913f8, 0x38791400, 0x38cf0000)
通過以上2個代碼以及日志的比較是不是可以得出結論:
當之前的每次晉升到老年代的平均大小小於老年代的剩余空間大小的時候,如果設置不允許擔保失敗,並且老年代的剩余空間大小不足以存放本次晉升的對象的大小,那么會觸發一次Full/Major GC,如果老年代的剩余空間大小足以存放本次晉升的對象的大小,那么就會執行Minor GC。

1 package com.froest.excel; 2 3 public class Test2 { 4 private static int _1M = 1024 * 1024; 5 6 /** 7 * -verbose:gc -Xms20m -Xmx20m -Xmn10m -XX:SurvivorRatio=8 -XX:+PrintGCDetails 8 * -XX:-HandlePromotionFailure 9 * 10 * @param args 11 */ 12 public static void main(String[] args) { 13 // TODO Auto-generated method stub 14 byte[] alloc1, alloc2, alloc3, alloc4, alloc5; 15 alloc1 = new byte[2 * _1M]; 16 alloc2 = new byte[2 * _1M]; 17 alloc3 = new byte[2 * _1M]; 18 alloc4 = new byte[6 * _1M];// 發生Minor GC 19 alloc5 = new byte[2 * _1M]; 20 } 21 }
日志:
[GC [DefNew: 6487K->148K(9216K), 0.0038807 secs] 6487K->6292K(19456K), 0.0039068 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC [DefNew: 6292K->6292K(9216K), 0.0000143 secs][Tenured: 6144K->6144K(10240K), 0.0060055 secs] 12436K->12436K(19456K), [Perm : 370K->370K(12288K)], 0.0060708 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
Heap
def new generation total 9216K, used 6456K [0x322f0000, 0x32cf0000, 0x32cf0000)
eden space 8192K, 78% used [0x322f0000, 0x3293e1a8, 0x32af0000)
from space 1024K, 0% used [0x32bf0000, 0x32bf0000, 0x32cf0000)
to space 1024K, 0% used [0x32af0000, 0x32af0000, 0x32bf0000)
tenured generation total 10240K, used 8192K [0x32cf0000, 0x336f0000, 0x336f0000)
the space 10240K, 80% used [0x32cf0000, 0x334f0040, 0x334f0200, 0x336f0000)
compacting perm gen total 12288K, used 370K [0x336f0000, 0x342f0000, 0x376f0000)
the space 12288K, 3% used [0x336f0000, 0x3374cb28, 0x3374cc00, 0x342f0000)
ro space 10240K, 54% used [0x376f0000, 0x37c6d9f8, 0x37c6da00, 0x380f0000)
rw space 12288K, 55% used [0x380f0000, 0x387913f8, 0x38791400, 0x38cf0000)
不管有沒有啟用擔保失敗機制,最后的2M內存都會被分配入老年代。
想知道空間擔保機制做運作,所以想先把空間擔保機制關閉,如果之前晉升老年代的平均大小小於老年代的剩余空間大小,那么會直接發生Full/Major GC,如果把空間擔保機制開啟,那么就不會發生Full/Major GC,會直接執行一次Minor GC。
如果有問題或者哪里寫的不對的地方,請各位大牛斧正,不勝感謝!
jvm參數優化參考:http://unixboy.iteye.com/blog/174173
jvm參數列表:http://kenwublog.com/docs/java6-jvm-options-chinese-edition.htm