From: http://hi.baidu.com/lennyxue/blog/item/322c438bcaccad1fc9fc7a43.html
- Java Heap为什么要分成几个不同的代(generation)? 由于80%-98%的对象的生存周期很短,大部分新对象存放在young generation可以很高效的回收,避免遍历所有对象。
- young与old中内存分配的算法完全不同。young generation中由于存活的很少,要mark, sweep 然后再 compact 剩余的对象比较耗时,干脆把 live object copy 到另外一个空间更高效。old generation完全相反,里面的 live object 变化较少。因此采用 mark-sweep-compact更合适。
当一个对象不再被引用的时候,内存回收它占领的空间,以便空间被后来的新对象使用。除了释放没用的对象,垃圾收集也可以清除内存记录碎片。
1、 引用计数法(Reference Counting Collector)
引用计数法是唯一没有使用根集的垃圾回收的法,该算法使用引用计数器来区分存活对象和不再使用的对象。一般来说,堆中的每个对象对应一个引用计数器。当每一次创建一个对象并赋给一个变量时,引用计数器置为1。当对象被赋给任意变量时,引用计数器每次加1当对象出了作用域后(该对象丢弃不再使用),引用计数器减1,一旦引用计数器为0,对象就满足了垃圾收集的条件。
基于引用计数器的垃圾收集器运行较快,不会长时间中断程序执行,适宜地必须 实时运行的程序。但引用计数器增加了程序执行的开销,因为每次对象赋给新的变量,计数器加1,而每次现有对象出了作用域生,计数器减1。
ps:用根集的方法(既有向图的方法)进行内存对象管理,可以消除循环引用的问题.就是说如果有三个对象相互引用,只要他们和根集是不可达的,gc也是可以回收他们.根集的方法精度很高,但是效率低.计数器法精度低(无法处理循环引用),但是执行效率高.
2、tracing算法(Tracing Collector)
tracing算法是为了解决引用计数法的问题而提出,它使用了根集的概念。基于tracing算法的垃圾收集器从根集开始扫描,识别出哪些对象可达,哪些对象不可达,并用某种方式标记可达对象,例如对每个可达对象设置一个或多个位。在扫描识别过程中,基于tracing算法的垃圾收集也称为标记和清除 (mark-and-sweep)垃圾收集器。
3、compacting算法(Compacting Collector)
为了解决堆碎片问题,基于tracing的垃圾回收吸收了Compacting算法的思想,在清除的过程中,算法将所有的对象移到堆的一端,堆的另一端就变成了一个相邻的空闲内存区,收集器会对它移动的所有对象的所有引用进行更新,使得这些引用在新的位置能识别原来 的对象。在基于Compacting 算法的收集器的实现中,一般增加句柄和句柄表。
4、copying算法(Coping Collector)
该算法的提出是为了克服句柄的开销和解决堆碎片的垃圾回收。
将内存分为两个区域(from space和to space)。所有的对象分配内存都分配到from space。在清理非活动对象阶段,把所有标志为活动的对象,copy到to space,之后清楚from space空间。然后互换from sapce和to space的身份。既原先的from space变成to sapce,原先的to space变成from space。每次清理,重复上述过程。
优点:copy算法不理会非活动对象,copy数量仅仅取决为活动对象的数量。并且在copy的同时,整理了heap空间,即,to space的空间使用始终是连续的,内存使用效率得到提高。
缺点:划分from space和to space,内存的使用率是1/2。收集器必须复制所有的活动对象,这增加了程序等待时间。
5、generation算法(Generational Collector)
来自IBM的一组统计数据:98%的java对象,在创建之后不久就变成了非活动对象;只有2%的对象,会在长时间一直处于活动状态。
(1)young generation
年轻代分三个区。一个Eden区,两个Survivor区。大部分对象在 Eden区中生成。当Eden区满时,还存活的对象将被复制到Survivor区(两个中的一个),当这个Survivor区满时,此区的存活对象将被复制到另外一个Survivor区,当这个Survivor区也满了的时候,从第一个Survivor区复制过来的并且此时还存活的对象,将被复制到tenured generation。需要注意,Survivor的两个区是对称的,没先后关系,所以同一个区中可能同时存在从Eden复制过来对象,和从前一个Survivor复制过来的对象,而复制到年老区的只有从第一个Survivor去过来的对象。而且,Survivor区总有一个是空的。
young generation的gc称为minor gc。经过数次minor gc,依旧存活的对象,将被移出young generation,移到tenured generation
(2)tenured generation
生命周期较长的对象,归入到tenured generation。一般是经过多次minor gc,还 依旧存活的对象,将移入到tenured generation。(当然,在minorgc中如果存活的对象的超过survivor的容量,放不下的对象会直接移入到tenured generation)
tenured generation的gc称为major gc,就是通常说的full gc。
采用compactiion算法。由于tenured generaion区域比较大,而且通常对象生命周期都比较长,compaction需要一定时间。所以这部分的gc时间比较长。
minor gc可能引发full gc。当eden+from space的空间大于tenured generation区的剩余空间时,会引发full gc。这是悲观算法,要确保eden+from space的对象如果都存活,必须有足够的tenured generation空间存放这些对象。
(3)permanent generation
该区域比较稳定,主要用于存放classloader信息,比如类信息和method信息。
对于spring hibernate这些需要动态类型支持的框架,这个区域需要足够的空间。(这部分空间应该存在于方法区而不是heap中)。
6、adaptive算法(Adaptive Collector)
在特定的情况下,一些垃圾收集算法会优于其它算法。基于Adaptive算法的垃圾收集器就是监控当前堆的使用情况,并将选择适当算法的垃圾收集器。
在JAVA中,对象的引用(reference)和8个基本类型(boolean,char,byte,short,int,long,float,double)存储在栈中(stack).对象本身存储在堆中(heap),即那些用new来生成的对象。
二、 JAVA中的内存管理
当在JAVA中声明一个对象时,如 Date test = new Date(); 引用test存储在stack中,对象保存在heap里,两者通过指针联接。正常情况下,当到达语句块的结束点时,java切断两者间的联系,释放占用的内存。但在大多数情况下,由于程序中存在传入传出等操作,在对象中往往存在几个引用指向同一个对象的情况。java无法判断该对象是否真的不再需要使用。不敢直接将该对象释放掉(如果这样处理会造成更危险的悬空引用)。随着程序的运行,大量实际已经不再使用的对象(即garbage)将耗尽heap的空间。
默認的java虛擬機的大小比較小,在對大數據進行處理時java就會報錯:java.lang.OutOfMemoryError。
設置jvm內存的方法,對於單獨的.class,可以用下面的方法對Test運行時的jvm內存進行設置。
java -Xms64m -Xmx256m Test
-Xms是設置內存初始化的大小
-Xmx是設置最大能夠使用內存的大小(最好不要超過物理內存大小)
在weblogic中,可以在startweblogic.cmd中對每個domain虛擬內存的大小進行設置,默認的設置是在commEnv.cmd裡面。
JVM內存的調優
1. Heap設定與垃圾回收
Java Heap分為3個區,Young,Old和Permanent。 Young保存剛實例化的對象。當該區被填滿時,GC會將對象移到Old區。 Permanent區則負責保存反射對象,本文不討論該區。
JVM的Heap分配可以使用-X參數設定,
-Xms初始Heap大小
-Xmx java heap最大值
-Xmn young generation的heap大小
JVM有2個GC線程。
第一個線程負責回收Heap的Young區。
第二個線程在Heap不足時,遍歷Heap,將Young區升級為Older區。 Older區的大小等於-Xmx減去-Xmn,不能將-Xms的值設的過大,因為第二個線程被迫運行會降低JVM的性能。
為什麼一些程序頻繁發生GC?有如下原因:
程序內調用了System.gc()或Runtime.gc()。
一些中間件軟件調用自己的GC方法,此時需要設置參數禁止這些GC。
Java的Heap太小,一般默認的Heap值都很小。
頻繁實例化對象,Release對象。此時盡量保存並重用對象,例如使用StringBuffer()和String()。
如果你發現每次GC後,Heap的剩餘空間會是總空間的50%,這表示你的Heap處於健康狀態。許多Server端的Java程序每次GC後最好能有65%的剩餘空間。
經驗之談:
1. Server端JVM最好將-Xms和-Xmx設為相同值。為了優化GC,最好讓-Xmn值約等於-Xmx的1/3[2]。
2.一個GUI程序最好是每10到20秒間運行一次GC,每次在半秒之內完成[2]。
注意:
1.增加Heap的大小雖然會降低GC的頻率,但也增加了每次GC的時間。並且GC運行時,所有的用戶線程將暫停,也就是GC期間,Java應用程序不做任何工作。
2. Heap大小並不決定進程的內存使用量。進程的內存使用量要大於-Xmx定義的值,因為Java為其他任務分配內存,例如每個線程的Stack等。
2. Stack的設定
每個線程都有他自己的Stack。
-Xss每個線程的Stack大小
Stack的大小限制著線程的數量。如果Stack過大就好導致內存溢漏。 -Xss參數決定Stack大小,例如-Xss1024K。如果Stack太小,也會導致Stack溢漏。
3.硬件環境
硬件環境也影響GC的效率,例如機器的種類,內存,swap空間,和CPU的數量。
如果你的程序需要頻繁創建很多transient對象,會導致JVM頻繁GC。這種情況你可以增加機器的內存,來減少Swap空間的使用[2]。
4.4種GC
第一種為單線程GC,也是默認的GC。 ,該GC適用於單CPU機器。
第二種為Throughput GC,是多線程的GC,適用於多CPU,使用大量線程的程序。第二種GC與第一種GC相似,不同在於GC在收集Young區是多線程的,但在Old區和第一種一樣,仍然採用單線程。 -XX:+UseParallelGC參數啟動該GC。
第三種為Concurrent Low Pause GC,類似於第一種,適用於多CPU,並要求縮短因GC造成程序停滯的時間。這種GC可以在Old區的回收同時,運行應用程序。 -XX:+UseConcMarkSweepGC參數啟動該GC。
第四種為Incremental Low Pause GC,適用於要求縮短因GC造成程序停滯的時間。這種GC可以在Young區回收的同時,回收一部分Old區對象。 -Xincgc參數啟動該GC。
4種GC的具體描述參見[3]。
參考文章:
1. JVM Tuning. http://www.caucho.com/resin-3.0/performance/jvm-tuning.xtp#garbage-collection
2. Performance tuning Java: Tuning steps
http://h21007.www2.hp.com/dspp/tech/tech_TechDocumentDetailPage_IDX/1,1701,1604,00.html
3. Tuning Garbage Collection with the 1.4.2 JavaTM Virtual Machine .
http://java.sun.com/docs/hotspot/gc1.4.2/