搜索
写经验 领红包
 > 电器

jvm标记清除算法(jvm方法区回收)

导语:大牛带你深入理解JVM并发标记清除回收,标记栈溢出各种处理方法标记栈溢出的各种处理方法在新生代的复制算法和老生代的并发标记算法中都借助于标记栈记录待标记对象,使用标记栈的性能效果较好,原因是数据局部性更好。但是使用标记栈有一个最大的问题就是标记栈的容量的设计。如果标记栈过大,会造成不必要的内存浪费,如果标记栈过小,会造成标记栈溢出,标记栈溢出时需要进行额外的处理。由于算法在运行时都是多线程执行的,为了避免多个线程之间的相互竞争,每个线程都有一个标记栈。但这样的设计中如果标记栈过大,造成的内存浪费就会加剧。所以更加常见的方式是使用较小的标记栈,在标记栈不足时优先进行标记栈的扩展,如果扩展后仍不满足使用的需要,则使用标记栈溢出的技术来处理。在ParNew中使用了链表法来处理标记栈的溢出,而在并发标记中则直接对溢出对象进行重标记,除了JVM中使用的这两种方法外,还有逆指针法,本节稍微介绍一下相关知识。重新标记法在标记过程中如果发现线程的标记栈空间不足,发生了溢出,可以设计一个溢出标记位,在第一轮标记完成后继续执行第二轮标记,直到没有标记溢出发生。这是最简单的方法。在实现时通常从内存的一端向另一端遍历标记对象,假设遍历内存的方式是从低向高移动,那么可以设计一个变量保存所有溢出对象的最低地址,在下一轮标记中从溢出对象的最低地址开始标记,这样就可以优化标记的执行时间。老生代的并发标记算法采用的就是这种方法。该方法最大的问题是,标记过程中可能发生大量标记对象的重复检查,导致性能较低。但是该方法的实现最为简单,同时在并发标记中不需要做任何额外的支持。全局列表法全局列表法使用一个额外的全局空间存储来暂存标记栈溢出的对象。为什么使用一个额外的列表可以解决标记栈溢出的问题?其实原理非常简单,线程在标记时从本地线程标记栈获取对象,然后递归标记;将溢出对象暂存到一个全局列表中,本地线程暂时不处理这些对象,待本线程的标记栈中全部清空后再去全局列表中把溢出对象移入本地标记栈中重新进行标记。使用全局列表法也有两种不同的实现:一种方法是使用一个额外的内存空间来暂存溢出的对象,此时全局列表的大小是不能限制的,也就是说在极端情况下,可能因为全局标记栈过大而导致JVM耗尽内存;另外一种方法是借助于对象头,复用对象头的内存空间形成链表,但是因为对象头包含元数据信息,所以在使用这种方法时需要对必要的元数据进行保存,待对象真正标记后再恢复对象头。在实际应用中,大部分对象的对象头都没有有效的信息,所以并不需要保存。这种方式只需要有限的空间保存对象头信息就可以处理溢出对象。但是这样的方法实现起来比较复杂,需要在对象溢出时利用对象头构造链表,同时还存在处理多个线程同时访问全局列表的同步问题。这两种方法在JVM中都有使用,比如G1、ZGC等都使用额外的空间来存储溢出对象,而CMS中则是利用对象头形成全局列表,只需要很少的空间保存溢出对象的对象头信息即可。逆指针法Schorr、Waite(在1967年)以及Deutsch(在1973年)分别独立设计了逆指针的方法以解决标记栈溢出问题。其思路是,在标记时,利用正在遍历对象的成员指针来保存对象的追踪,通过使用成员变量指针指向其父节点来记录引用关系,同时辅以3个额外的指针来记录对象关系,在对象标记完成后(相当于变成黑色),再恢复对象的引用关系。逆指针本质上相当于把堆空间直接作为标记栈。关于该算法的更多内容可以参考相关文献。虽然该算法设计得相当优雅,但是该算法在实际中几乎没被使用过,主要原因是它会重复访问对象,耗时非常严重,性能极其低下。本文给大家讲解的内容是JVM垃圾回收器详解:并发标记清除回收,标记栈溢出的各种处理方法下篇文章给大家讲解的内容是JVM垃圾回收器详解:并发标记清除回收,元数据内存管理感谢大家的支持!

免责声明:本站部份内容由优秀作者和原创用户编辑投稿,本站仅提供存储服务,不拥有所有权,不承担法律责任。若涉嫌侵权/违法的,请反馈,一经查实立刻删除内容。本文内容由快快网络小林创作整理编辑!