过早收集资源垃圾
Posted
技术标签:
【中文标题】过早收集资源垃圾【英文标题】:Resource garbage collected too early 【发布时间】:2010-08-19 20:44:07 【问题描述】:我用SWIG 创建了一个php 扩展,一切正常,但是在链接方法调用时我观察到一些奇怪的垃圾收集行为。例如,这有效:
$results = $response->results();
$row = $results->get(0)->iterator()->next();
printf('%s %s' . "\n", $row->getString(0), $row->getString(1));
但是这个段错误:
$row = $response->results()->get(0)->iterator()->next();
printf('%s %s' . "\n", $row->getString(0), $row->getString(1));
唯一的区别是第一个创建$results
,而第二个将调用链接在一起。
SWIG 实际上只向 PHP 公开函数并生成 PHP 代理类以与它们交互。这些代理类基本上包含一个资源,该资源与这些函数通常采用的任何其他参数一起传递给每个公开的函数。考虑到这些代理类可能是问题所在,我重新编写了代码以绕过它们,而是直接使用公开的函数。和以前一样,这有效:
$results = InvocationResponse_results($response->_cPtr);
$row = TableIterator_next(Table_iterator(Tables_get($results, 0)));
printf('%s %s' . "\n", Row_getString($row, 0), Row_getString($row, 1));
再一次,这个段错误:
$row = TableIterator_next(Table_iterator(Tables_get(InvocationResponse_results($response->_cPtr), 0)));
printf('%s %s' . "\n", Row_getString($row, 0), Row_getString($row, 1));
同样,唯一的区别是第一个创建$results
,而第二个将调用链接在一起。
此时,我在 gdb/valgrind 中调试了一段时间,并确定在将调用链接在一起时,InvocationResponse_results
返回的析构函数调用得太早了。为了观察,我在暴露的 C++ 函数及其析构函数的顶部插入了 std::cout
语句。这是没有链接的输出:
InvocationResponse_results()
Tables_get()
Table_iterator()
TableIterator_next()
__wrap_delete_TableIterator
Row_getString()
Row_getString()
Hola Mundo
---
__wrap_delete_InvocationResponse
__wrap_delete_Row
__wrap_delete_Tables
我在脚本末尾打印了---
,以便能够区分脚本执行期间发生的情况和之后发生的情况。 Hola Mundo
来自 printf
。其余的来自 C++。如您所见,所有内容都按预期顺序调用。析构函数仅在脚本执行后调用,尽管TableIterator
析构函数的调用比我预期的要早。但是,这并没有引起任何问题,并且可能不相关。现在将其与链式输出进行比较:
InvocationResponse_results()
Tables_get()
__wrap_delete_Tables
Table_iterator()
TableIterator_next()
__wrap_delete_TableIterator
Row_getString()
Segmentation fault (core dumped)
如果没有将InvocationResponse_results
的返回值保存到$results
中,那么在执行甚至退出调用链之前(在Tables_get
和Table_iterator
之间)就会很高兴地进行垃圾收集,这很快就会导致问题,最终导致段错误。
我还在各个地方使用xdebug_debug_zval()
检查了引用计数,但没有发现任何异常。这是它在$results
和$row
上的输出,没有链接:
results: (refcount=1, is_ref=0)=resource(18) of type (_p_std__vectorT_voltdb__Table_t)
row: (refcount=1, is_ref=0)=resource(21) of type (_p_voltdb__Row)
在$row
上使用链接:
row: (refcount=1, is_ref=0)=resource(21) of type (_p_voltdb__Row)
我已经花了几天时间解决这个问题,但我几乎没有想法,所以任何关于如何解决这个问题的见解真的将不胜感激。
【问题讨论】:
没有通灵调试能力的任何人都不太可能解决这个问题。我建议您在_zend_list_delete
中放置一个断点,并找出调用代码删除资源的原因。可能是资源引用计数达到 0 或直接删除。
@Artefacto 我在_zend_list_delete
内部偷看__wrap_delete_Tables
被调用,在这两种情况下(没有段错误和段错误),它都被垃圾收集,因为它的引用计数(--le->refcount
)是 - 1.
所以找出为什么__wrap_delete_Tables
在某个特定时间被调用,但在另一个时候没有被调用,然后继续往上走。
在这两种情况下,相关 zval 的引用计数可能正在以不同的方式进行操作。为其refcount
字段设置数据中断(refcount__gc
在 5.3+ 中)。
最后,确保您使用的是调试版本,并且当您使用 valgrind 时,关闭 zend 内存管理器很有用。
【参考方案1】:
This 原来是类似调试问题 segfaulting 的问题的一部分。 (Artefacto 说的)
【讨论】:
以上是关于过早收集资源垃圾的主要内容,如果未能解决你的问题,请参考以下文章
Atitit。Time base gc 垃圾 资源 收集的原理与设计
Spark学习之路 (十四)SparkCore的调优之资源调优JVM的GC垃圾收集器