java虚拟机7:垃圾收集器 基于回收算法理论的实现

垃圾收集器

垃圾收集器就是上面讲的理论知识的具体实现了。不同虚拟机所提供的垃圾收集器可能会有很大差别,我们使用的是HotSpot,HotSpot这个虚拟机所包含的所有收集器如图:

上图展示了7种作用于不同分代的收集器,如果两个收集器之间存在连线,那说明它们可以搭配使用。虚拟机所处的区域说明它是属于新生代收集器还是老年代收集器。多说一句,我们必须姚明带一个道理:没有最好的垃圾收集器,更加没有万能的收集器,只能选择对具体应用最合适的收集器。这也是HotSpot为什么要实现这么多收集器的原因。OK,下面一个一个看一下收集器:

1、Serial收集器

最基本、发展历史最久的收集器,这个收集器是一个采用复制算法的单线程的收集器,单线程一方面意味着它只会使用一个CPU或一条线程去完成垃圾收集工作,另一方面也意味着它进行垃圾收集时必须暂停其他线程的所有工作,直到它收集结束为止。后者意味着,在用户不可见的情况下要把用户正常工作的线程全部停掉,这对很多应用是难以接受的。不过实际上到目前为止,Serial收集器依然是虚拟机运行在Client模式下的默认新生代收集器,因为它简单而高效。用户桌面应用场景中,分配给虚拟机管理的内存一般来说不会很大,收集几十兆甚至一两百兆的新生代停顿时间在几十毫秒最多一百毫秒,只要不是频繁发生,这点停顿是完全可以接受的。

2、ParNew收集器

ParNew收集器其实就是Serial收集器的多线程版本,除了使用多条线程进行垃圾收集外,其余行为和Serial收集器完全一样,包括使用的也是复制算法。ParNew收集器除了多线程以外和Serial收集器并没有太多创新的地方,但是它却是Server模式下的虚拟机首选的新生代收集器,其中有一个很重要的和性能无关的原因是,除了Serial收集器外,目前只有它能与CMS收集器配合工作(看图)。CMS收集器是一款几乎可以认为有划时代意义的垃圾收集器,因为它第一次实现了让垃圾收集线程与用户线程基本上同时工作。ParNew收集器在单CPU的环境中绝对不会有比Serial收集器更好的效果,甚至由于线程交互的开销,该收集器在两个CPU的环境中都不能百分之百保证可以超越Serial收集器。当然,随着可用CPU数量的增加,它对于GC时系统资源的有效利用还是很有好处的。它默认开启的收集线程数与CPU数量相同,在CPU数量非常多的情况下,可以使用-XX:ParallelGCThreads参数来限制垃圾收集的线程数。

3、Parallel收集器

Parallel收集器也是一个新生代收集器,也是用复制算法的收集器,也是并行的多线程收集器,但是它的特点是它的关注点和其他收集器不同。介绍这个收集器主要还是介绍吞吐量的概念。CMS等收集器的关注点是尽可能缩短垃圾收集时用户线程的停顿时间,而Parallel收集器的目标则是打到一个可控制的吞吐量。所谓吞吐量的意思就是CPU用于运行用户代码时间与CPU总消耗时间的比值,即吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间),虚拟机总运行100分钟,垃圾收集1分钟,那吞吐量就是99%。另外,Parallel收集器是虚拟机运行在Server模式下的默认垃圾收集器

停顿时间短适合需要与用户交互的程序,良好的响应速度能提升用户体验;高吞吐量则可以高效率利用CPU时间,尽快完成运算任务,主要适合在后台运算而不需要太多交互的任务。

虚拟机提供了-XX:MaxGCPauseMillis和-XX:GCTimeRatio两个参数来精确控制最大垃圾收集停顿时间和吞吐量大小。不过不要以为前者越小越好,GC停顿时间的缩短是以牺牲吞吐量和新生代空间换取的。由于与吞吐量关系密切,Parallel收集器也被称为“吞吐量优先收集器”。Parallel收集器有一个-XX:+UseAdaptiveSizePolicy参数,这是一个开关参数,这个参数打开之后,就不需要手动指定新生代大小、Eden区和Survivor参数等细节参数了,虚拟机会根据当亲系统的运行情况手机性能监控信息,动态调整这些参数以提供最合适的停顿时间或者最大的吞吐量。如果对于垃圾收集器运作原理不太了解,以至于在优化比较困难的时候,使用Parallel收集器配合自适应调节策略,把内存管理的调优任务交给虚拟机去完成将是一个不错的选择

4、Serial Old收集器

Serial收集器的老年代版本,同样是一个单线程收集器,使用“标记-整理算法”,这个收集器的主要意义也是在于给Client模式下的虚拟机使用。

5、Parallel Old收集器

Parallel收集器的老年代版本,使用多线程和“标记-整理”算法。这个收集器在JDK 1.6之后的出现,“吞吐量优先收集器”终于有了比较名副其实的应用组合,在注重吞吐量以及CPU资源敏感的场合,都可以优先考虑Parallel收集器+Parallel Old收集器的组合。

6、CMS收集器

CMS收集器是一种以获取最短回收停顿时间为目标的老年代收集器。目前很大一部分Java应用集中在互联网站或者B/S系统的服务端上,这类应用尤其注重服务的响应速度,希望系统停顿时间最短,以给用户带来较好的体验,CMS收集器就非常符合这类应用的需求。CMS收集器从名字就能看出是基于“标记-清除”算法实现的

7、G1收集器

G1(Garbage-First)收集器是当今收集器技术发展的最前沿成果之一,JDK 7 Update 4后开始进入商用。在G1收集器之前的其他收集器进行收集的范围都是整个新生代或者老年代,而G1收集器不再是这样,使用G1收集器时,Java堆的内存布局就与其他收集器有很大差别,它将整个Java堆分为多个大小相等的独立区域(Region),虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔离的了,它们都是一部分Region的集合。G1收集器跟踪各个Region里面的垃圾堆积的价值大小,在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region(这也是Garbage-First名称的由来)。这种使用Region划分内存空间以及有优先级的区域回收方式,保证了G1收集器在有限的时间内可以获取尽可能高的收集效率。

 

垃圾收集器总结

来看一下对垃圾收集器的总结,列了一张表

                         

         

        GC组合                                                                                          

    

    

    Minor GC                                           

      

      

     Full GC                                                                                                               

描述
-XX:+UseSerialGCSerial收集器串行回收Serial Old收集器串行回收该选项可以手动指定Serial收集器+Serial Old收集器组合执行内存回收
-XX:+UseParNewGCParNew收集器并行回收Serial Old收集器串行回收该选项可以手动指定ParNew收集器+Serilal Old组合执行内存回收
-XX:+UseParallelGCParallel收集器并行回收Serial Old收集器串行回收该选项可以手动指定Parallel收集器+Serial Old收集器组合执行内存回收
-XX:+UseParallelOldGCParallel收集器并行回收Parallel Old收集器并行回收该选项可以手动指定Parallel收集器+Parallel Old收集器组合执行内存回收
-XX:+UseConcMarkSweepGCParNew收集器并行回收 缺省使用CMS收集器并发回收,备用采用Serial Old收集器串行回收

该选项可以手动指定ParNew收集器+CMS收集器+Serial Old收集器组合执行内存回收。优先使用ParNew收集器+CMS收集器的组合,当出现ConcurrentMode Fail或者Promotion Failed时,则采用ParNew收集器+Serial Old收集器的组合

-XX:+UseConcMarkSweepGC

-XX:-UseParNewGC

Serial收集器串行回收
-XX:+UseG1GCG1收集器并发、并行执行内存回收暂无

 

GC日志

每种收集器的日志形式都是由它们自身的实现所决定的,换言之,每种收集器的日志格式都可以不一样。不过虚拟机为了方便用户阅读,将各个收集器的日志都维持了一定的共性,就以的java虚拟机4:jvm GC机制回收 判断对象生死 及 方法区永久代回收条件 TestReferenceCountingGC代码为例:

虚拟机参数为“-XX:+PrintGCDetails -XX:+UseSerialGC”,使用Serial+Serial Old组合进行垃圾回收的日志

[GC [DefNew: 310K->194K(2368K), 0.0269163 secs] 310K->194K(7680K), 0.0269513 secs] [Times: user=0.00 sys=0.00, real=0.03 secs] 
[GC [DefNew: 2242K->0K(2368K), 0.0018814 secs] 2242K->2241K(7680K), 0.0019172 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (System) [Tenured: 2241K->193K(5312K), 0.0056517 secs] 4289K->193K(7680K), [Perm : 2950K->2950K(21248K)], 0.0057094 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
Heap
 def new generation   total 2432K, used 43K [0x00000000052a0000, 0x0000000005540000, 0x0000000006ea0000)
  eden space 2176K,   2% used [0x00000000052a0000, 0x00000000052aaeb8, 0x00000000054c0000)
  from space 256K,   0% used [0x00000000054c0000, 0x00000000054c0000, 0x0000000005500000)
  to   space 256K,   0% used [0x0000000005500000, 0x0000000005500000, 0x0000000005540000)
 tenured generation   total 5312K, used 193K [0x0000000006ea0000, 0x00000000073d0000, 0x000000000a6a0000)
   the space 5312K,   3% used [0x0000000006ea0000, 0x0000000006ed0730, 0x0000000006ed0800, 0x00000000073d0000)
 compacting perm gen  total 21248K, used 2982K [0x000000000a6a0000, 0x000000000bb60000, 0x000000000faa0000)
   the space 21248K,  14% used [0x000000000a6a0000, 0x000000000a989980, 0x000000000a989a00, 0x000000000bb60000)
No shared spaces configured.

虚拟机参数为“-XX:+PrintGCDetails -XX:+UseParNewGC”,使用ParNew+Serial Old组合进行垃圾回收的日志

[GC [ParNew: 310K->205K(2368K), 0.0006664 secs] 310K->205K(7680K), 0.0007043 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC [ParNew: 2253K->31K(2368K), 0.0032525 secs] 2253K->2295K(7680K), 0.0032911 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (System) [Tenured: 2264K->194K(5312K), 0.0054415 secs] 4343K->194K(7680K), [Perm : 2950K->2950K(21248K)], 0.0055105 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
Heap
 par new generation   total 2432K, used 43K [0x0000000005550000, 0x00000000057f0000, 0x0000000007150000)
  eden space 2176K,   2% used [0x0000000005550000, 0x000000000555aeb8, 0x0000000005770000)
  from space 256K,   0% used [0x0000000005770000, 0x0000000005770000, 0x00000000057b0000)
  to   space 256K,   0% used [0x00000000057b0000, 0x00000000057b0000, 0x00000000057f0000)
 tenured generation   total 5312K, used 194K [0x0000000007150000, 0x0000000007680000, 0x000000000a950000)
   the space 5312K,   3% used [0x0000000007150000, 0x0000000007180940, 0x0000000007180a00, 0x0000000007680000)
 compacting perm gen  total 21248K, used 2982K [0x000000000a950000, 0x000000000be10000, 0x000000000fd50000)
   the space 21248K,  14% used [0x000000000a950000, 0x000000000ac39980, 0x000000000ac39a00, 0x000000000be10000)
No shared spaces configured.

虚拟机参数为“-XX:+PrintGCDetails -XX:+UseParallelGC”,使用Parallel+Serial Old组合进行垃圾回收的日志

[GC [PSYoungGen: 4417K->288K(18688K)] 4417K->288K(61440K), 0.0007910 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (System) [PSYoungGen: 288K->0K(18688K)] [PSOldGen: 0K->194K(42752K)] 288K->194K(61440K) [PSPermGen: 2941K->2941K(21248K)], 0.0032663 secs] [Times: user=0.02 sys=0.00, real=0.00 secs] 
Heap
 PSYoungGen      total 18688K, used 321K [0x0000000034190000, 0x0000000035660000, 0x0000000048f90000)
  eden space 16064K, 2% used [0x0000000034190000,0x00000000341e05c0,0x0000000035140000)
  from space 2624K, 0% used [0x0000000035140000,0x0000000035140000,0x00000000353d0000)
  to   space 2624K, 0% used [0x00000000353d0000,0x00000000353d0000,0x0000000035660000)
 PSOldGen        total 42752K, used 194K [0x000000000a590000, 0x000000000cf50000, 0x0000000034190000)
  object space 42752K, 0% used [0x000000000a590000,0x000000000a5c0810,0x000000000cf50000)
 PSPermGen       total 21248K, used 2982K [0x0000000005190000, 0x0000000006650000, 0x000000000a590000)
  object space 21248K, 14% used [0x0000000005190000,0x0000000005479980,0x0000000006650000)

虚拟机参数为“-XX:+PrintGCDetails -XX:+UseConcMarkSweepGC”,使用ParNew+CMS+Serial Old组合进行垃圾回收的日志

[Full GC (System) [CMS: 0K->194K(62656K), 0.0080796 secs] 4436K->194K(81792K), [CMS Perm : 2941K->2940K(21248K)], 0.0081589 secs] [Times: user=0.01 sys=0.00, real=0.01 secs] 
Heap
 par new generation   total 19136K, used 340K [0x0000000005540000, 0x0000000006a00000, 0x0000000006a00000)
  eden space 17024K,   2% used [0x0000000005540000, 0x0000000005595290, 0x00000000065e0000)
  from space 2112K,   0% used [0x00000000065e0000, 0x00000000065e0000, 0x00000000067f0000)
  to   space 2112K,   0% used [0x00000000067f0000, 0x00000000067f0000, 0x0000000006a00000)
 concurrent mark-sweep generation total 62656K, used 194K [0x0000000006a00000, 0x000000000a730000, 0x000000000a940000)
 concurrent-mark-sweep perm gen total 21248K, used 2981K [0x000000000a940000, 0x000000000be00000, 0x000000000fd40000)

这四段GC日志中提炼出一些共性:

1、日志的开头“GC”、“Full GC”表示这次垃圾收集的停顿类型,而不是用来区分新生代GC还是老年代GC的。如果有Full,则说明本次GC停止了其他所有工作线程。看到Full GC的写法是“Full GC(System)”,这说明是调用System.gc()方法所触发的GC。

2、“GC”中接下来的“DefNew”、“ParNew”、“PSYoungGen”、“CMS”表示的是新生代垃圾收集器的名称,“PSYoungGen”中的“PS”指的是“Parallel Scavenge”,它是Parallel收集器的全称。

3、以第一个为例,方括号内部的“320K->194K(2368K)”、“2242K->0K(2368K)”,指的是该区域已使用的容量->GC后该内存区域已使用的容量(该内存区总容量)。方括号外面的“310K->194K(7680K)”、“2242K->2241K(7680K)”则指的是GC前Java堆已使用的容量->GC后Java堆已使用的容量(Java堆总容量)

4、还以第一个为例,再往后“0.0269163 secs”表示该内存区域GC所占用的时间,单位是秒。最后的“[Times: user=0.00 sys=0.00 real=0.03 secs]”则更具体了,user表示用户态消耗的CPU时间、内核态消耗的CPU时间、操作从开始到结束经过的钟墙时间。后面两个的区别是,钟墙时间包括各种非运算的等待消耗,比如等待磁盘I/O、等待线程阻塞,而CPU时间不包括这些耗时,但当系统有多CPU或者多核的话,多线程操作会叠加这些CPU时间所以如果user或sys超过real是完全正常的。

5、“Heap”后面就列举出堆内存目前各个年代的区域的内存情况

 

触发GC的时机

最后总结一下什么时候会触发一次GC,个人经验看,有三种场景会触发GC:

1、第一种场景应该很明显,当年轻代或者老年代满了,Java虚拟机无法再为新的对象分配内存空间了,那么Java虚拟机就会触发一次GC去回收掉那些已经不会再被使用到的对象

2、手动调用System.gc()方法,通常这样会触发一次的Full GC以及至少一次的Minor GC

3、程序运行的时候有一条低优先级的GC线程,它是一条守护线程,当这条线程处于运行状态的时候,自然就触发了一次GC了。这点也很好证明,不过要用到WeakReference的知识,后面写WeakReference的时候会专门讲到这个。



posted on 2021-02-24 14:38  signheart  阅读(84)  评论(0编辑  收藏  举报

导航