前言
首先我们需要先搞定几个假说
- 弱分代假说:绝大多数对象都是朝生夕灭的
- 强分代假说:熬过越多次垃圾回收的对象就越难以消亡
- 跨代引用假说:跨代引用对于同代引用来说只占极小数
那么这三个假说有什么用处呢
试想一下,如果有大量的对象都是要被回收的,我们再回收这些垃圾之前是要对所有的内容进行标记,此时,我们是标记那些需要回收的对象,还是标记那些不需回收的对象,这俩者哪个效率较高呢,很明显在以上前提下,标记需要不许回收的更好,这就是弱分代假说的应用
接下来我们来看一下强分代假说,这里我们把对象熬过的垃圾回收的次数成为寿命,那么强分代假说的含义就是对象的寿命越大,越难回收(其实就是可能一直在被使用,不能回收,否则会影响正常的系统运行),此时我们每次扫描还要把所有的对象,包括寿命大的,这就有些浪费时间了,我们大可把寿命超过某个阈值的对象放在一个区域,寿命短的放在一个区域,首先对寿命短的区域进行清理,若清理结束后仍空间不够,这时在对寿命长的区域进行清理,这就大大的节省了时间。寿命长的区域称为老年代,寿命短的称为新生代
跨代引用假说可以这样理解,存在引用关系的对象大都在一种区域(老年代或新生代)中,而那些跨代引用(跨区域引用)的则是极少数。
为什么要区别这个呢
现在我们再做一下假设,假如我们此时需要做新生代回收,但是有可能引用新生代的对象在老年代中,我们又需要再去扫描老年代,那我们设置新生代和老年代的意义在哪,此时只需在新生代上设置一个全局变量Remember set,并且把老年代分为很多小块,只需在Remember set记录哪些老年代块中引用新生代即可,每次新生代回收时只需扫描这些记录的老年代块就可以了。
接下来是关于虚拟机设置的参数
含义 | 参数 |
---|---|
堆初始大小 | -Xms |
堆最大大小 | -Xmx或-XX:MaxHeapSize =size |
新生代大小 | -Xmn或(-XX:NewSize-size -XX:MaxNewSize-size ) |
幸存区比例(动态) | -XX:InitialSurvivorRatio-ratio和-XX: UseAdaptiveSizePolicy |
幸存区比例(ratio是指伊甸园所在比例) | -XX:SurvivorRatio= ratio |
晋升阈值 | -XX:MaxTenuringThreshold=threshold |
晋升详情 | -XX: PrintTenuringDistribution |
GC详情 | -XX: PrintGCDetails -verbose:ge |
FullGC前MinorGC | -XX: ScavengeBeforeFullGC |
幸存区比例不会变化的垃圾回收器 | -XX: UseSerialGC |
串行垃圾回收器(新生代是复制算法,老年是标记整理算法) | -XX: UseSerialGC= Serial Serialold |
上了解了上述内容之后,我们来了解一下垃圾回收的几种算法
垃圾回收算法
标记-清除
思想
这个咱们可以按照名字来理解了,整个算法分为两个阶段,分别是标记和清除。
- 标记阶段,咱么在上面已经介绍过了,就是标记需要回收的资源或则不需要回收的资源。
- 清除阶段,就是回收刚刚标记的对象或未被标记的对象,具体怎么回收就取决于你选择的算法了。
优缺点
该算法是很多算法的基础,但也有几个缺点
- 算法不稳定,若是采用标记需回收的对象,这时恰恰又有很多对象需要回收,这就造成了回收时间过长
- 其次,该算法只做清理,而不做整理空间,此时就导致了可能会产生空间碎片,使得虽然剩余空间总大小足够分配给一个对象,但是因为没有连续空间,就导致了无法分配空间问题。
标记-复制
思想
这种算法也是再上述弱分代假说上设置的,也就是说,我们认为大多数对象都是朝生夕灭的,事实确实如此。
首先我们需要将内存分为两块区域,分别称为from和to,当from中满了时,我们就需要对from进行垃圾回收。
先对from中的对象进行标记,而后将那些不需回收的对象复制到to中,然后对from中对象直接清空即可,随后交换from和to的,也就是说现在from就是to,而to就是from。
优缺点
- 该算法不会产生碎片空间,因为在复制时,我们可以通过调节指针的方式,使得复制过去的对象都可以紧凑的存放在一起。
- 虽然实现简单,运行起来也很高效,但该算法有一个明显的缺点,这种将空间划分为俩部分的方式,已经浪费了一半空间
目前这种算法被应用到很多虚拟机,如下图,就是一种应用该算法的空间的划分
可以看出,这里内存被分成了老年代与新生代,为什么要设置老年代呢,这是因为我们之前介绍的强分代假说。
这里看下新生代,我们可以发现内存分为了伊甸园与幸存区俩部分,幸存区又被分为了两部分,可以认为我们上诉提到的from和to。这里一旦伊甸园存满了,就需要进行清理,我们称之为Minor GC,清理过程就是上诉的标记-复制过程,而后我们将伊甸园中幸存的对象移入到幸存区中,然后直接清理伊甸园即可。
在之后伊甸园又满了,我们这时不仅仅要对伊甸园对象进行处理,也要对幸存区中存有数据的那一块进行处理,将二者之中幸存的对象移入到幸存区的另一块中,然后交换二者的角色(即交换from与to的角色)。
如果说清理新生代对象之后仍无法满足对象存储的需求,就需要对老年代中的对象进行回收,此时就发生了Full GC,这里是会对新生代和老年代一起回收,如果还是不可以,这就发生了内存空间溢出的问题。上述这种思想也就是分代回收。即先回收新生代,若是回收结束之后仍内存不足,在进行回收全部的空间。
标记-整理
思想
- 判断哪些对象未被不可回收的对象直接或间接引用,对其进行标记
- 清楚时,将可用的对象向前移动,从而使得内存空间更见紧凑,从而实现空间更加连续
优缺点
- 没有内存碎片
- 耗费时间较多,例如如果有引用对象引用就是将移动的对象,需要修改大量内容,造成浪费时间,这也是标记整理与标记清除区别
- 因为要移动对象,这个时间段这些对象的地址可能会发生变化,原本需要使用他们的线程就需要暂停,待将这些线程的使用的引用更新之后才可以继续运行,也就是在垃圾回收这段时间,所有的用户线程都要暂停,也被称为stop the world。
总结
在看完上述的分代回收之后,大家可能有疑惑,如果幸存区不够存放幸存对象怎么办,这时候就会出现新生代回收的对象直接晋升至老年代,而不需要进行我们上述提到的达到一定寿命之后才可以晋升。
看起来是没有任何问题的,但可以设想一下,通过上诉这种机制我们可以了解到,老年代的回收频率一定是小于新生代的回收频率,幸存对象直接上升至老年区,导致该对象可能已经该回收了,却一直因为未进行老年代回收而一直迟迟占着空间。
同时我们也可以注意到由于整理空间时会引起stop the world,但不整理又会存在空间碎片,现在有一种折中的方法就是空间足够时使用标记清理,当空间碎片太多时,就是用标记整理进行一下整理即可。
该博客仅仅是我自己在学习之后的总结,有什么问题,还请大佬们指教。还有分区的方式进行整理等以后博客再整理,就到这了