1.枚举根节点java
检查对象是否被引用须要根据GC Roots节点来查找引用链。可做为GC Roots的节点主要是全局性的引用与执行上下文中,若是要逐个检查引用,必然消耗时间。
另外可达性分析对执行时间的敏感还体如今GC停顿上,由于这项分析工做必须在一个能确保一致性的快照中进行——这里的“一致性”的意思是指整个分析期间整个系统执行系统看起来就行被冻结在某个时间点,不能够出现分析过程当中对象引用关系还在不断变化的状况,该点不知足的话分析结果的准确性就没法获得保证。这点是致使GC进行时必须暂停全部Java执行线程的其中一个重要缘由。安全
目前主流的java虚拟机使用的都是准确式GC,因此当执行系统停顿下来后,并不须要一个不漏地检查完全部执行上下文和全局的引用位置,虚拟机应当是有办法直接获得哪些地方存放着对象引用。在HotSpot的实现中,是使用一组成为OopMap的数据结构来达到这个目的,在类加载完成的时候,HotSpot就把对象内什么偏移量上是什么类型的数据计算出来,在JIT编译过程当中,也会在特定的位置记录下栈里和寄存器里哪些位置是引用。这样GC在扫描时就就能够直接得知这些信息了。数据结构
对OopMap(Ordinary Object Pointer Map)的理解,引自http://dsxwjhf.iteye.com/blog/2201685线程
OopMap 用于枚举 GC Roots ;
RememberedSet 用于可达性分析。
OopMap
OopMap 记录了栈上本地变量到堆上对象的引用关系。其做用是:垃圾收集时,收集线程会对栈上的内存进行扫描,看看哪些位置存储了 Reference 类型。若是发现某个位置确实存的是 Reference 类型,就意味着它所引用的对象这一次不能被回收。但问题是,栈上的本地变量表里面只有一部分数据是 Reference 类型的(它们是咱们所须要的),那些非 Reference 类型的数据对咱们而言毫无用处,但咱们仍是不得不对整个栈所有扫描一遍,这是对时间和资源的一种浪费。
一个很天然的想法是,能不能用空间换时间,在某个时候把栈上表明引用的位置所有记录下来,这样到真正 gc 的时候就能够直接读取,而不用再一点一点的扫描了。事实上,大部分主流的虚拟机也正是这么作的,好比 HotSpot ,它使用一种叫作 OopMap 的数据结构来记录这类信息。
咱们知道,一个线程意味着一个栈,一个栈由多个栈帧组成,一个栈帧对应着一个方法,一个方法里面可能有多个安全点。 gc 发生时,程序首先运行到最近的一个安全点停下来,而后更新本身的 OopMap ,记下栈上哪些位置表明着引用。枚举根节点时,递归遍历每一个栈帧的 OopMap ,经过栈中记录的被引用对象的内存地址,便可找到这些对象( GC Roots )。
经过上面的解释,咱们能够很清楚的看到使用 OopMap 能够避免全栈扫描,加快枚举根节点的速度。但这并非它的所有用意。它的另一个更根本的做用是,能够帮助 HotSpot 实现准确式 GC (我的感受这才是 OopMap 被设计出来的根本缘由,提升 GC Roots Enumeration 速度更像是一个“意外的惊喜”)。关于准确式 GC 的具体内容(如:什么叫准确式 GC ?什么叫保守式 GC ?什么叫半保守式 GC ?准确式 GC 有哪些实现思路?等等),在此不一一说明,你们能够参考 找出栈上的指针/引用 这篇文章。须要说明的是,该文章的做者是 Oracle HotSpot 虚拟机团队的开发人员。
RememberedSet
RememberedSet 用于处理这类问题:好比说,新生代 gc (它发生得很是频繁)。通常来讲, gc 过程是这样的:首先枚举根节点。根节点有可能在新生代中,也有可能在老年代中。这里因为咱们只想收集新生代(换句话说,不想收集老年代),因此没有必要对位于老年代的 GC Roots 作全面的可达性分析。但问题是,确实可能存在位于老年代的某个 GC Root,它引用了新生代的某个对象,这个对象你是不能清除的。那怎么办呢?
仍然是拿空间换时间的办法。事实上,对于位于不一样年代对象之间的引用关系,虚拟机会在程序运行过程当中给记录下来。对应上面所举的例子,“老年代对象引用新生代对象”这种关系,会在引用关系发生时,在新生代边上专门开辟一块空间记录下来,这就是 RememberedSet 。因此“新生代的 GC Roots ” + “ RememberedSet 存储的内容”,才是新生代收集时真正的 GC Roots 。而后就能够以此为据,在新生代上作可达性分析,进行垃圾回收。
咱们知道, G1 收集器使用的是化整为零的思想,把一块大的内存划分红不少个域( Region )。但问题是,不免有一个 Region 中的对象引用另外一个 Region 中对象的状况。为了达到能够以 Region 为单位进行垃圾回收的目的, G1 收集器也使用了 RememberedSet 这种技术,在各个 Region 上记录自家的对象被外面对象引用的状况。设计
2.安全点指针
为了空间成本考虑,HotSpot没有为每条指令都生成OopMap,只是在特定的位置记录了这些信息,这些位置称为安全点(Safepoint)。程序只有在到达安全点是才能停下来开始GC。安全点的设定基本上是以程序“是否具备让程序长时间执行的特征”为标准进行选定的,长时间执行的最明显特征就是指令序列服用,例如 方法调用、循环跳转、异常跳转等。对象
如何在GC发生时让全部的线程都到达安全点上停顿下来:抢先式中断(Preemptive Suspension)和主动式中断(Voluntary Suspension)。blog
抢先式中断:不须要线程的执行代码去主动配合,当发生 GC 时,先强制中断全部线程,而后若是发现某些线程未处于安全点,那么将其唤醒,直至其到达安全点再次将其中断;这样一直等待全部线程都在安全点后开始 GC。递归
主动式中断:不强制中断线程,只是简单地设置一个中断标记,各个线程在执行时轮询这个标记,一旦发现标记被改变(出现中断标记)时,那么将运行到安全点后本身中断挂起;目前全部商用虚拟机所有采用主动式中断。内存
3.安全区域
Safepoint机制保证了程序执行时,在不太长的时间会进入GC的Safepoint,但若是线程处于Sleep状态或者Blocked状态,这种状况须要安全区域(Safe Region)来解决。
安全区域是指在一段区域内,对象引用关系等不会发生变化,在此区域内任意位置开始 GC 都是安全的;线程运行时,首先标记本身进入了安全区,而后在这段区域内,若是线程发生了阻塞、休眠等操做,JVM 发起 GC 时将忽略这些处于安全区的线程。当线程再次被唤醒时,首先他会检查是否完成了 GC Roots枚举(或这个GC过程),而后选择是否继续执行,不然将继续等待 GC 的完成。