前言
关于垃圾收集的一些理论基础,我在上一篇文章《从头开始学习JVM(九):垃圾收集(上)》中已经讲述完了,本文讲的是具体的如何实现的问题。
但是要注意的是,本文所讲的东西,都是基于上一篇文章的理论基础上的,所以再看本篇文章之前,笔者非常建议您先去看上一篇文章,然后再来看本文,这样才能对垃圾收集有一个整体的,闭环的印象。
当然,如果您觉得您对垃圾收集的理论部分,有着自己独特的见解,不需要我的讲解的话,那您也可以直接看本文。
正文
垃圾收集器,非常的复杂,网络上的博客对这些东西的讲述,也是千奇百怪,如果对这些东西不熟悉的话,我们可能会对gc产生一些疑问,比如CMS GC和Minor GC,这两者有什么不同,这也是笔者我的疑惑。
经过笔者的研究,这些问题的出现,来源于我们的国内的翻译对一些技术书籍术语的翻译不准确,导致了我们对这些东西的理解出现了一些偏差,实际上来讲,所谓的CMS GC和Minor GC,完全是两种东西,在意义上来讲,CMS是确确实实应用在虚拟机上的一种收集器,而Minor GC,却不是垃圾收集器,只是一直垃圾收集行为而已。
由于分代收集思想的出现,对于新生代和老年代,会按照不同的垃圾收集算法,来进行不同的垃圾收集行为,而这些行为,我们也为会称之为gc。
垃圾收集行为
垃圾收集行为gc主要有以下三种:
- Minor GC(小型gc,又叫Yong gc)
- Major GC和Full GC
- Mixed GC
要注意的是,我现在的所说,基本逻辑是在HotSpot虚拟机下的逻辑,但是在java发展过程中,虚拟机的类型已经是多种多样了,不同的虚拟机,甚至相同的虚拟机但是不同的版本,垃圾收集行为的取名都不一样了,这也是导致现在各种名词的解读已经完全混乱了。
因此,我所讲的是,是大体上的名词解释,不同的虚拟机的不同的垃圾收集器,在新生代和老年代上的垃圾收集行为上,给这些垃圾收集行为所取的名词都不一样,也因此,在后续关于垃圾收集行为的解读中,我会稍微讲述一下其他的垃圾收集行为的名词,但是这些名词在意思上是基本上一致的。
1.Minor GC(新生代垃圾收集行为)
在分代收集思想基础上,java堆的内存被划分了多个区域。不同的区域都会有垃圾回收的情况的出现。而我们的Minor GC,就是指代了在年轻代内存回收垃圾的行为。
要知道的是,在HotSpot中,也会将在新生代的垃圾收集行为称之为Yong GC,不同的虚拟机不同的垃圾收集器,都会有着不同名字命名这个行为,但是在本文中,我们将这种在新生代的垃圾收集行为称之为Minor GC。
我们都知道,新生代分为两个区域,Eden区和Survivor区域,那么,我们的Minor GC会在什么被触发呢?
当Eden区的内存满了之后,才会触发GC!
可能有朋友好奇了,因为Survivor区域是划分了两个区域,To Survivor区和From Survivor区,假如From Survivor区域的内存满了之后,会不会触发Minor GC呢?
要在这里说明的是,只有当Eden区域满才会触发Minor GC,也就是说,From Survivor区满了,也不会触发Minor GC。
要理解的是,为什么会这样的呢?这样做,不会出现内存溢出的情况吗?其实仔细了解一下整个Minor的收集行为,就能知道为什么JVM会这么处理了。
基本上所有的对象都是出生在Eden区域,而From Survivor区域的所有的对象,都是从Eden区域复制过来的。
也就是说,当Eden区域满了以后,会触发Minor GC,将Eden区域还存活的对象和From Survivor区域还存活的对象都复制到To Survivor区域,然后将Eden区域和From Survivor区域清除干净。
那么,当我们复制的时候,发现To Survivor区域,不够保存这么多的对象怎么办?
JVM在这里有很多的优化方案,如下:
- 首先,会再次MinorGC意图回收From Surviovr区域无引用的数据,把有引用的数据移动到To Survivor。如果To Survivor够用,此时会清空From Surviovr;如果To Survivor满了,会回滚刚存入To Survivor的数据,通过空间分配担保机制直接把本次GC的数据存入老年代,From Surviovr保持刚刚MinorGC时的状态。
- 如果设置了JVM的自适应开关,在Minor GC结束后,会调整新生代的大小。
- JVM设置了一些优化,如果Eden区域满了,也不一定会触发Minor GC,而是会将对象直接new到老年代。
从上面的表述我们可以知道,我们的Minor GC,使用的垃圾回收算法,是 标记-复制算法,这在我们上一篇文章《从头开始学习JVM(九):垃圾收集(上)》中,我们们已经对这个算法进行解析了,这里就不重复讲述了。
2.Major GC和Full GC
Major GC,指代的是在老年代的垃圾收集行为。
但是不同的垃圾收集器,在老年代的收集行为有着不同的名字,但是只有CMS垃圾收集器,会在老年代有一个专门只收集老年代垃圾的gc,一般是叫做Major GC,但是如果是其他的垃圾收集器,对老年代的垃圾收集,都是使用Full GC(G1垃圾收集器除外),Major GC只收集老年代,但是Full GC却是收集整个java堆。(这种情况的出现,是因为业界的虚拟机以及垃圾收集器太多了,很多的名词标准没有统一起来,所以导致了这种情况的出现。)
而且在HotSpot中,也会将这种垃圾收集行为称之为Old GC,也因此,实际上只有CMS垃圾收集器中将这种垃圾守行为取名为Major GC,但是为了更好的理解这种垃圾行为,在本文中,我们将在老年代的垃圾收集行为,称之为Major GC。(但是要注意的是,虽然都是对老年代的垃圾回收行为,但是不同的GC,使用的垃圾收集算法都不一样了,比如Major GC使用的是标记-清除算法,而Full GC,使用的则是标记-整理算法。)
其实这种垃圾收集行为,我在前文《从头开始学习JVM(八):运行时数据区(下)》讲述运行时数据区java堆的时候,也讲过这个,现在我再讲述一次。
Major GC的触发,是和Minor GC有着很大的关联性。
在发生Minor GC之前,虚拟机会检查老年代最大可用的连续空间是否大于新生代所有对象的总空间,如果大于,则此次Minor GC是安全的。如果小于,则虚拟机会查看HandlePromotionFailure设置值是否允许担保失败。
如果HandlePromotionFailure=true,那么会继续检查老年代最大可用连续空间是否大于历次晋升到老年代的对象的平均大小,如果大于,则尝试进行一次Minor GC,但这次Minor GC依然是有风险的;如果小于或者HandlePromotionFailure=false,则改为进行一次Major GC。
这也就是我们常常所说的,JVM的空间分配担保机制。
但是麻烦的是,我上面讲述的如果是CMS垃圾收集器那就是这样的,如果是其他的垃圾收集器,那么使用的就是Full GC来收集整个堆,自然收集的区域也包括了老年代了。在这其中,有一个例外,那就是G1垃圾收集器,它的老年代是使用的Mixed GC来收集的。
而Full GC定义是相对明确的,就是针对整个新生代、老年代、元空间(metaspace,java8以上版本取代永久代)的全局范围的GC。
3.Mixed GC
Mixed GC是收集整个新生代以及部分老年代的垃圾收集行为。只有G1有这个模式。
因为G1垃圾收集器,是将我们的java堆分拆成了许多许多的小块的内存,这些块装内存称之为region,也因此,虽然G1的垃圾收集依然遵循了分代收集理论,但是对垃圾的收集已经不局限于某个新生代或者老年代,而是扩展到了整个java堆。
G1不再坚持固定大小以及固定数量的分代区域划分,它不仅仅是使用了分代收集思想,它也使用了分区收集思想。它而是把连续的java堆划分为多个大小相等的独立区域,每一个区域region都可以根据需要,扮演新生代的Eden空间,Survivor空间或者老年代空间。
Mixed GC只会选取所有年轻代的region和一些收集收益高的老年代region进行收集,不是将所有的老年代region都收集。在收集属于老年代的region的时候,会考虑哪块内存存放的垃圾数量最多,回收的收益最大。
G1会找出需要回收的对象,这个过程是非常漫长的。当G1确定一个region里面有需要回收的对象的时候,就会将这个region中还存活的对象复制到其他的空白region中,然后将这个region清除干净。
也就是说,Mixed GC使用的垃圾回收算法,仅从局部的region的角度来看,是标记-复制算法。但是从整个java堆的范畴来看,用的垃圾回收算法却是标记-整理算法。但是无论如何,都不会产生内存碎片,这也保证了垃圾收集完成后能提供完整的可用内存,这种特性有利于程序的长期运行。
总结
对于我们的垃圾收集行为,我们会将这些行为定义为一种gc,但是这不是所谓垃圾收集器,这只是一种垃圾收集行为,而具体的垃圾收集器,我会在下一篇文章中讲述。
对我们在学习中所谓的Minor GC,Major GC,Full GC以及Mixed GC,我相信看完了本文的朋友,已经有了很好的分类和理解。
但是要注意的是,java虚拟机的世界,如此广阔,还有很多的虚拟机,使用着是其他的标准在收集垃圾,我在本文中所讲述的,都是我们的官方的虚拟机的垃圾收集行为,不能涵盖整个Java虚拟机世界。