JVM如何判断数据是引用还是基本数据(保守式GC)

       HotSpotJVM在进行GC回收的时候,采用的是可达性分析法来对对象进行标记的,可达性分析法是从GC Roots出发(注意是GC Roots 说明是有多个GC Root),当某个对象到GC Roots没有任何引用链时,则该对象判定为可回收对象(当然如果该对象重写过finalize()方法的话 还会有一次自我拯救的机会,会先将该对象放入一个F-Queue对象中)。不得不说,可达性分析法是一个非常不错的方法,能够解决引用计数法中相互引用而导致无法回收的问题。

     从上面的描述中,我们不难发现有个GC Roots,那么什么能作为GC Roots呢,肯定是只有引用能作为GC Roots。在JVM中,作为引用的有:①虚拟机栈中的局部变量表②本地方法栈中的局部变量③静态变量④常量。为了简化问题,我们就取虚拟机栈中的局部变量来说明。

    经过上面的描述,问题已经简化成如何判断虚拟机栈中的局部变量表中的数据存的是一个引用还是一个基本数据。打个比方:

        从图中可以看出,对于变量A,JVM在得到A的值后,肯定能够立刻判断出它不是一个引用,为什么?,因为引用是一个地址,JVM中地址是32位的,也就是8位的16进制,很明显A是一个4位16进制,不能作为引用(这里的专业术语叫对齐检查)。同时,JVM对变量D也是能够立刻判断出它不是引用,因为Java堆的上下边界是知道的,如同图中所标识的堆起始地址和最后地址,JVM发现变量D的值早就超出了Java堆的边界,故认为它不是引用(这里的专业术语叫做上下边界检查)。

       接下来才是重点,对于变量B和变量C,发现他们两个的值是一样的,(这里要说明的是,虽然图中画了一个从变量B到实例的一个箭头,但那对于JVM是不知道的,在执行完new 指令后,B的值就变为了0x01110111,画个箭头只是我们人为便于分析的),于是JVM就不能判断了,在专业名称上,基于这种方式的GC称为“保守式GC”,为什么保守,我想应该不言而喻了。

       接下来我们继续谈这个保守式GC,这种保守式GC的内存模型并不是上图所示的,我们试想,当执行B=null之后,对象B的实例的确应该没有任何指向了,但是JVM错误的认为变量C的值是一个引用,因为此时JVM很保守,担心会判断错误,所以只好认为C是一个引用,不能误删这实例对象(然而我们知道这个情况下,它不会误删)。

      让我们把B=null这条语句的假设去除,B变量还是存在。对象B的实例顺利的存活了下来,然后经过一次新生代的复制算法,进入Survivor区,注意,此时对象B的实例存活了下来,但它要经过一次内存的变更,也就是说对象B的实例地址要改变,JVM现在很慌,为什么慌,因为C变量如果当做引用,它也会指向对象B的实例,此时C的值也应该随实例B内存的变量而改变C的值,但是万一C变量不是一个引用,而就是一个int型数据呢。于是保守式GC真正的内存模型出来了:

通过上图,不难发现,在java堆中增加了一个句柄池,当变量B的实例更改存放内存的地方后,JVM只要改变句柄值,而不用改变变量B和变量C的值,这样JVM就不用犯愁了,因为不论变量C是不是一个引用,之后用到C的地方,C的值也没有发生变化,可以正常使用。

通过上述的分析,我们可以发现,在保守式GC中,只能通过堆的上下边界检查和对齐检查去判断是否为一个引用。同时,我们可以发现保守式GC的一些缺点:①伪引用,如同上面所说的,当B=null之后,本来实例B应该被GC掉的,但是有变量C这么个伪引用存在,JVM不敢动手②增加了句柄池,占用java堆的空间,虽然灵活了,但是访问效率变低了,需要两次才能访问到真正的对象。

本篇文章讲述了保守式GC中如何判断一个变量是否为引用,并在此基础上给出了其内存模型,对于半保守式GC,准确式GC的判断方式,会有新的文章。

参考自:《深入理解JVM》

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值