作者:凯文 @开源Favorer
垃圾回收几乎是JAVA面试必答题,那么你对JVM垃圾回收机制了解有多少呢?小编为此准备了一个JVM垃圾回收系列的专题,如果读者感兴趣,欢迎关注小编。本CHAT是该专题的第一部分,将为你介绍JVM内存的划分、JVM如何确定一个对象是否为垃圾、垃圾回收算法以及著名的STOP THE WORLD问题和三色标记法。
在正式盘点垃圾回收器开始前,先跟着小编回顾一下JVM的内存划分。
(JVM内存结构图)
如上图所示,JVM的内存分为堆、方法区、程序计数器、本地方法栈和虚拟机栈,根据内存块是否线程独占可分为私有内存和非私有内存,其中,私有内存有程序计数器、虚拟机栈、本地方法栈。
对于堆、虚拟机栈、方法区一般大家应该还比较熟悉,这里小编简单介绍一下程序计数器和本地方法栈。
程序计数器可以视为当前线程执行字节码的行号指示灯,它是控制程序分支、循环、跳转、异常处理、线程恢复等基础功能的基础设施。
本地方法栈和虚拟机栈其实构成类似,只是后者用于处理字节码,前者用于处理本地方法(即Native)。
1、 上面提到了线程私有的内存块,那么非私有的线程块可以是不同进程的线程共享吗?
JVM是属于单进程的,也就是一个程序一个独立的JVM,所以不存在跨进程共享JVM内存的情况。
2、 为何在上面的结构里找不到常量池呢?
准确说是运行时常量池,它是方法区的一部分,是存储字节码中各类字面量和符号的引用。
3、 上面各内存块,在遇到无法再申请到内存时,都会抛出OutOfMemoryError吗?
是的。但栈(虚拟机栈和本地方法栈)还涉及栈深度问题,超过最大深度时抛出StackOverFlowError。
在对象中添加一个计数器,每当有一个地方引用时,计数器就加一;当引用计数器失效时,计数器就减一;当引用计数器值为0时就判定为垃圾。
选定GCROOT对象,通过引用关系向下搜索,能遍历到的即仍可能被调用,不能被引用到处于游离状态的即判定为垃圾。
1、 可以作为GCROOT的对象有哪些?
1)在虚拟机栈(即栈帧中的本地变量表)中引用的对象;2)方法区中静态对象;3)常量池中的引用;4)本地方法栈中JNI引用的对象;4)同步锁持有的对象;5)虚拟机相关的基础对象如类加载器、异常管理对象。
2、JVM为何选用可达性分析而不采用引用计数法?
引用计数法中有一个死穴,对循环依赖没有办法。
默认情况下,垃圾回收都是针对堆内存进行垃圾回收。那么,方法区为什么不进行垃圾回收呢?我们知道,方法区存放的主要是常量和类型,对其是否为垃圾的判定过程比较复杂,相较于其可回收的内存块,就显得“性价比”不足。
首先标记出所有要回收的的对象,标记完成后,统一回收掉所有被标记的对象,当然,反过来也一样,可以标记不回收的对象,标记完成后,统一回收未被标记的对象。
缺点:1、执行效率不稳定,如果堆中包含大量对象,且属于需要回收的,必须进行大量标记和清除,效率会下降比较厉害。2、标记清除后会导致大量不连续的空间,会因为找不到足够的连续空间触发新一次的GC活动。
该算法是对清除法的优化,将内存块分为等大的两块,其中在其中一个半块中完成标记清除后,整体复制到另一个半块后,再完全清理本半块。
原理:将新生代内存进行分配,维持一个干净、连续的区域。例如Appel回收理论推出的Eden、survivor from 、 survivor to 区域。(注:当to区域无法承担一次完整的拷贝,多余的对象将直接送往老年代)
优点:解决了内存连续的碎片化问题。
缺点:在存活率较高的场景下,较多的复制操作会拉低效率。
在标记完成后,将存活对象向内存的一端移动,然后一次性清理掉边界以外的内存区。
缺点:对存活对象进行内存移动,将触发 stop the world。(注:若不移动采用分区链表的方法来存储,将极大压缩程序的吞吐量)
垃圾在回收过程中,我们的用户线程在标记和移动两个环节是需要停止工作的,这个过程我们称之为STOP THE WORLD。为何需要线程停下来的道理是比较容易想见的,标记我们需要基于一个静态的环境着手,如果一直处于动态变化中,那么是找不到某个时点确切的完整视图的;移动就更好理解,因为我们的内存地址发生了调整,必须停下来重新建立关系。
三色标记问题也是一个面试高频问题,其是处理并行标记的一种方法。具体做法是,将对象分为黑白灰三种颜色,白色表示垃圾回收器从未访问过;黑色表示已被垃圾回收器访问过且对象的所有引用也已经访问过;灰色则是前面两种情况的中间状态,即被垃圾回收器访问过,但其引用至少还存在一个没有被访问。
初始情况下,只有GCROOT对象是黑色的,其他的都是白色的对象。随着垃圾回收器的不断扫描,对象一个一个经历由白变灰再变成黑色的过程,遍历完成后,剩下的白色对象即为不可达对象,判定为垃圾,整个过程如下图所示:
(开始状态)
(标记中状态)
(标记完成状态)
善于思考的读者心中可能已经有了疑问,上面的过程两色标记即可,三色标记究竟解决了什么问题呢?这里的优势主要在于通过黑色、灰色两种颜色可以携带部分标记和完全标记两种不同的信息,在标记时可以减少扫描范围,提升标记效率。
前面讲到,三色标记法是解决并发标记问题,那么在并发期间,如果发生了引用关系的变迁,如下图所示,又该怎么处理呢?
(并行标记引用状态变化)
如上图,若原来的第三个对象处于灰色状态时,正下方与其存在引用关系的对象尚处于白色,这时候由于引用关系发生变迁,下方的对象与其不再有关系,转而与前一个黑色对象建立了引用关系,根据三色法的标记原则,黑色节点的引用对象是无需再次标记的,这时候下面的白色对象就会因为保持白色而被当成垃圾回收,从而产生严重的问题。这种现象,我们称之为漏标,漏标问题wilson在90年的就总结出了只有满足下面两个条件才会产生:
1. 至少有一个黑色对象在自己被标记之后指向了这个白色对象
2. 所有的灰色对象在自己引用扫描完成之前删除了对白色对象的引用
为了解决这个问题,人们想到了通过破坏上面条件中的任何一个来避免漏标。
使用增量更新,是破坏第一种情况达成的方法。当黑色对象关联该白色对象的时候,会通过“写屏障”记录该黑色对象, 在重新标记的时候,该黑色对象变为灰色,重新开始修正标记。但是这种方案能够确保垃圾都被清理,缺点就是效率非常低,因为会扫描到整个黑色对象所有引用。
原始快照备份,是破坏第二种情况达成的方法。如果用户线程在灰色对象断开一个白色对象的时候,记录其原始快照,在重新标记的时候,将白色对象变为灰色对象扫描整个链,如果该白色对象没有被其他对象关联,那么此次并不会被清理,而是在下次再进行回收,此次就幸存了。
以上就是本CHAT的相关内容,下一期,凯哥将和你一起盘点JVM垃圾回收器,欢迎继续关注。
开源Favorer为读者提供了广泛的IT认证类辅导资料,欢迎点击领取「链接」
留言与评论(共有 0 条评论) “” |