JVM垃圾收集器

各种收集器介绍:
Serial收集器:单线程收集器,必须暂停其他所有的工作线程,默认client模式下新生代的收集器
ParNew收集器:Serial的多线程版本,一般在Servr模式下的新生代首选收集器,除Serial外,目前只有它能与CMS收集器配合工作,ParNew收集器在单cpu的情况下不会有比Serial收集器更好的效果,甚至由于存在线程交互的开销,该收集器在通过超线程技术实现的两个cpu的环境中都一定能超越 Serial收集器。
Parallel Scavenge收集器:新生代收集器,使用复制算法,是并行的多线程收集器,跟其他收集器不一样,CMS等收集器是关注尽可能的缩短垃圾收集时用户线程的停顿时间,而Parallel Scavenge收集器的目标则是达到一个可控制的吞吐量。吞吐量是cpu用于运行用户代码的时间与cpu总消耗时间的比值。
Serial Old收集器:单线程收集器,是Serial收集器老年代版本,使用“标记-整理”算法,主要用在client模式下,
Parallel Old收集器:Parallel Scavenge收集器的老年代版本,在注重吞吐量及CPU资源敏感的场合,都可以优先考虑Parallel Scavenge收集器家Parallel Old收集器。
CMS收集器:使用“标记-清除”算法,以获取最短回收停顿时间为目标,对于重视服务的相应速度,希望系统停顿时间最短的B/S系统的服务器端尤其有用。CMS收集器分4个步骤:1,初始标记 2,并发标记 3,重新标记 4,并发清除 耗时最长的并发标记和并发清除都可以与用户线程一起工作了。 还有三个缺点:1,对cpu资源敏感,默认启动的回收线程数是(cpu数量+3)/4,当cpu数较少的时候,会分掉大部分的cpu去执行收集器线程,影响用户,降低吞吐量。 2,无法处理浮动垃圾,浮动垃圾即在并发清除阶段因为是并发执行,还会产生垃圾,这一部分垃圾即为浮动垃圾,要等下次收集。3,因为使用的是“标记-清除”算法,会产生碎片。
G1收集器(Garbage First):基于“标记-整理”算法,之前的垃圾收集器都是整个新生代或者老生代,而G1将整个java堆(包括新生代和老生代)划分为多个大小固定的独立区域,并跟踪这些区域的垃圾堆积程度,在后台维护一个优先列表,每次根据允许的收集时间,优先回收垃圾最多的区域(也是Garbage First的由来)。
下面是一个垃圾收集的例子,运行的参数为:
-verbose:gc -Xms20m -Xmx20M -Xmn10M -XX:SurvivorRatio=8 -XX:+PrintGCDetails -XX:+UseSerialGC
Xms20m表示分配的最小堆内存,Xmx20M表示最大堆内存,Xmn10m表示分给新生代的内存为10m,SurvivorRatio表示新生代的eden和from与to区域的内存占比,PrintGCDetails表示打印垃圾收集的日志信息,UseSerialGC表示新生代使用SerialGC收集器。
代码很简单,如下:
private static final int _1MB=1024*1024;
	public static void main(String[] args) throws Exception {
		byte[] a1,a2,a3,a4,a5,a6,a7;
		a1 = new byte[2*_1MB];
		a2 = new byte[2*_1MB];
		a3 = new byte[2*_1MB];
		
		System.out.println("1....");
		a4 = new byte[2*_1MB];
		
		a5 = new byte[2*_1MB];
		a6 = new byte[2*_1MB];
		System.out.println("2....");
		a7 = new byte[2*_1MB];
	}

运行之后,得到信息如下(详细解释在注释里面):
1....
[GC [DefNew: 6487K->151K(9216K), 0.0432371 secs] 6487K->6295K(19456K), 0.0433072 secs] [Times: user=0.02 sys=0.00, real=0.05 secs] 
2....
[GC [DefNew: 6381K->6381K(9216K), 0.0000447 secs][Tenured: 6144K->8192K(10240K), 0.4194740 secs] 12525K->12439K(19456K), [Perm : 374K->374K(12288K)], 0.4196179 secs] [Times: user=0.00 sys=0.00, real=0.42 secs] 
Heap
 def new generation   total 9216K, used 6459K [0x315e0000, 0x31fe0000, 0x31fe0000)
  eden space 8192K,  78% used [0x315e0000, 0x31c2ede0, 0x31de0000)
  from space 1024K,   0% used [0x31ee0000, 0x31ee0000, 0x31fe0000)
  to   space 1024K,   0% used [0x31de0000, 0x31de0000, 0x31ee0000)
 tenured generation   total 10240K, used 8192K [0x31fe0000, 0x329e0000, 0x329e0000)
   the space 10240K,  80% used [0x31fe0000, 0x327e0040, 0x327e0200, 0x329e0000)
 compacting perm gen  total 12288K, used 374K [0x329e0000, 0x335e0000, 0x369e0000)
   the space 12288K,   3% used [0x329e0000, 0x32a3d970, 0x32a3da00, 0x335e0000)
    ro space 10240K,  54% used [0x369e0000, 0x36f5ee00, 0x36f5ee00, 0x373e0000)
    rw space 12288K,  55% used [0x373e0000, 0x37a82800, 0x37a82800, 0x37fe0000)

因为新生代有10m的空间,内存一开始都是分配在新生代的eden区,eden区大小为8m,所以a1,a2,a3都能正常分配,eden区空闲的不完全有8m的空间,所以分配a4的时候没有足够的空间,因此要发生minor GC,而新生代的survivor区(1m )也无法将a4装入,所以只好通过担保机制转移到老年代,即1....下面的信息
[GC [DefNew: 6487K->151K(9216K), 0.0432371 secs] 6487K->6295K(19456K), 0.0433072 secs] [Times: user=0.02 sys=0.00, real=0.05 secs]
其中DefNew:6487k->151k表示新生代从占用6487k内存变到只占用151内存,即大约有6m的内存被转移走了,看代码,即知a1,a2,a3被转移到老年代去了,后面的6487k->6295k是表示总内存从占用6487k到6295k,基本上没变化。最后再给a4分配2m的内存,即现在新生代只放了a4,老生代放了a1,a2,a3.接下来a5,a6也被分配到新生代,新生代大约用了6m的样子,分配a7的时候新生代空间不够了,即发生第二次垃圾收集,即2.....下面的信息:
[GC [DefNew: 6381K->6381K(9216K), 0.0000447 secs][Tenured: 6144K->8192K(10240K), 0.4194740 secs] 12525K->12439K(19456K), [Perm : 374K->374K(12288K)], 0.41
从以上信息可知,新生代DefNew从6381k->6381k没有变化,老生代Tenured从6144k->8192k大约增长了2m,即a7是直接分配在老生代。后面的信息是各种内存的占比。



你可能感兴趣的:(JVM垃圾收集器)