Haskell中unsafeThaw数组写入引发段错误的场景分析
老年代不可变数组引用新生代对象的失效场景与unsafe数组操作风险解析
什么时候unsafeThaw后的写入会因GC根丢失引发段错误?
- 当已进入老年代的不可变数组被
unsafeThaw转为可变数组,且数组内部引用了新生代对象,同时这个可变数组本身没有被任何GC根持有:- GHC分代GC默认不会扫描老年代对象去标记它引用的新生代对象(这是性能优化),一旦GC触发,那些未被直接根引用的新生代对象会被回收
- 这时对可变数组做写入操作,就会访问已经被GC回收的内存,直接触发段错误
- 额外前提:原不可变数组的引用已经被完全丢弃,可变数组也没绑定到活跃变量、顶层符号或者其他被GC跟踪的对象上
GC机制是怎么影响这些unsafe操作的?
- GHC分代GC基于代际假说:老年代对象存活久,新生代对象存活短。默认逻辑里,GC不会遍历老年代对象的引用去标记新生代对象——这省了扫描时间,但也给unsafe操作挖了坑
- 不可变数组进入老年代后,GC会认为它的引用关系是稳定的,不再跟踪它指向的新生代对象;用
unsafeThaw转成可变后,写入操作也没法让GC重新感知这些引用
unsafeThaw/unsafeFreeze的安全边界
unsafeThaw和unsafeFreeze只是unsafe的类型转换,不做内存拷贝或引用更新:unsafeThaw只是把不可变数组的类型改成可变,完全不会修改GC的引用跟踪状态unsafeFreeze把可变数组转回不可变,这时GC会重新把它当成稳定对象,但如果之前写入了新生代对象,还是可能存在引用失效的隐患
- 要安全用这两个操作,必须保证:从
unsafeThaw到unsafeFreeze的整个过程中,可变数组始终被GC根持有(比如绑定到局部变量且没被编译器优化掉,或者被其他活跃对象引用)
不调用unsafeFreezeArray会有什么问题?
- 如果可变数组没被
unsafeFreeze就丢弃:- 要是数组本身没有任何引用,GC会回收它的内存,后续如果有残留指针(比如C代码调用、其他unsafe操作)访问这块内存,就会触发段错误
- 就算数组没被回收,它内部引用的新生代对象可能已经被GC收走,导致数组里的指针变成悬空指针
- 长期不freeze的可变数组会一直占用GC根资源,拖慢GC性能;同时,不可变数组能享有的优化(比如内存共享、只读映射)也没法用上
内容的提问来源于stack exchange,提问作者ron
相关产品推荐
相关产品推荐

