[讨论] 关于memory_serialize_page的一些疑问
stefmoon
2013-10-25
memory_serialize_page是在HotSpot VM启动时,在polling page后面分配的一个page,是用来在不使用memory barrier系指令的场景下模拟其操作,这样VM Thread可以在Java线程状态发生变化时,及时获取到它们的状态,以正确地进行safe point时的管理。HotSpot VM有一个参数UseMembar来控制是否使用memory barrier系的指令,在x86下默认情况是关的,这样应该是出于性能方面的考虑。之前加入memory barrier系指令是因为出现了线程在safe point时无法正常block的bug,这里有些许介绍。后来曾经尝试过去掉参数UseMembar,相当于把默认情况下的-XX:-UseMembar给固化了,但后来发现还是有bug,于是又重新加上了。
以上都是我个人的理解,如有不对的地方,还请指正。 基本原理是这样,但具体的实现却有很多令人困惑的地方,就是HotSpot是如何通过memory_serialize_page来模拟memory barrier系的指令的。 当线程状态发生改变时,会调用ThreadStateTransition::transition(JavaThread *thread, JavaThreadState from, JavaThreadState to),里面的相应代码如下: // Make sure new state is seen by VM thread if (os::is_MP()) { if (UseMembar) { // Force a fence between the write above and read below OrderAccess::fence(); } else { // store to serialize page so VM thread can do pseudo remote membar os::write_memory_serialize_page(thread); } } 这个地方的逻辑是一目了然,问题就出在这个os::write_memory_serialize_page上: static inline void write_memory_serialize_page(JavaThread *thread) { uintptr_t page_offset = ((uintptr_t)thread >> get_serialize_page_shift_count()) & get_serialize_page_mask(); *(volatile int32_t *)((uintptr_t)_mem_serialize_page+page_offset) = 1; } 这个offset看起来应该是某个线程在memory_serialize_page里的offset,对应某个线程应该会占int32_t,也就是4个字节,1个page一般是4096字节,也就是说只支持1024个线程? 还有如何保证每个线程对应一个int field,这个地方的计算我也感到很困惑,get_serialize_page_shift_count()是4,计算方法是log2_intptr(sizeof(class JavaThread)) - log2_intptr(64),64是指cache line size,这么算的意义不太明白;get_serialize_page_mask()是4092,即11 11111100,这个值倒很好理解,计算方法是vm_page_size() - sizeof(int32_t)。 我的最新理解:其实每个线程都可以只写memory_serialize_page的一个地方,但出于避免cache竞争的考虑,尽量写到不同的地方,写到哪个cache line和地址后log2_intptr(64)位完全没关系,而JavaThread对象地址除了后log2_intptr(sizeof(class JavaThread))位,前面的部分是完全不同的,因此右移log2_intptr(sizeof(class JavaThread)) - log2_intptr(64)位后,JavaThread对象地址中决定出现在哪个cache line的部分是完全不同的。 好好,问题写着写着感觉自己弄明白了。。。 |
|
stefmoon
2013-10-25
在serialize所有线程状态时,VM Thread做的操作如下 // Serialize all thread state variables void os::serialize_thread_states() { // On some platforms such as Solaris & Linux, the time duration of the page // permission restoration is observed to be much longer than expected due to // scheduler starvation problem etc. To avoid the long synchronization // time and expensive page trap spinning, 'SerializePageLock' is used to block // the mutator thread if such case is encountered. See bug 6546278 for details. Thread::muxAcquire(&SerializePageLock, "serialize_thread_states"); os::protect_memory((char *)os::get_memory_serialize_page(), os::vm_page_size(), MEM_PROT_READ); os::protect_memory((char *)os::get_memory_serialize_page(), os::vm_page_size(), MEM_PROT_RW); Thread::muxRelease(&SerializePageLock); } 把memory_serialize_page设为只读,我的理解是这样会使之前写过这个page的线程对应的cache write back到内存里,也就能够使相应的线程状态更新到内存里。 |
|
LeafInWind
2014-04-25
stefmoon 写道 把memory_serialize_page设为只读,我的理解是这样会使之前写过这个page的线程对应的cache write back到内存里,也就能够使相应的线程状态更新到内存里。 这个理解应该是对的。一直很疑惑代码中为什么要连续的置为只读和可读写。 考虑到serialize_page在init_2中首先通过mmap设置为可读写,这里先置为只读清一下cache,保证之前的对java线程状态的修改操作为其他所有线程都可见,然后再重新置为可读写,等待之后的write_serialization_page操作。 |
|
fei1710
2018-06-29
protect_memory由于涉及到页表的更改,需要刷新对应页的CPU的TLB cache。OS会向各CPU发送IPI中断来做这个事。各CPU在处理TLB cache的时候,必定会先将对应页的本地store buffer刷新到内存(如果不做,那将来再刷的时候可能引起内存访问违例,这违反单线程处理语义)。而OS会一直等待,直到各CPU都处理完成。所以事实上protect_memory会跟各CPU对这个页的访问建立一个同步点。而对这个页的访问必须是写,因为TSO架构的CPU写之间是不会发生乱序的,所以可以保证前面对线程状态的写发生在这个保护页的写之前。原理其实挺复杂的。
|
相关讨论
相关资源推荐
- 什么是In-Memory计算? Mastering In Memory Computing:A Comprehensive Guide
- Hotspot 垃圾回收之SafepointSynchronize(一) 源码解析
- 【genius_platform软件平台开发】第五十九讲:Linux系统之V4L2视频驱动-VIDIOC_QUERYBUF查询缓存信息
- jquery源码_详细中文注释
- Linux内存管理(十八):percpu 分配器——动态分配
- android emulator虚拟设备之qemu pipe分析(三),头条android面试算法
- item_search_shop - 获得店铺的所有商品
- Doris之BE的所有配置项(全面)
- RocketMq存储之___消息写入内存
- item_search_shop - 1688获得店铺的所有商品