• 微信公众号:美女很有趣。 工作之余,放松一下,关注即送10G+美女照片!

垃圾回收

开发技术 开发技术 4小时前 2次浏览

Java垃圾回收器的发展历史

第一阶段:串行垃圾回收器:jdk1.3.1之前Java虚拟机仅仅只支持Serial收集器
第二阶段:并行垃圾回收器:随着多核的出现,Java引入了并行垃圾回收器,充分利用多核性能提升垃圾回收效率
第三阶段:并发标记清理回收器CMS:垃圾回收器可以和应用程序同时运行,降低暂停用户线程执行的时间
第四阶段:G1(并发)回收器:初衷是在清理非常大的堆空间的时候能满足特定的暂停应用程序的时间,与CMS相比会有更少的内存碎片

垃圾回收算法

垃圾回收器(GC:Garbage Collection)的3个任务:分配内存、确保被引用对象不被错误回收、回收不再被引用的对象的内存空间

  • 引用计数算法Reference Counting Collector:简单效率低,通过在堆中对每个对象都有一个计数器,当对象被引用时+1,当引用被置空或者离开作用域时-1;这种方式无法解决互相引用问题,JVM没有采用
  • 追踪回收算法Tracing Collector:利用JVM维护的对象引用图,从根节点出发开始遍历,同时标记遍历过的对象,遍历结束后没有被标记的说明可以被回收
  • 压缩回收算法Compacting Collector:把堆中活动的对象转移到一端,另一端会空出很大的空闲区域,对堆中的碎片进行了处理,但带来了性能的损失
  • 拷贝回收算法Coping Collector:把堆分为2个大小相同的区域,任何适合都只使用其中一个,直到这个区域消耗完为止,此时垃圾回收器中断程序执行,通过遍历的方式将对象拷贝到另一区域,拷贝时也是相邻布置的,拷贝结束后程序继续运行,直到这块区域被使用完再采用其他方式进行垃圾回收;优点是消除了内存碎片,缺点是需要两倍堆空间大小的内存,而且内存调整中断程序降低程序执行效率
  • 按代回收算法Generational Collector:缺点是执行时所有处于活动的对象都要拷贝,效率低;可根据“程序中大部分对象的生命周期都很短”的特点进行优化

串行垃圾回收器

单线程垃圾回收器,运行时会暂停所有应用线程,Stop The World(STW),不适用与服务器环境中,适用在客户端程序中;
可以使用JVM参数-XX:+UseSerialGC来指定使用串行垃圾回收器

年轻代使用的是拷贝算法老年代或永久代使用的是标记-清扫-压缩算法
标记-清扫-压缩:标记阶段识别哪些对象存活;扫描阶段会扫描整个代,识别出哪些是垃圾;压缩阶段执行平移压缩,存活的移动到最前端,尾部流出连续空闲空间。
之后的分配就可以在老年代或永久代使用空指针算法
空指针(bump-the-pointer)算法:JVM维护2个指针(allocatedTail指向已分配对象的尾部,geneTail指向代尾)当需要分配内存是由两个指针可判断剩余空闲空间是否够用,如果够则更新allocatedTail指针来把内存分配给请求的对象

并行垃圾回收器

并行垃圾回收运行时仍然会暂停应用程序,但使用了多线程能够缩短垃圾回收的时间
设置使用的垃圾回收算法:-XX:+UseParallelGC 在单核CPU上设置无效
在一台有N个CPU的主机上并行垃圾回收器会使用N个垃圾回收线程进行垃圾回收,也可通过参数指定:-XX:ParalleGCThreads=<垃圾回收器线程个数>

并发标记清理回收器CMS

最大并发量的标记清除垃圾回收器:Concurrent Mark Sweep;年轻代使用拷贝算法,老年代使用最大并发量的标记清除算法(避免清理老年代暂停用户程序太长时间)
主要通过以下两种方法来实现避免用户程序有太长时间的停顿:

  • 使用空闲链表来管理和回收的空间,而非压缩老年代内存;
  • 将多数的标记清理工作和应用程序并发执行。

这种算法花费了大量的时间在标记-清理阶段,这个阶段的认为可与用户线程并发执行,不过仍与用户线程竞争CPU资源,默认下该GC算法使用的线程数等于机器物理内核数量的1/4
可使用参数:-XX:UseConcMarkSweepGC显示指定使用CMS算法。
CMS的执行步骤

  1. 初始标记:标记在老年代由Root集直接可达或被年轻代引用的对象,这一步会暂停用户线程的执行;
  2. 并发标记遍历老年代,由上一步标记的节点开始标记所有被引用的对象,并发执行所以并不会标记所有的被引用对象;
  3. 并发预清理:与应用程序并发执行,由于上一步执行中有些引用可能发生了变化而导致标记不准确,这一步将这些引用发生变化的节点标记为dirty,对从dirty节点出发的可达节点进行标记;
  4. 并行的可被终止的预清理(CMS-concurrent-abortable-preclean):与应用程序并发执行,也是执行一些预清理;
  5. 重标记:暂停应用程序的执行,最终确定老年代中所有存活的对象;
  6. 并行清理:与应用程序并发执行,删除不再使用的对象从而回收被他们占用的内存空间;
  7. 并发重置:重置数据结构为下一次运行做准备。

这种回收算法与应用程序的并发执行大大减少了暂停应用程序的时间,适用于在多核机器上使用,与并行垃圾回收器相比CMS通常在CPU密集的应用程序中有更低的吞吐量。

G1

G1(Garbage-First)解决了垃圾回收器暂停用户线程时间的不确定性,它是面向服务器的垃圾回收器,主要针对配备多核CPU及大容量内存的机器,在以极高的概率满足GC暂停用户线程的同时还具有很高的吞吐量,主要有以下几个特点:

  1. 可预测性:可以预测暂停用户线程的时间,提供了设置暂停时间的选项;
  2. 压缩特性:在满足暂停时间的要求上尽可能多地消除碎片;
  3. 并发性:与CMS一样,GC操作可与应用线程一起并发执行;
  4. 节约:不需要请求更大的Java堆。

与之前的垃圾回收器相比,G1可被看作是一种增量式的并行压缩GC算法,它提供了可以预测暂停时间的功能。通过并行、并发和多阶段标记循环,G1可被应用在堆空间更大的场景,同时还提供合理的在最坏情况下的暂停时间。它的基本思想是在GC工作前设置堆范围(-Xms用来设置堆的最小值,-Xmx设置堆的最大值)和实际暂停目标时间(使用-XX:MaxGCPauseMillis来设置)

G1将年轻代、老年代的物理空间划分取消了,它将堆空间划分为若干个区域Region,一段连续的堆空间被划分为固定大小的区域,然后用一个空闲链表来维护,每个区域要么对应老年代,要么对应年轻代,根据实际堆空间的大小这些区域可被划分为1MB~32MB,从而可以保持总的区域个数在2048左右。G1最主要的原则是:在标记阶段完成后,G1就可以知道哪些heap区的empty空间最大,它会在满足暂停时间的基础上优先回收空闲区域最大的区域,因此它也被称为garbage-first(垃圾优先)的垃圾回收器

虽然引入了区域的概念,但G1本质上仍然属于分代回收器,年轻代的垃圾回收依然会暂停应用线程的执行,它会把存活对象拷贝到Survivor或者老年代;在老年代中G1通过把对象从一个区域复制到另外一个区域来实现垃圾清理的工作,好处是压缩了堆内存,避免了CMS的那种内存碎片的问题,而在G1中这些区域可以是不连续的;

除了年轻代的Eden区、Survivor区和老年代的Old,G1还引入了一种特殊的区域:Humongous区域;这些区域被设计为存放占用超过分区容量50%以上的那些对象,它们被保存在一个连续的区域集合里;对于很大的对象默认会被分配到老年代,如果该对象的生命周期较短则会对垃圾回收器的性能造成很大的影响,Humongous区域就是为了解决这个问题,如果一个H区无法容纳那么G1会寻找连续的H区来存储;

在老年代的回收算法主要分为以下几个步骤:

  1. 初始标记:G1对跟进行标记,主要标记那些可能有引用对象的O区,会暂停应用程序执行
  2. 跟区域扫描:G1在上一步的基础上扫描对老年代的引用,并标记被引用的对象,不会暂停应用程序的执行,但只有完成该阶段后才能开始下一次的STW年轻代垃圾回收;
  3. 并发标记:G1在整个堆区域查找存活对象,不会暂停用户程序,而且该阶段可以被STW年轻代垃圾回收中断;
  4. 重新标记阶段:G1在这个阶段会清空SATB(Snapshot-At-The-Beginning)缓冲区,跟踪未被访问的存活对象,并执行引用处理。如果发现某个region上的所有对象都不再被引用了那么它们将会被直接清除,该阶段会暂停应用程序的执行。
  5. 清理阶段:这个阶段会执行统计和RSet(Remembered Sets:用来跟踪指向某个heap区内的对象引用,堆内存的每个区都会有一个RSet)的净化操作,在统计过程中G1会识别出那些完全空闲的区域和可以进行混合垃圾回收的区域,此阶段只有一个操作为并发操作:将空白区域重置,并且返回空闲链表。因此该阶段也会触发STW,暂停应用程序的执行

在Java9之前,Java默认使用的垃圾回收器是ParallelGC,从Java9开始G1作为了默认的垃圾回收器


程序员灯塔
转载请注明原文链接:垃圾回收
喜欢 (0)