深入理解Java虚拟机阅读心得21/4/29

今天来具体了解一下在虚拟机中垃圾回收的各种收集器,收集算法是垃圾收集的方法论,而收集器就是其的具体实现,关于各种收集器的选择,Java虚拟机并没有定义该如何实现垃圾收集器,所以一般各个厂商的收集器都是不同的,而且也会提供参数来让用户自己选择各个年代区域所使用的垃圾收集器。

1.Serial
了解这个英文单词意思的读者应该能猜到这是一个单线程的垃圾收集器,很可惜我的英文基础相当差劲,体会不到这种见名知意的舒畅感。
不过深入了解的话,就能知道这个收集器的名字并不只是只用一条线程进行垃圾回收处理的意思,而是在这个垃圾收集器开始工作时,虚拟机中所有正在执行的线程都必须停顿下来以避免数据产生异常,这种现象叫做“Stop The World”,听起来有点高大上,不过在有些情况下确是一个非常致命的问题,在我之前写的《为什么需要JVM调优》中也总结过,如果在像淘宝或者京东双11/618活动期间,系统出现STW,这将会带来巨大的损失。并且这种停顿是不可见的,你想一下如果你的电脑每运行一小时,就要停止响应五分钟,那么像我这种重度游戏玩家可能心中会产生不可逆的损伤。不过这个问题也无法避免,就好像妈妈在给你打扫房间,而你还在房间中制造垃圾,那么打扫房间的时间成本就会大大上升。
虽然上面说了Serial有这么多的缺点,但直到现在,它也还是虚拟机运行在Client使用最多的新生代收集器,因为它简单也足够高效,对于限定单个CPU的环境来说,Serial收集器由于没有线程交互的开销,专心做垃圾收集自然可以做到最好的单线程收集效率。在普通用户级的桌面应用中,虚拟机分配的空间不会很大,收集几十兆甚至一两百兆的新生代,停顿时间完全可以控制在几十毫秒到一百毫秒之内,只要不是发生得过于频繁,这点时间可以接受。所以,Serial对于运行在Client下的虚拟机来说依旧是非常好的选择。
2.ParNew
ParNew收集器其实就是Serial收集器的多线程版本,除了使用多条线程进行垃圾回收的机制,其他的参数配置,回收算法等都是和Serial一样的。在server环境下是新生代的首选收集器,因为除去非性能方面的因素说,它是唯一一个可以与CMS一起使用的新生代收集器,关于CMS收集器将会在本文的后续说到。但可惜的是,由于需要管理线程交互资源的消耗,在使用超线程技术完成的双CPU情况下,其性能可能还是不能与Serial收集器一较高低,但随着CPU线程的增加,目前的机器基本上都是四核起步,并且使用了很多的超线程,在这种情况下,ParNew收集器来管理新生代中的CG回收会是非常好的选择。
从ParNew收集器开始,后续还会介绍到其他几个在并行和并发的收集器,在这里解释一下这两个词的含义:
并行:垃圾收集器使用多条线程同时进行垃圾回收,但此时用户线程仍然处在等待状态。
并发:用户线程和垃圾收集器线程同时进行(但不一定是并行的,可能会交替进行),用户程序在继续执行,而垃圾收集器在另一个CPU上运行。
**3.Parallel Scavenge **
Parallel Scavenge是一个新生代收集器,也是使用的复制-清理算法,同样也是并行多线程收集器,那么它和ParNew的定位不就重复了吗,当然是不可能的。这里就要引出一个新的概念了,吞吐量:用户代码运行时间在总运行时间中的比例。比如在整个运行时间中用了100分钟,垃圾回收用了一分钟,那么用户代码运行时间就用了九十九分钟,那么这个吞吐量就等于99/100,百分之九十九。ParNew、CMS收集器的目的都相同,就是尽可能的降低垃圾回收中的停顿时间,而Parallel Scavenge的目的就在于达到一个可控制的吞吐量。
停顿时间越短就可以减少用户等待的时间,比较适合和用户频繁交互的情景,而高吞吐量就可以高效率的利用CPU的资源,尽快的完成程序的运算任务,适合不需要太多与用户进行交互的后台计算任务。
Parallel Scavenge设置了两个参数来精确控制吞吐量,一个是控制最大垃圾收集停顿时间的 -XX:MaxGCPauseMillis参数,另一个是直接控制垃圾回收吞吐量大小的-XX:GCTimeRatio参数。
MaxGCPauseMillis允许的值是一个大于零的毫秒数,那大家肯定会想这个值不是调整的越小越好吗,实际上不是这样的,垃圾收集停顿时间的减小是通过牺牲新生代的空间以及吞吐量换来的,数值设置的小,新生代的空间也会降低,吞吐量也会变小,垃圾回收会更加频繁,比如之前是100ms收集一次,每次停顿10s,停顿时间拉低后,70ms收集一次,每次停顿时间7s,这样吞吐量降低了,就违背了Parallel Scavenge收集器设计的目的。
GCTimeRatio参数的值是一个大于等于一小于等于九十九的数,它的值就类似吞吐量的倒数,比如如果该值设置为19,那么吞吐量就等于(1/(1+19))=5%,其默认值是99,就是(1/(1+99))=1%的允许垃圾回收时间。
除了以上两个参数,Parallel Scavenge还有一个值得关注的开关参数:-XX:UseAdaptiveSizePolicy,当这个开关打开之后,垃圾回收器中的细节设计比如新生代,老生代,Eden区的大小等等详细参数就不需要用户考虑了,收集器会根据当前系统运行的性能监视信息来自动设置这些参数,以达到最适合的停顿时间和吞吐量。这种调节方式称为GC自适应的调节策略,如果同学们对收集器原理不太了解,手动优化困难时,不如直接使用该开关,只要设置基本的内存数据,然后根据刚开始介绍的两个参数来给定一个优化的目标,其他的具体设置就可以呀由虚拟机自己完成了,CG自适应调节模式也是Parallel Scavenge与ParNew的区别之一。
4.Serial Old
Serial Old就是Serial的老年代版本,同样是一个单线程收集器,同样是主要提供在Client的情景下使用的。而在Server环境下,则主要有两个作用,一个是在JDK1.5之前与Parallel Scavenge配合使用,还有一个则是作为CMS的后备预案,在并发收集发生Concurrent Mode Failure时使用,这两点的详细内容会在后续提到。
5.Parallel Old
同上一个收集器一样见名知意,Parallel Old就是Parallel Scavenge的老年代的版本,在Parallel Old出现之前,新生代收集器Parallel Scavenge一直处在一个非常尴尬的处境,因为其不能与CMS协同使用,所以在老年代只能使用Serial Old进行配合(Parallel Scavenge其中实际上是自带了老年代收集器的,但其处理机制与Serial Old十分接近,为了不再加入多的概念而导致混淆,这里就用Serial Old进行讲述了),由于单线程Serial Old在服务端应用性能上的拖累,即使新生代中使用了Parallel Scavenge也往往无法达到高吞吐量的目地,由于其无法充分利用服务端多CPU的优势,在老年代较大而且硬件比较高级的情况下,这种组合的吞吐量还不一定有ParNew加CMS这种主打低停顿时间的收集器带来的吞吐量可观。
知道Parallel Old出现后,Parallel Scavenge才终于有了扬眉吐气的机会,在注重吞吐量和CPU资源敏感的情况下,都可以优先考虑使用Parallel Scavenge加Parallel Old的组合。
6.CMS
CMS收集器是一种追求最短停顿时间的并发收集器,目前很多的Java程序都运行在B/S模式的服务端以及网站上,在这种与用户交互频率极高的情况下,CMS带来的低停顿时间的优势是非常明显的。
CMS的收集过程主要包含以下四步:
1.初始标记
2.并发标记
3.重新标记
4.并发清除
其中的初始标记和重新标记都仍是会STW的,初始标记仅仅是标记以下GC Roots能直接关联到的对象,速度非常快,而并发标记就是进行GC Roots Tracing的过程,而重新标记则是为了处理在用户线程继续运行而导致的标记状态产生变化的那一批对象,这个阶段的停顿时间会比初始标记稍微长一点,但远远小于并发标记的时间。而由于整个过程中最耗时间的并发标记和并发清除都可以与用户线程一起运行,因此在整个宏观上来看,CMS收集器内存收集过程是与用户线程一起并发执行的。
CMS的优点很明显,低停顿,并发式。但其并不是完美的垃圾收集器,它有三个明显的缺点。
1.CMS收集器对CPU资源非常的敏感,不过其实面向并发设计的程序对CPU资源都很敏感。在并发阶段中,由于占用了一部分线程,会导致用户程序运行速度变慢,吞吐量降低,而CMS默认启动的回收线程是(CPU数量+3/4),这意味这当CPU数量在4以上时,CMS占用CPU资源不低于百分之二十五并将随着CPU数量的上升而下降,但如果在性能比较低,CPU数量低于四个的情况下,就会对程序产生非常大的影响,本来CPU运行程序负载就已经不低,还要分出一半的运算能力来进行垃圾回收,这可能会让程序的运行速度直接降低50%,这对于用户来说明显是无法接受的。
2.还有两个缺点和传说中跨时代的G1收集器,因为我肚子太饿了,顶不住先去吃饭了,下次有时间再更,告辞!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值