メモリリークとは何か

Warltersville Rd leak

メモリリークに悩まされている技術者は多いだろう。メモリリークが嫌でGCという技術が開発されたといっても過言ではないし、歴史的にはC++からJavaへシフトが起きた大きな理由のひとつといっていい。Unix系の簡単な定義でいえば、ヒープ領域を指すポインタ(アドレス)をロストしてしまえばそのメモリはもう漏れたといってよい。たとえばこういったコードだ。

struct { int i; char c; } spam;
int main(){
  void* p;
  int i;
  for(i=0; i<1024; ++i){
     p = malloc(sizeof(struct spam));
  }
  pause();
}

このコードではpause(3)の時点で約5KBのメモリが漏れている。free(3)を使えばメモリをOSに返却できるが、アドレスが分からないので返却できない。

ところが、ここでいいたいのは、メモリリークはそれだけじゃないということだ。この時点でピンと来た方には、この話はもしかしたらつまらないかもしれない。
さて、そもそもメモリリークとは何か、Wikipediaをみてみよう。

メモリリーク (Memory leak)とは、プログラミングにおけるバグの一種。プログラムが確保したメモリの一部、または全部を解放するのを忘れ、確保したままになってしまうことを言う。プログラマによる単純なミスやプログラムの論理的欠陥によって発生することが多い。

いわゆる一般的で古典的なメモリリークのことだ。この記事の最後に、リソースリークという項目で漏れるのはメモリだけでなないと書いてある。DBの中の忘れられたレコード、忘れられたTCPソケット、忘れられたiノード、わたしたちの歴史には忘却されたものが沢山あるはずだが、悲しんでいたところで彼らは戻ってこない。

次のコードはPythonで書かれている。これはメモリリークかどうか、考えていただきたい。

class Stack:
  def __init__(self):
    self.stack = []
    self._stack = []
  def push(self, obj):
    self.stack.append(obj)
    self._stack.append(obj)
  def pop(self):
    if len(self.stack) > 0:
      obj = self.stack[0]
      self.stack = [1:]
      return obj
    else:
      return None

s = Stack()
while(True): s.push('spam ham egg')

Pythonだからメモリリークは起きるわけがないか? ポイントは Stack の _stack というメンバだ。もともとは何か目的があって _stack が追加されていたはずだが、いつしかそれは不要になって、 Stack.push のときにpushされるようになっていった。ここでは while(True): の中にあるが、たとえばこのオブジェクト、1日に1000個だけpushされるようなサーバーで動いていたら一年かけて365000個のだけスタックが伸びる。
Pythonのリストのオーバーヘッドにもよるが、1KB の文字列が追加されていくのであれば、少なくとも 1年で365MBのメモリが漏れていることになる。え、そんなの大したことないって? ミドルウェアの開発者は貧乏性なんだよほっとけ。

さて、最初の定義に戻っていただきたい。このとき _stack への参照はコンテキストに残っている。残っているのだからメモリリークではないということになる。いざとなればこのコンテキストを抜けてしまえばよい(ここではトップレベルコンテキストになっているが、普通そんなPythonコードは書かないだろう)。そうすれば refcount が0になるだろう。

だがちょっと待ってほしい。よく考えてほしい。考えなくても分かるか。そのメモリは必要ないのだ。必要ないのに使える状態でプログラム中に残っている。つまり、メモリリークの定義は「プログラム上必要がないメモリ空間が確保されている」ということだ。

世間にはこうやって忘れ去られたメモリやオブジェクトが沢山あるはずだ。valgrindやその他の静的・動的含めた解析ツールでは要不要を判断する手段がない。使われていないかどうかを検証すれば分かるか?その問題はNP困難だかNP完全に見える。

これはJavaだろうがErlangだろうがCだろうが、どんな言語どんな処理系でも同じだ。そのメモリやオブジェクトが必要か不要か、設計者や開発者でないと判断できない。だから、ソースコードのレビューや長期耐久試験*1を通してメモリが太っていかないことを人間の手で見つけ出すしかない。
という話を、遠い昔に職場の先輩としたのを、昨日ふと「Erlangメモリリークなんて起きるわけがない、起きたら大事だ」と自分で言って思い出して反省したのであった。

Debug Hacks -デバッグを極めるテクニック&ツール

Debug Hacks -デバッグを極めるテクニック&ツール


Binary Hacks ―ハッカー秘伝のテクニック100選

Binary Hacks ―ハッカー秘伝のテクニック100選

*1:前職の職場では長安試験と言っていた。私はこの呼称が割と気に入っているが一般的ではないだろうから、この言葉はやめた