嵌入式 V8 隔离映射的内存泄漏



需要V8开发人员。

我注意到以下代码泄漏了映射内存(mmap,munmap(,具体来说,cat /proc/<pid>/maps内的映射区域数量不断增长并很快达到系统限制(/proc/sys/vm/max_map_count(。

void f() {
auto platform = v8::platform::CreateDefaultPlatform();
v8::Isolate::CreateParams create_params;
create_params.array_buffer_allocator =
v8::ArrayBuffer::Allocator::NewDefaultAllocator();
v8::V8::InitializePlatform(platform);
v8::V8::Initialize();
for (;;) {
std::shared_ptr<v8::Isolate> isolate(v8::Isolate::New(create_params), [](v8::Isolate* i){ i->Dispose(); });
}
v8::V8::Dispose();
v8::V8::ShutdownPlatform();
delete platform;
delete create_params.array_buffer_allocator;
}

我对platform-linux.cc文件进行了一些尝试,发现UncommitRegion调用只是用PROT_NONE重新映射区域,但不释放它。可能这与这个问题有关。

我们在程序执行期间重新创建隔离有几个原因。

第一个是,就 GC 而言,创建新的隔离并丢弃旧的隔离更容易预测。基本上,我发现做

auto remoteOldIsolate = std::async(
std::launch::async,
[](decltype(this->_isolate) isolateToRemove) { isolateToRemove->Dispose(); },
this->_isolate
);
this->_isolate = v8::Isolate::New(cce::Isolate::_createParams);
// 

比调用LowMemoryNotification更可预测、更快。因此,我们使用GetHeapStatistics监视内存消耗,并在达到限制时重新创建隔离。事实证明,我们不能将 GC 活动视为代码执行的一部分,这会导致糟糕的用户体验。

第二个原因是,每个代码的隔离允许并行运行多个代码,否则v8::Locker将阻止该特定隔离的第二个代码。


看起来在这个阶段我别无选择,将重写应用程序以为每个代码提供一个隔离池和持久上下文。当然,这种方式code#1可能会通过执行许多分配来影响code#2,并且GC将在完全没有分配的code2上运行,但至少不会泄漏。


附言。我已经提到我们使用GetHeapStatistics进行内存监控。我想澄清一下这一部分。

在我们的例子中,当 GC 在代码执行期间工作时,这是一个大问题。每个代码都有执行超时(100-500ms(。在代码执行期间具有 GC 活动会锁定代码,有时我们只是为赋值操作超时。GC 回调不能为您提供足够的准确性,因此我们不能依赖它们。

我们实际做什么,我们指定--max-old-space-size=32000(32GB(。这样 GC 就不想运行,因为它应该看到存在大量内存。使用GetHeapStatistics(以及我上面提到的隔离娱乐(,我们可以手动监控内存。


.PPS。我还提到代码之间的共享隔离可能会影响用户。 假设你有user#1user#2.它们中的每一个都有自己的代码,两者都是不相关的。code#1有一个具有巨大内存分配的循环,code#2只是一个赋值操作。GC 可能会在code#2期间运行,user#2将收到超时。

需要 V8 开发人员。

请在 crbug.com/v8/new 提交错误。请注意,此问题可能会被视为低优先级;我们通常假设每个进程的分离物数量仍然相当小(即,不是数千或数百万(。

有一个分离物池

是的,这可能是要走的路。特别是,正如您已经写过的,如果要并行执行脚本,则每个线程需要一个隔离。

这样,代码#

1 可以通过执行许多分配来影响代码 #2,并且 GC 将在代码 2 上运行,根本没有分配

不,这不可能发生。只有分配会触发 GC 活动。无分配代码将花费零时间执行 GC。此外(正如我们之前在前面的问题中所讨论的(,GC 活动被分成许多微小的(通常是亚毫秒级(步骤(这些步骤又由分配触发(,因此特别是短期运行的代码不会遇到一些巨大的 GC 暂停。

有时我们只是为了分配操作而超时

这听起来令人惊讶,而且听起来与GC无关;我敢打赌还有其他事情正在发生,但我无法猜测那可能是什么。你有复制品吗?

我们指定 --max-old-space-size=32000 (32GB(。这样 GC 就不想运行,因为它应该看到存在大量内存。使用 GetHeapStatistics(以及我上面提到的隔离重新创建(,我们可以手动监控内存。

你有没有试过做这些?默认情况下,V8 的 GC 进行了非常精细的调整,我认为以这种方式避开它会导致比它解决的问题更多的问题。当然,您可以尝试任何您喜欢的东西;但是,如果结果的行为不是你所希望的,那么我的第一个建议是让 V8 做它的事情,并且只有在你发现默认行为不令人满意时才进行干预。

Code#

1 有一个具有巨大内存分配的循环,Code#2 只是一个赋值操作。GC 可能会在代码 #2 期间运行,用户 #2 将收到超时。

再次:没有。未分配的代码不会被 GC 中断。并且同一隔离中的多个函数永远无法并行运行;一个隔离中只能同时有一个线程处于活动状态。

最新更新