文章目录
JVM会自己选择合适的垃圾收集策略,而用户自己也可以自己来设置自己所需要的垃圾收集策略。不过就个人而言,强烈建议采用默认的垃圾收集处理机制。
垃圾的收集一定要分两个空间考虑:年轻代、老年代。因为老年代的内存空间要大于年轻代的内存空间,所以老年代每次执行GC都会消耗更多的时间。
垃圾收集策略分类
因为同一种垃圾收集策略,有可能会根据触发内存代的不同又不同的效果,所以先看各个内存策略的操作特点:
-
年轻代-串行GC
-
年轻代-并行回收GC
同一个GC的处理操作,需要有多个线程共同完成,一个线程负责内存的扫描(扫描出不用的内存对象),而另一个线程负责对象的复制操作。
大家看上图的左半边,是一个串行回收的过程。在回收的过程中在橙色部分做了一次停止,什么叫停止呢?比如我要进行空间的打扫,那我需要什么?大家先出去,我再来看那些有用那些没有用,不能所大家在这里边操作着,我还在这里做着打扫,要是你们吃着瓜子,吃一个扔一个,我就得你扔一个我扫一个,是不是性能太差了,是不是需要做一个中断,先出去再扫,扫完了垃圾你们再回来。
继续看图的左边,在橙色部分有个叫“Stop-The-World pause”(STW可以简单理解为用于在垃圾回收时候进行的一个短暂的线程挂起,简称叫线程暂停)。
再看右边的图,是并行回收过程。可以看到在并行的时候,暂停的时间会短些,这是由于有多个线程在共同做这件事。不过依然会有暂停。
-
年轻代-并行GC
注:
CMS GC 是在整个设计过程中最为重要的一个环节,不过它本身也会有问题,因为老年代的空间一般较大,所以在扫描和标记存活对象上需要花费较长时间。
并行回收只是处理年轻代的,而并行GC需要与老年代的 GC结合 -
老年代-串行GC
注:
所有的串行GC处理都只是单线程处理,那么在进行处理的时候都必须暂停操作。 -
老年代-并行GC
操作步骤说明:
将老年代内存空间按照线程个数分为若干个子区域
如下图教室分工区域
举个例子:今天有小郭和小岳要打扫教室,怎么打扫?是不是一人一半,他们两个谁也不吃亏的性格让一个人全做可能吗?不可能的,小郭在左边,小岳在右边。要是再来两个人一起打扫,就变成了4个人平分了。话说回来,上图是分配了4个线程4份区域。多个线程并行对多个各自负责子区域对象进行标记
标记时候,操作已经暂停了,将有用的对象标记出来
多个线程并行清楚所有未标记的对象
没有用的对象,都扫扫扫,扫干净
多个线程并行将多个存活对象整理在一起,并将所有被回收的对象空间整合为一体
从操作步骤可以看出:与串行操作相比,整体的操作只是多了一个多线程的支持,但是这样的暂停时间就会减少。
-
老年代-并行GC
图中操作步骤:- 初始标记(STW Initial Mark):虚拟机暂停正在执行的任务(STW),由根对象扫描出所有的关联对象,并做出标记。此过程只会导致短暂的JVM暂停
- 并发标记(Concurrent Marking):恢复所有暂停的线程对象,并且对之前标记过的对象进行扫描,取得所有跟标记对象有关联的对象;
- 并发预处理(Concurrent Preclearning):查找所有在并发标记阶段新进入老年代的对象(一些对象可能从新生代晋升到老年代,或者有一些对象被分配到老年代),通过重新扫描,减少下一阶段的工作;
- 重新标记(STW Remark):此阶段会暂停虚拟机,对在“并发标记”阶段被改变引用或新创建的对象进行标记;
- 并发清理(Concurrent Sweeping):恢复所有暂停的应用线程,对所有未标记的垃圾对象进行清理,并且会尽量将已回收对象的空间重新拼凑为一个整体。在此阶段收集器线程和应用程序线程并发执行;
- 并发重置(Concurrent Reset):重置CMS收集器的数据结构,等待下一次的垃圾回收
操作步骤:
操作步骤放大图:
图中优缺点:
只有在第一次和重新标记阶段才会暂停整个应用,这样对应用程序所带来的影响非常小。缺点是并发标记和回收线程会与应用线程争抢CPU资源,并容易产生内存碎片。