“这是我参与8月更文挑战的第27天,活动详情查看:8月更文挑战”
🔉引言
今天同样是开心的一天,不知道兄弟们开不开心,嘻嘻,生活就是这样,你对它笑,它就会对你笑,你对它哭,它就会对你哭,你抱怨它,它也抱怨你,学习呢,也一样,所以我们今天把剩下的Shenandoah收集器内容学完,Shenandoah收集器你就算了解的差不多了,话不多说,我们开始吧。
昨天介绍了Shenandoah收集器的执行流程,今天把Shenandoah收集器里最重要的核心概念-并发整理给捋清楚,就算完结了。
转发指针
转发指针英文名是Brooks Pointer,转发指针是1984年由Rodney A.Brooks的论文中提出来的,作为在对象移动和用户程序并发时的执行方案,原来我们是怎么做的呢,这里就不得不提内存保护陷阱了(Memory Protection Trap)一旦用户程序访问我原来的这个旧对象的时候,你是访问不成功的,那你的访问就会进入到设定好的异常处理器中,之后呢,在这段代码逻辑里掐指一算,找到新的对象的位置,给你转发过去,咋一听好像没毛病,但这个操作必须得有操作系统层面的直接支持,否则你就会在用户态和内核态来回切换,成本非常高昂。
然后,Brooks说了,咱不玩这套,怎么玩呢,就在对象的布局结构的最前面统一增加一个新的引用字段,在正常不处于并发移动的情况下,将它指向自己,图如下:你可能会说这跟句柄好像啊,的确哈,不过句柄是存储在句柄池中,负责对象的位置的,而转发指针是存储在每个对象头前面。
那这样,似乎就比较好玩了,一旦对象发生变动的时候,那根据原来的内存地址就找不到新的对象,那我们就可以先找旧内存的地址,然后找到转发指针,只要它指向新对象的地址就行了,那这样所有通过旧地址访问的代码依旧有用,图如下:
存在的问题
线程安全
当然转发指针也是有开销的,而且由于对象访问频繁,也是一笔不可忽视的成本,而且转发指针是可能存在多线程竞争的,如果大家都读,那没啥问题,那如果大家都写呢?你必须保证写操作只能发生在复制的对象,而不是写入旧的对象中。
我们可以假设出以下三种场景:
- 收集器线程复制了新对象的副本
- 用户线程更新对象的某个字段
- 收集器线程更新转发对象的引用值为新的副本值
那如果我们什么也不做,可能会发生本来用户线程更新的字段发生在旧的对象上,这是绝对不允许的,所以必须对访问转发指针采取同步措施,收集线程访问时,用户线程必须等待。实际上该操作是采用CAS来保证并发访问的准确性的。
执行效率
虽然有了Brooks Pointers来保证原对象和复制对象的访问一致性,但实际上对象访问的范畴是非常大的覆盖了对象的读取、写入、比较、哈希值的计算、加锁等等。要覆盖全部对象的读写操作,不得不同时设置读、写屏障去拦截。
前面介绍收集器时,说写屏障已经累积不少任务了,但为了实现转发指针,Shenandoah在读写屏障也都额外加入了转发处理,尤其是读屏障,由于代码里对象的读取频率比写频率高出很多,所以读操作要谨慎使用,不能有重量级操作,所以在JDK13使用了基于引用读屏障的实现,这块就不多提了,因为我已经肝不动了,大脑都不转了。
📝题外话
从历史上看,人们总是倾向于将宗教和科学视为不可调和的对立物,原因很简单。对于任何一个深信因果律的普遍作用的人来说,下面的想法,即存在一个能够干预世界事件进程的存在物,是完全不可能的。当然,必须假设他对因果律假说持有真正严肃的态度。他不需要恐惧宗教,也不需要社会或道德宗教。对他而言,一个有赏有罚的上帝是难以想象的,因为人的行为活动取决于外在和内在的必然,因而在上帝眼中,他就不需要对自己的行为负责,如同一个无生命体不能对它的行为负责一样。有人因此指责科学,称其有损于道德,但是,这样的指责是不公正的。一个人的道德举止应该有效地建立在同情心、教育和社会关系及社会需求上,不需要任何宗教基础。如果一个人仅仅因害怕受到惩罚或是希望死后得到奖赏而约束行为,那的确是太可悲了。
你们相信上帝的存在嘛