栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

HotSpot的算法实现和垃圾收集器

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

HotSpot的算法实现和垃圾收集器

HotSpot的算法实现和垃圾收集器

仅作为笔记

文章目录
  • HotSpot的算法实现和垃圾收集器
  • 前言
  • 一、HotSpot的算法实现
    • 1.1、枚举根节点
    • 1.2、安全点
    • 1.3、安全区域
  • 二、垃圾收集器
    • 2.1、Serial收集器
    • 2.2、ParNew收集器
    • 2.3、Parllel Scavenge收集器
    • 2.4、Serial Old收集器
    • 2.5、Parallel Old收集器
    • 2.6、CMS收集器
    • 2.7、G1收集器


前言

随着线程消失而消失的有:程序计数器、虚拟机栈、本地方法栈,这三个地区不需要考虑内存回收的问题,而java堆和方法区不一样,多个实现类需要的内存不一样,方法的多个分支需要的内存也不一样,这些都只能在程序处于运行期间才能确定,意味着这些内存都是动态分配的,所以垃圾收集器关注的只是这部分。
仅作为笔记


一、HotSpot的算法实现

基于对象存活判定和垃圾收集算法,研究Hot Spot如何实现这些算法。

1.1、枚举根节点
  • 可达性分析之前提到过,实际上这也是现代主流的判断对象死亡的方法。可达性分析需要查看的是当前对象的状态,意味着这需要记录的是这一个时刻对象的引用情况,这就不得不将时间封装起来从而产生停顿。
  • 准确式GC:这是目前主流java虚拟机采用的方法,属于所有线程停顿后的操作。准确式GC指的是:并不需要一个不漏的遍历所有的上下文和全局的引用位置,应该是有具体的地方存放着,直接去看就行了。
  • Hot Spot的准确式GC:使用OopMap数据结构与来存储引用信息,GC在扫描的时候可以直接看这里。
  • OopMap:在源代码里面每个变量都是有类型的,但是编译之后的代码就只有变量在栈上的位置了。oopMap就是一个附加的信息,告诉你栈上哪个位置本来是个什么东西。 这个信息是在JIT编译时跟机器码一起产生的。因为只有编译器知道源代码跟产生的代码的对应关系。 每个方法可能会有好几个oopMap,就是根据safepoint把一个方法的代码分成几段,每一段代码一个oopMap,作用域自然也仅限于这一段代码。 循环中引用多个对象,肯定会有多个变量,编译后占据栈上的多个位置。那这段代码的oopMap就会包含多条记录。
1.2、安全点
  • OopMap可以很好的帮助完成GC Roots枚举,但是前面我们提到了这个过程是需要停顿的,这就会产生两个问题,1)产生过多的OopMap会使用大量的内存空间,2)OopMap太多,也会花费大量的时间去检查。为了解决这个问题那就需要设置一个安全的地方进行OopMap存储,叫安全点。
  • 在哪里设置安全点:以是否具有让程序长时间执行的特征为标准,最明显的特征是指令序列复用,如:方法调用,循环跳转,异常跳转。由这些功能的才会产生安全点Safepoint。
  • 如何停下:1)抢先式中断:当需要执行GC时,立即让所有线程中断,如果发现有线程中断的地方不是安全点,那就恢复线程让其跑到安全点。注意:这个方案几乎没人用。2)主动式中断:当GC需要中断线程时,不直接对线程操作,仅仅简单地设置一个标志,各个线程执行时主动去轮询这个标志,发现中断标志为真时就自己中断挂起。轮询标志的地方和安全点是重合的,另外再加上创建对象需要分配内存的地方。
1.3、安全区域
  • 为了解决有的线程处于sleep或者Blocked状态无法执行到safepoint而提出的。
  • 安全区域是指在一段代码片段之中,引用关系不会发生变化。在这个区域中的任意地方开始GC都是安全的。我们也可以把Safe Region看做是被扩展了的Safepoint。
  • 在线程执行到Safe Region中的代码时,首先标识自己已经进入了Safe Region,那样,当在这段时间里JVM要发起GC时,就不用管标识自己为Safe Region状态的线程了。在线程要离开Safe Region时,它要检查系统是否已经完成了根节点枚举(或者是整个GC过程),如果完成了,那线程就继续执行,否则它就必须等待直到收到可以安全离开Safe Region的信号为止。
二、垃圾收集器
  • 这里讨论的收集器是JDK1.7 Update 14之后的HotSpot虚拟机。
  • 一个虚拟机肯定不只是一个收集器,要完成那么多的垃圾回收,需要各种各样的收集器。下面是虚拟机中包含的收集器,连线的表示可以搭配使用的。
2.1、Serial收集器
  • 第一代垃圾收集器,收集时会暂停掉所有线程。
  • 特点:与其他收集器的单线程相比,他简单而高校。Serial收集器没有线程交互,专心于垃圾收集。
  • 适用环境:Serial收集器对于运行在Client模式下的虚拟机来说是一个很好的选择。用户的桌面应用场景中,分配给虚拟机管理的内存一般来说不会很大,收集几十兆甚至一两百兆的新生代(仅仅是新生代使用的内存,桌面应用基本上不会再大了),停顿时间完全可以控制在几十毫秒最多一百多毫秒以内,只要不是频繁发生,这点停顿是可以接受的
    过程如下所示:
2.2、ParNew收集器
  • 只是Serial收集器的多线程版本,其余的和Serial没什么两样。
  • 特点:运行在新生代,目前唯一可以和老年代收集器——CMS收集器配合的收集器。
  • 适用环境:Server模式下选择这个收集器是首选,原因是他可以配合CMS。
    示意图如下:
2.3、Parllel Scavenge收集器
  • 吞吐量:吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)。高吞吐量则可以高效率地利用CPU时间,尽快完成程序的运算任务。
  • 特点:并行的多线程的新生代收集器,侧重点在于吞吐量的控制。有两个参数用于精确控制吞吐量,分别是:圾收集停顿时间的**-XX:MaxGCPauseMillis参数以及直接设置吞吐量大小的-XX:GCTimeRatio参数。如果调小-XX:MaxGCPauseMillis参数,虽然能缩短GC停顿时间,但是这会降低新生代的大小以及降低吞吐量**。
  • 适用场景:主要适合在后台运算而不需要太多交互的任务。
  • GCTimeRatio参数的值:是一个大于0且小于100的整数,是吞吐量的倒数。如果把此参数设置为19,那允许的最大GC时间就占总时间的5%(即1/(1+19)),默认值为99,就是允许最大1%(即1/(1+99))的垃圾收集时间。
2.4、Serial Old收集器
  • Serial是新生代的收集器,而Serial old是老年代的,同样是单线程收集器,使用的是“标记-整理算法”。
  • 应用场景:主要是应用在Client模式下,如果使用在Server模式下,主要作用:1)在JDK1.5之前是配合Parallel Scavenge收集器使用。2)作为CMS收集器的后被预案,在并发收集发生Concurrent Mode Failure时使用。
2.5、Parallel Old收集器
  • 是Parallel Scavenge收集器的老年代版本,使用多线程和“标记——整理”算法。诞生于JDK1.6。
  • 适用场景:吞吐量优先,在注重吞吐量和CPU资源敏感的场合,优先考虑Parallel Scavenge加Parallel Old组合。
2.6、CMS收集器

先有如下概念:

  • GC RootsTracing过程:判断对象是否可回收或者说死亡了的算法,基于可达性分析算法。
  • Concurrent Mode Failure:CMS收集器特有的错误,老年代正在清理,从年轻代晋升了新的对象,或者直接分配大对象年轻代放不下导致直接在老年代生成,这时候老年代也放不下,则会抛出“concurrent mode failure”
  • 关于Full GC:这是一个挺乱的概念,稍后再做整理。

再接着详细看CMS:

  • 前面提到过收集器侧重问题,比如吞吐量优先收集器,这里提到的就是以最短回收停顿算法为目标的响应优先收集器。
  • 主要应用场景:互联网站或者B/S系统的服务端上,这类应用都是希望停顿时间短,有更好的用户使用体验。
  • CMS基于“标记-清除”算法,分为以下四个步骤:1)初试标记,仅仅标记 GC Roots 能直接关联到的对象。2)并发标记,对初始标记标记过的对象,进行 trace(进行追踪,得到所有关联的对象,进行标记)。3)重新标记,为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录。4)并发清除。其中,初试标记和重新标记是需要“Stop The World”的。虽然有两个步骤需要停止世界,但是这两个步骤是耗时最短的,耗时相对长的是并发标记和并发清除两个步骤,但是这两个步骤是收集器线程可与用户线程一起工作的,意味着总体来说CMS收集器的内存回收过程是并发的。
  • 缺点:1)对CPU资源非常敏感,虽然在连个并发阶段用户线程还是可以和收集线程并发执行的,但是毕竟有回收线程在占用CPU,这依然会降低用户程序的执行速度。诞生过增量式并发收集器,思想是看待单核CPU那样,让收集器和用户线程交互使用CPU,减少线程独占CPU的时间,但是这个在时间过程中还是没能解决这个问题,所以被废弃了。2)无法处理浮动垃圾,浮动垃圾就是在并发标记阶段产生的垃圾,这部分是不会被发现的。3)内存碎片问题,由于基于的是“标记-清除”算法,容易在老年代产生大量的内存碎片,会出现老年代还有大量的空间但是没有合适的来为对象分配空间时就会提前出发Full GC。
  • 为了解决因为内存碎片导致的提前出现Full GC问题,提供了一个开关,用于CMS收集器顶不住了要开始Full GC时进行内存碎片整理,这个过程是没办法并发的,意味着执行时间变长了。虚拟机设计者还提供了另外一个参数-XX:CMSFullGCsBeforeCompaction,这个参数是用于设置执行多少次不压缩的Full GC后,跟着来一次带压缩的(默认值为0,表示每次进入Full GC时都进行碎片整理)。
2.7、G1收集器
  • 是唯一一个同时用于新生代和老年代的垃圾收集器,但是这个收集器和之前的不同,不把内存区域在物理上就划分为新生代和老年代,而是各种Region。
  • 采用标记-整理算法,避免碎片。该收集器将堆内存分为不同大小相等的region,并维护了一个优先列表,每次根据允许的收集时间,优先选择回收价值最大(可得到的内存以及所需时间的估计值)的Region,把内存化整为零。
  • 但是由于关系引用的存在,始终存在如何避免全局扫描问题,这里采用每一个Region提供一个remembered Set进行记录引用关系,避免可达性分析阶段的全区域垃圾扫描。
  • G1大致分为下面四个步骤:初试标记,并发标记,最终标记,筛选回收。初试标记和CMS一样的,最终标记阶段,将并发标记阶段产生的引用变化记录在Remembered Set Logs里面,这样可以解决CMS的浮动垃圾问题,最终把Remembered Set Logs合并到Remembered Set中,这一阶段需要停顿线程,但是可并行执行。
  • 筛选回收,对每一个region的价值和成本进行筛选,根据用户期望GC停顿时间,得到最好的回收方案并回收。
  • 特点:并发性强、分代收集、标记整理进行空间整合,可以预测停顿时间。特别是可预测停顿时间!
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/276819.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号