垃圾回收相关
垃圾收集器
警告:纯八股文!
如果说上面我们讲的收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体体现.
垃圾收集器的作用:垃圾收集器是为了保证程序能够正常,持久运行的一种技术,它是将程序中不用的死亡对象也就是垃圾对象进行清除,从而保证新的对象能够正常申请到内存空间.
垃圾回收器和垃圾回收算法的关系:
垃圾回收器是一种负责自动管理内存的软件组件,它负责在程序运行时识别和回收不再使用的内存(即垃圾),以便将内存重新分配给程序的其他部分.垃圾回收器实现了垃圾回收算法,这些算法决定了如何识别和回收垃圾.
1.垃圾回收器是实现垃圾回收算法的具体实体或组件,它是负责执行垃圾回收算法的执行者.
2.垃圾回收算法是一组规则和策略.用于确定哪些内存被视为垃圾,并在何时回收这些内存.
3.垃圾回收器通过调用和执行垃圾回收算法来实现内存管理.它负责在适当时机进行垃圾回收.并选择适当回收垃圾回收算法来回收内存.不同的垃圾处理器能实现不同的垃圾回收算法,以满足不同场景的需求.
以下这些收集器是HotSpot虚拟机伴随着不同版本推出的重要的垃圾收集器:
上图展示了7种作用于不同分代的收集器,如果两个收集器之间存在连线,就说明它们可以搭配使用. 所处的区域,表示它是属于新生代收集器还是老年代收集器.再讲具体的收集器之前我们先来明确三个概念:
并行:指多条垃圾收集线程并行工作,用户线程仍处于等待状态.
并发:指用户线程和垃圾回收线程同时执行(不一定并行,可能会交替执行),用户程序继续执行,而垃圾收集程序在另外一个CPU上.
吞吐量:就是CPU用于运行用户代码的时间与CPU总消耗时间的比值.
吞吐量=运行用户代码时间 / (运行用户代码时间 + 垃圾收集时间)
例如:虚拟机总共运行了100分钟,其中垃圾收集花掉1分钟,那么吞吐量就是99%.
为什么会有这么多的垃圾收集器?
JVM有多种垃圾收集器,是因为不同的应用场景和需求可能需要不同的垃圾回收算法和策略.这些垃圾回收器的存在是为了在不同情况下提供最佳性能和吞吐量,并满足应用程序要求.
1.应用场景多样化:不同类型的应用程序具有不同的内存使用模式和性能要求.例如,一些应用可能需要低延迟,而另一些应用可能更注重吞吐量.因此需要不同类型的垃圾收集器来满足需求.
2.内存使用模式的不同:一些应用程序可能会生成大量短期对象,不同的垃圾收集器能针对这些不同的内存使用方式优化.
3.硬件和操作系统的不同:不同的硬件平台和操作系统可能对垃圾收集器产生不同的影响.因此存在多种垃圾回收器可以在不同的硬件和操作系统环境下提供最佳的性能.
4.持续的研究,更进,历史发展:自从有了Java语言就有了垃圾收集器,这么多垃圾收集器其实是历史发展的产物.最早的垃圾回收器为Serial,也就是串行执行的垃圾收集器,Serial Old为串行的老年代收集器,而随着时间的发展,为了提升更高的性能,于是就有了Serial多线程版的垃圾回收器ParNew.后来人们想要更高吞吐量的垃圾收集器,吞吐量是指单位时间内成功回收垃圾的数量,于是就有了吞吐量优先的垃圾收集器Parallel Scavenge(吞吐量优先的新生代垃圾收集器)和Parallel Old(吞吐量优先的老年代垃圾收集器).随着技术的发展后来又有了CMS垃圾收集器,CMS可以兼顾吞吐量和以获取最短回收停顿时间为目标的收集器,在JDK1.8(包含)之前BS系统的主流垃圾收集器,而在JDK1.8之后,出现了第一个既不完全属于新生代也不完全属于老年代的垃圾收集器G1,G1提供了基本不需要停止程序就可以收集垃圾的技术.
下面我们来看每种垃圾收集器的具体介绍:
CMS收集器(老年代收集器,并发GC)
特性:CMS收集器是一种以获取最短回收停顿时间为目标的收集器.目前很大一部分的Java应用几种在互联网站或者B/S系统的服务端上,这类应用尤其重视服务的响应速度,希望系统停顿时间最短,给用户带来较好的体验.CMS收集器就非常符合这类应用的需求.
CMS收集器是基于"标记-清除"算法实现的,它的运作过程较为复杂,整个过程分为四个步骤:
初始标记:初始标记仅仅只是标记一下GC Roots能直接关联到的对象,速度很快,需要"Stop the world".("Stop the world"是指在垃圾收集的过程当中,应用程序的所有线程都会停止执行,这样垃圾收集器可以独占地执行垃圾收集操作.在停止的过程中,所有的用户线程都会暂停,包括应用程序的业务逻辑和其它的并发操作.这种暂停时间是可预测的,但是会对系统的响应时间和吞吐量产生影响.)
并发标记:并发标记阶段就是进行Roots Tracing的过程.
重新标记:重新标记阶段是为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象标记记录,这个阶段的停顿时间一般会比初始标记阶段稍微长一些,但远比并发标记的时间短,仍然需要"Stop the world".
并发清除:并发清除阶段会清除对象.
由于整个过程中耗时最长的并发标记和并发清除过程收集器线程可以与用户线程一起工作,所以,从整体上来说,CMS收集器的内存回收过程是与用户线程一起并发执行的.
优点:
CMS是一款优秀的收集器,它的主要优点在名字上已经体现出来了:并发收集,低停顿
缺点:
CMS收集器对CPU资源非常敏感. 其实,面向并发设计的程序都对CPU资源比较敏感.在并发阶段,它虽然不会导致用户线程停顿,但是会因为占用了一部分线程(或者CPU资源)而导致应用程序变慢,总吞吐量会降低. CMS默认启动的回收线程数是(CPU数量+3)/4.也就是当CPU在4个以上时,并发回收时垃圾收集线程不少于25%的CPU资源.并且随着CPU数量的增加而下降.但是当CPU不足4个(譬如2个)时,CMS对用户程序的影响就可能变得很大.
CMS收集器无法处理浮动垃圾,可能会出现"Concurrent Mode Failure"失败而导致另一次Full GC的产生.由于CMS并发清理阶段用户线程还在运行着,伴随程序运行字眼就还会有新的垃圾不断产生,这一部分垃圾出现在标记过程之后,CMS无法在当次收集中处理掉它们,只好留待下一次GC再清理掉. 这一部分就叫做"浮动垃圾".也是由于在垃圾回收阶段用户线程还需要运行,那也就还需要预留有足够的内存空间给用户线程使用,因此CMS收集器不能像其他收集器那样等到老年代几乎完全被填满了再进行收集,需要预留一部分空间提供并发收集时的程序运作使用.要是CMS运行期间预留的内存无法满足程序需要,就会出现一次"Concurrent Mode failure"失败,这是虚拟机将启动后备预案:临时启动Serial Old收集器来重新进行老年代的垃圾收集,这样停顿的时间就很长了.
CMS收集器会产生大量空间碎片
CMS是一款基于"标记-清除"算法实现的收集器,这意味着收集结束时会有大量空间碎片的产生.空间碎片过多时,将会给大对象分配带来很大麻烦,往往会出现老年代还有很大空间剩余,但是无法找到足够大的连续空间来分配当前对象,不得不提前触发一次Full GC.
G1收集器(唯一一款全区域的垃圾回收器)
G1垃圾收集器是用在heap memory很大的情况下, 把heap划分为很多很多的region 块, 然后并行的对其进行垃圾回收.
G1垃圾收集器在清除实例所占用的内存空间后,还会做内存压缩.
G1垃圾收集器回收region的时候基本不会STW,而是基于most garbage优先回收(整体来看是基于"标记-整理"算法, 从局部(两个region之间)基于"复制"算法)的策略来对region进行垃圾回收的.无论如何,G1收集器采用的算法都意味着一个region有可能属于Eden,Survivor或者Tenured内存区域.图中的E表示改region属于Eden内存区域,S属于Survivor内存区域,T属于Tenured内存区域.图中空白的表示未使用的内存空间.G1垃圾收集器还增加了一种新的内存区域,叫做Humongous内存区域,如图中的H块.这种内存区域主要用于存储大的对象-即大小超过一个region大小的50%对象.
年轻代垃圾收集
在G1垃圾收集器中,年轻代的垃圾回收过程使用复制算法,把Eden区和Survivor区的对象复制到新的Survivor区域.
如下图:
老年代垃圾收集
对于老年代上的垃圾收集,G1垃圾收集器也分为4个阶段,基本跟CMS垃圾收集器一样,但略有不同.
初始标记阶段-同CMS垃圾收集器的初始标记阶段一样,G1也需要暂停应用程序的执行,它会标记从根对象出发,在根对象的第一层孩子节点中标记所有可达的对象.但是G1的垃圾收集器的初始标记是跟minor gc一同发生的. 也就是说,在G1中,你不用像CMS那样,单独暂停应用程序来进行初始标记阶段,而是在G1触发minor gc的时候一并将老年代的初始标记给做了
并发标记阶段-在这个阶段G1做的事情跟CMS一样.但G1同时还做了一件事情,就是如果在并发标记阶段中,发现哪些Tenured region中对象的存活率很小或者基本没有对象存活,那么G1就会在这个阶段将其回收掉,而不用等到后面的clean up阶段.这也是Garbage First名字的由来.同时,在该阶段,G1会计算每个region的对象存活率,方便后面的clean up阶段使用.
最终标记-在这个阶段G1做的事情和CMS一样,但是采用的算法不同,G1采用一种叫STAB的算法能够在Remark阶段更快的标记可达对象.
筛选回收阶段-在G1中,没有CMS中对应的Sweep阶段.相反它有一个Clean up/Copy阶段,在这个阶段中,G1会挑选出那些对象存活率低的region进行回收,这个阶段也是与minor gc一同发生的,如下图所示:
G1是一款面向服务端应用的垃圾收集器.HotSpot开发团队赋予它的使命是未来可以替换掉JDK1.5中发布的CMS收集器.如果你的应用追求低停顿,G1可以作为选择;如果你的应用追求吞吐量,G1并不带来明显的好处.
总结:一个对象的一生
一个对象的一生:我是个普通的Java对象,我出生在Eden区,在Eden区我还看到了和我长得很像的小兄弟,我们在Eden区中玩了挺长时间.有一天Eden区的人实在是太多了,我就被迫去了Survivor区的"From"区(s0区),有时候在Survivor的"To"区(s1区),居无定所.知道我18岁的时候,爸爸说我成人了,该去社会上闯闯了.于是我去了老年代那边,老年代里,人很多,并且年龄都挺大的,我在这里也认识了很多人.在老年代里,我生活了很多年(每次GC加一岁)然后被回收了.