Windowsのタスクマネージャの闇
和歌山で一番のオシャレボーイが、Windowsのタスクマネージャの仮想メモリは、Unixのps(1)やtop(1)などで見えるVSZ(仮想メモリサイズ)とは違うので、見えないメモリリークに気をつけるべし、と教えてくれました。
参考サイト
- http://msdn.microsoft.com/en-us/library/aa965225(VS.85).aspx
- http://msdn.microsoft.com/en-us/library/ms684879(VS.85).aspx
タスクマネージャのメモリ関連の列は3つあります(デフォルトでは非表示の列です)。メモリ使用量、最大メモリ使用量、仮想メモリサイズの3つです。上のサイトの英語との対応を書くと、それぞれ、"Mem Usage"、"Peak Mem Usage"、"VM Size"です。
今まで、語感から、「メモリ使用量」がUnixでのresident size(=物理(実)メモリの使用量)に相当し、「仮想メモリサイズ」がUnixでのvirtual memory size(=仮想メモリの使用量)に相当するのだろうと思っていました(最大メモリ使用量はピーク時の値と予想)。
「メモリ使用量」が物理メモリの使用量(resident size相当)であるのは正しいようです。しかし、「仮想メモリサイズ」の方は、非共有メモリの仮想メモリの使用量と書いてあります。具体的なAPIとの関係を明確にするため、試してみました。
#include <stdio.h> #include <assert.h> //#include <stdlib.h> //malloc #include <windows.h> #define MEMSZ (1024 * 1024 * 32) int main(int argc, char **argv) { HANDLE hh = GetProcessHeap(); while (1) { char* pt = (char*)HeapAlloc(hh, 0, MEMSZ); // char* pt = (char*)malloc(MEMSZ); // char* pt = (char*)GlobalAlloc(GMEM_FIXED, MEMSZ); assert(pt); printf("%p\n", pt); getchar(); } return 0; }
Windows APIのメモリ確保周りはカオスで良く分からないのですが、HeapAlloc()、GlobalAlloc()、malloc()の3つぐらい見ておけば事足りるだろうと思います。上のサンプルはGlobalAlloc()とmalloc()をコメントアウトしてあります。3つのどれかひとつのコメントアウトを外して動かしました。無限ループに入るので、リターンキーを押し続けてメモリを確保し続けます。
上のサンプルを動かして、タスクマネージャのプロセス一覧を観察すると、「仮想メモリサイズ」はリターンキーを押すごとに増加します。「メモリ使用量」はほとんど増えません。これは予想どおりの動作です。
#include <stdio.h> #include <assert.h> //#include <stdlib.h> //malloc #include <windows.h> #define MEMSZ (1024 * 1024 * 32) int main(int argc, char **argv) { HANDLE hh = GetProcessHeap(); while (1) { char* pt = (char*)HeapAlloc(hh, 0, MEMSZ); // char* pt = (char*)malloc(MEMSZ); // char* pt = (char*)GlobalAlloc(GMEM_FIXED, MEMSZ); assert(pt); for (int i = 0; i < MEMSZ; i++) { pt[i] = '\0'; } printf("%p\n", pt); getchar(); } return 0; }
確保したメモリを参照するコードをいれます。このサンプルコードを動かしてタスクマネージャを見ると、「仮想メモリサイズ」も「メモリ使用量」も同時に増えていきます。
確保したメモリを参照すると、物理メモリに(割り当てたページを)マップするからです。他のプロセスが物理メモリを要求して、物理メモリの容量が足りなくなれば、物理メモリにマップされたページはスワップファイルにページアウト(スワップアウト)されます。そうすると、タスクマネージャの「メモリ使用量」の値は減るはずです。この辺の動作はUnixと同じはずなので、わざわざ確認はしていません。
さて、本題はここからです。タスクマネージャの「仮想メモリサイズ」の値が増加しないのに、プロセスのメモリ使用量が増えるパターンがあります。次のサンプルです。
#include <stdio.h> #include <assert.h> #include <windows.h> #define MEMSZ (1024 * 1024 * 32) int main(int argc, char **argv) { while (1) { HANDLE h = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, MEMSZ, NULL); assert(h); char* pt = (char*)MapViewOfFile(h, FILE_MAP_READ|FILE_MAP_WRITE, 0, 0, MEMSZ); assert(pt); /* for (int i = 0; i < MEMSZ; i++) { pt[i] = '\0'; } */ printf("%p\n", pt); getchar(); } return 0; }
確保したメモリを参照するforループはコメントアウトしてあります。コメントアウトした状態で動かしてタスクマネージャのプロセス一覧を見ると、「メモリ使用量」も「仮想メモリサイズ」も値はほとんど増加しません。しかし、タスクマネージャのパフォーマンスタブを見ると、システムのメモリ使用量は上がっていきます。
メモリを参照するforループのコメントアウトを外すと、タスクマネージャの「メモリ使用量」の値は上がり、「仮想メモリサイズ」の値は上がらない、というUnix的な感覚からすると奇妙な状況になります。
対比のために、GNU/Linuxでも似たようなことをしてみます。
#include <stdio.h> #include <stdlib.h> #include <assert.h> #define MEMSZ (1024*1024*32) int main() { while (1) { int i; char* pt = malloc(MEMSZ); assert(pt); for (i = 0; i < MEMSZ; i++) { pt[i] = '\0'; } printf("%p\n", pt); getchar(); } return 0; }
このサンプルを動かして、top(1)、ps(1)、あるいは/proc/${process-id}/statmなどを見ると、resident size(以下、RES)もvirtual memory size(以下、VSZ)もどちらの値も増加します。メモリを参照するforループを無くせば、RESの値は増加しなくなります。
GNU/Linuxのglibcのmalloc(3)は、要求メモリのサイズが一定量より大きい場合、内部でmmap(2)を呼びます(strace(1)で見ると、実際にはmmap2(2)でした)。上のサンプルコードのMEMSZ(=32MB)の値の場合、mmap2(2)を呼びます。
MEMSZを32KBに変えると、システムコールとしてはbrk(2)を呼ぶようになります。top(1)やps(1)で観測できるRESやVSZは、期待どおりの変化を見せます。ちなみに、/proc/${process-id}/mapsファイルの見え方は、mmap(2)とbrk(2)で少し違いがあります。興味がある人は実験してみてください。
WindowsのCreateFileMapping()のコードに相当するサンプルを試してみます。
#include <stdio.h> #include <stdlib.h> #include <assert.h> #include <sys/mman.h> #define MEMSZ (1024*1024*32) int main() { while (1) { int i; char* pt = mmap(NULL, MEMSZ, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0); assert(pt); for (i = 0; i < MEMSZ; i++) { pt[i] = '\0'; } printf("%p\n", pt); getchar(); } return 0; }
これを動かしても、top(1)やps(1)で見えるRESやVSZは期待どおりの動きです。つまり、アプリケーションがメモリを確保すればVSZの値は増えますし、メモリを参照して物理メモリに乗ればRESの値は増えます。ちなみに、このサンプルの/proc/${process-id}/mapsファイルの見え方も少しだけ違うので、興味のある人は試してみてください。
メモリ管理に関してOSの奥深くではどちらのOSもそれなりに複雑なことをしているはずですが、ユーザ空間への見せ方という観点では、Unix系がシンプルで分かりやすいです。
もっとも、かつてのUnixには暗黒の歴史もありました。SVR4系のshm(共有メモリ)やセマフォ周りは、プロセスが不当に死ぬと確保されたリソース(共有メモリやセマフォ)が残り続ける上に、特別なコマンドを使わないとリソースが残っていることが分からず、それらのリソースを解放することもできない、というひどい状況でした。
- Category(s)
- カテゴリなし
- The URL to Trackback this entry is:
- http://dev.ariel-networks.com/Members/inoue/win-taskmgr/tbping
Re:Windowsのタスクマネージャの闇
表現が古いww