标签:
GC (Garbage Collection)早于java出现,60年代出现的Lisp中最早使用了GC。
当需要排查各种内存溢出、内存漏斗问题时,当垃圾回收成为系统达到更高并发量的瓶颈时,就需要用到gc了。
总之,写出高性能的Java程序需要懂GC。
HotSpot JVM体系结构。
和应用性能相关的部分用紫色标出,调优从它们着手!
在对Java应用程序进行调优时,主要关注两点:响应速度和吞吐量。
响应速度是应用或系统返回指定数据的速度。举例:
吞吐量关注一段时间内应用完成的工作量。举例:
简而言之,自动垃圾回收的过程就是到堆内存中查看哪个对象还被使用,哪些不被使用了,并删掉不被使用的对象。
被使用的对象在程序某处有引用指向它,不被使用的对象不再被引用。在C中,分配和回收内存是手动的。在Java中垃圾回收由GC完成。
第一步:标记
标记被引用和不再被引用的对象。
如果系统中的所有对象都被扫描,这个过程会很费时。(伏笔)
第二步 (一种选择):正常清除。
删除所有不被引用的对象,保留被引用对象,并将指针指向空闲空间。
内存分配器持有指向空闲空间的指针。
第二步 (另一种选择) : 清除并整理。
为了提高性能,在删除不被引对象之后,我们将所有剩下的被引对象整理在一起,这样空闲空间也能成为连续的大块。这样让新的内存分配更简洁更快速。
经过大量的分析,大部分的对象是短命的。如下图所示,Y轴表示存活对象的字节数,X轴表示GC随着时间已分配的字节数。
从图中可看成,大部分对象存活的时间很短。
上面的伏笔讲到,扫描SVM中所有对象,对其进行标记整理效率很低。对象数量增加的话,GC的耗时也会增加。
在4.2中我们讲到大部分对象都是短命的。所以我们可以将JVM分代回收以提高性能。将堆分成几个部分:新生代、老年代和永久代。
所有新对象被分配到这里。当新生代被占满,会出发minor garbage collection (简称minor GC),回收一个充满“死”对象的新生代会很快。部分幸存对象变老并在年龄达到一定阈值后放到老年代。
Stop the World Event:所有minor GC都是Stop the World Event。就是说会停掉所有的线程直到GC完成。
老年代存放长期存活对象。新生代的对象年龄超过一定阈值时,会进入老年代。对老年代回收叫做major garbage collection (major GC)。
major GC也是Stop the World Event。一般来说major GC比minor GC要慢很多,因为会涉及到所有存活的对象。所以对于响应速度要求高的应用,应该尽可能避免major GC。
另外,major GC的时长也取决于GC使用何种回收策略进行老年代回收。
永久代存放描述类和方法的元数据。
如何判断哪些对象还“存活”?哪些已经“死去”呢?一般用引用计数算法和可达性分析算法。
是什么?给对象添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器为0的对象就是不可能再被使用的。
这种算法实现简单,判定效率高。Python就是用了这种算法管理内存。但是主流Java虚拟机里面没有选用引用计数算法来管理内存。主要原因是因为它很难解决对象之间的相互循环引用的问题。
/** * testGC()方法执行后,objA和objB会不会被GC呢? * @author zzm */ public class ReferenceCountingGC { public Object instance = null; private static final int _1MB = 1024 * 1024; /** * 这个成员属性的唯一意义就是占点内存,以便在能在GC日志中看清楚是否有回收过 */ private byte[] bigSize = new byte[2 * _1MB]; public static void testGC() { ReferenceCountingGC objA = new ReferenceCountingGC(); ReferenceCountingGC objB = new ReferenceCountingGC(); objA.instance = objB; objB.instance = objA; objA = null; objB = null; // 假设在这行发生GC,objA和objB是否能被回收? System.gc(); } }
这个算法的基本思想就是通过一系列称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链。当一个对象到GC Roots没有任何引用链相连时 (用图论中的话来说,就是GC Root到这个对象不可达),则证明此对象是不可用的。如下图。
Java语言中,可作为GC Roots的对象包括下面几种:
上面阐述了堆为何要分成不同的回收代,下面看不同回收代之间如何协作已完成GC过程。以下图片讲述对象分配和在回收代之间的流转。
第一步,所有新对象都分配在eden空间,两个surviror空间刚开始为空。如下图。
第二步,eden空间被占满后,触发minor GC。如下图。
第三步,被引用对象被移到S0空间,未被引用对象在清除eden时被删除。如下图。
第四步,在下一次minor GC时,eden操作与第三步相同,即未被引用对象被删除,被引对象移到survivor空间,但是是移到S1。
另外,在S0空间中在上次minor GC存活的对象年龄增长并移到S1。当所有的存活对象移到S1后,将S0和eden清除。如下图。
第五步,在接下来的minor GC中,重复和第四步同样的过程,只不过survivor空间互换。将存活对象移到S0,存活对象年龄增长,Eden和S1被清除。如下图。
第六步,在每次minor GC时对象年龄都增长,当对象年龄超过某个阈值时 (假设为8) ,那么这些对象从新生代移到老年代。如下图。
第七步,随着minor GC不断发生,不断有对象从新生代移到老年代。如下图。
第八步,从上面看出,GC的大部分过程是在新生代进行的。直到触发major GC,老年代被清除并整理。如下图。
Serial GC一般不用于服务器端。但是虚拟机运行在Client模式下的默认新生代收集器,因为它简单高效 (与其他收集器的单线程相比)。
7.3 Parallel Scavenge收集器
1 private static final int _1MB = 1024 * 1024; 2 /** 3 * VM参数:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 4 */ 5 public static void testAllocation() { 6 byte[] allocation1, allocation2, allocation3, allocation4; 7 allocation1 = new byte[2 * _1MB]; 8 allocation2 = new byte[2 * _1MB]; 9 allocation3 = new byte[2 * _1MB]; 10 allocation4 = new byte[4 * _1MB]; // 出现一次Minor GC 11 }
1 private static final int _1MB = 1024 * 1024; 2 /** 3 * VM参数:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 4 * -XX:PretenureSizeThreshold=3145728 5 */ 6 public static void testPretenureSizeThreshold() { 7 byte[] allocation; 8 allocation = new byte[4 * _1MB]; //直接分配在老年代中 9 }
本文介绍垃圾收集作用、过程、垃圾收集算法、垃圾收集器和各种原理。GC在很多时候是影响系统性能、并发能力的主要因素之一。总之,想要写出高性能JAVA程序,懂GC是必要的!
参考资料:
深入理解JVM 周志明
http://www.oracle.com/webfolder/technetwork/tutorials/obe/java/gc01/index.html
http://www.cubrid.org/blog/dev-platform/understanding-java-garbage-collection/
标签:
原文地址:http://www.cnblogs.com/lin-xuan/p/5271299.html