GC がちゃんと動いているところを見たい

ひとつ前のエントリで、GC のタイミングにあわせて裏にあるオブジェクトが解放されるような仕掛けを入れました…が、ちょっとやそっとでは(…という言い方も語弊があるかとは思いますが) GC がおこらないので、テストするのが面倒です。
こういうときは、GC を手でトリガできるととても便利。Java でいう System.gc() みたいなものがほしいわけです。
だけど、V8 の API にはそんなのあるの!?


…答えからいうと、公開されている API にはありません。:-(
ただ、がんばればやり方が 2 通りほどあります。

(1) internal な領域に足を踏み入れる

V8 のソースコードの heap.cc あたりを見ると、CollectAllGarbage() とかよさそうな関数が定義されていますが、これは v8 namespace の API としては公開されていません。
ただ、なんとかして呼ぶことはできます。次のようにすれば OK です。

#include <v8.h> // こっちは v8/include にある v8.h
#include <../src/v8.h> // v8/src の v8.h
using namespace v8;

int main()
{
  // ... テストなど ...
  
  v8::internal::Heap::CollectAllGarbage(); // ここで GC を強制的に起動

  // ... つづき ...
}

(2) Javascript の extension を用いる

V8 のコンテクスト生成時に extension set を渡せるので、そこで "v8/gc" という extension を導入することで、Javascript のグローバルに gc() という関数が現れるようになります。これを呼ぶと GC が起動されます。
この方法は、internal なところを触らないのでちょっとは分離性がよくなります。

#include <v8.h>
using namespace v8;

int main()
{
  // ... 準備 ...

  // Create context
  const char* extensionNames[] = {
    "v8/gc",
  };
  ExtensionConfiguration extensions(sizeof(extensionNames)/sizeof(extensionNames[0]), extensionNames);
  context = Context::New(&extensions, global);
  Context::Scope context_scope(context);

  // ... テストなど ...
}

じゃあテストしてみよう

#include <v8.h>
#include <../src/v8.h>
using namespace v8;

class Counter {
public:
  explicit Counter(int initialValue = 0)
      : m_Count(initialValue)
  { printf("Counter construct this=%p\n", this); }
  virtual ~Counter()
  { printf("Counter destruct this=%p\n", this); }

  // ...
};

// ...

static void ReportException(const TryCatch& try_catch)
{
  String::Utf8Value str(try_catch.Exception());
  printf("Exception: %s\n", str);
}

static void RunScript(const char* script)
{
  HandleScope handle_scope;
  TryCatch try_catch;

  Local<Script> script = Script::Compile(String::New(script));
  if (script.IsEmpty()) {
    ReportException(try_catch);
  } else {
    Local<Value> result = script->Run();
    if (result.IsEmpty()) {
      ReportException(try_catch);
    } else {
      String::Utf8Value str(result);
      printf("Result: %s\n", str);
    }
  }
}

Persistent<Context> context;

int main()
{
  HandleScope handle_scope;

  Local<ObjectTemplate> global = ObjectTemplate::New();
  CounterJSIF::InitializeTemplate(global);

  context = Context::New(NULL, global);
  Context::Scope context_scope(context);

  // Test
  printf("---\n");
  RunScript("Counter");
  RunScript("var c = new Counter(); c");
  RunScript("c.count");
  RunScript("c.add(); c.count");
  RunScript("var c2 = new Counter(10); c2");
  RunScript("c2.add(); c2.count");
  printf("---\n");
  RunScript("c = c2 = undefined;");
  v8::internal::Heap::CollectAllGarbage();
  printf("---\n");
  RunScript("var c = new Counter(); c");
  // ... このほかにもテストあれば ...

  context.Dispose();
}

これを実行すると次のように出る。

---
Result: function Counter() { [native code] }
Counter construct: this=003BBE40
Result: [object Counter]
Result: 0
Result: 1
Counter construct: this=003B5AE0
Result: [object Counter]
Result: 11
---
Result: undefined
Counter destruct: this=003B5AE0
Counter destruct: this=003BBE40
---
Counter construct: this=003BBE40   ←ここでメモリが実際に再利用される
Result: [object Counter]