更新履歴(4件・最終更新 2026年08月02日)
この記事に加えた変更の記録です。アーカイブした更新前のバージョンは、DOI付きの固定URLから読めます。
- 記事の冒頭に「この記事の知識マップ」節を追加しました。本文で扱っている概念とその関係を、要約・図・詳細ページへのリンクにまとめたものです。本文の主張は変えていません。
- 外部レビュー(1283件)への対応として本文を更新しました。個々の変更内容は、この下の履歴を参照してください。
- for コマンドの変数の書き方について、「バッチファイル内でも直接実行でも同じ」という誤った注記を修正しました。バッチファイル内は %%d、コマンドプロンプトに直接打つときは %d と書き分けが必要です。
- Process Explorer と VMMap の操作を、メニュー名どおりの番号付き手順として書き起こしました。あわせて前提環境、dbghelp.dll の入手方法、前編の該当節の要点、名前で検索できるのは名前付きオブジェクトだけという制約をまとめにも加えています。
- 初版公開
この記事を引用する(DOI: 10.5281/zenodo.21543894)
この記事はZenodoにアーカイブされています。常に最新版へ解決されるDOIと、いま表示している版に固定されたDOIの両方を下に示します。
小村 豪(2026)「Process Explorer / Handle / VMMap実践 ── ハング・リーク・「ファイルが使用中」を今この瞬間の状態から追う」合同会社小村ソフト. https://doi.org/10.5281/zenodo.21543894 https://staging.comcomponent.com/blog/sysinternals-process-explorer-handle-vmmap-guide/
- DOI(最新版)
- 10.5281/zenodo.21543894
- DOI(この版)
- 10.5281/zenodo.21733143
「月曜の朝に再起動すれば直るんです」── 装置連携アプリの保守相談で、何度も聞いたセリフです。稼働直後は快調なのに、木曜あたりから画面の切り替えがもたつき、金曜の夕方にはボタンを押しても数秒返ってこない。たまに「ハンドルが無効です」という見たこともないエラーが出る。そして再起動すると、何事もなかったように直る。こうなるともう運用は「毎週再起動」で固定され、根本原因は誰も知らないまま数年が過ぎます。
この種の症状は、ログをいくら眺めても原因にたどり着けません。必要なのは「今この瞬間、そのプロセスが何をどれだけ抱え込んでいるか」を直接見ることです。前回の「Process Monitor(ProcMon)実践ガイド」では、操作の時系列を記録するProcMonを扱いました。今回はその続編・Sysinternals実践の第2弾として、状態を見る側のツール ── Process Explorer・Handle・VMMap ── を、長期稼働アプリの三大症状「だんだん重くなる」「ファイルが消せない」「ハングした」に沿って整理します。
この記事の前提環境は次のとおりです。
- OS ── Process Explorerの公式の動作要件は、クライアントがWindows 11以降、サーバーがWindows Server 2016以降です。1
- ビット数 ── 64bit環境では
procexp.exeを起動すると64bit版が自動的に展開・実行されます。64bitプロセスのスタックやハンドルを正しく見るには、この64bit版で動いている必要があります。 - 権限 ── 調査目的では「管理者として実行」が事実上必須です。一般権限のままだと、他ユーザーやサービスのプロセスに限ってスレッドスタックもハンドル一覧も「アクセスが拒否されました」になります。CLI版の
handle.exeは公式に管理者権限が必要とされています。2 - 初回起動 ── EULA(使用許諾契約)への同意ダイアログが出ます。スクリプトや無人実行では
/accepteulaスイッチで同意を明示します(8章)。
なお本記事はGUIツールの操作を扱いますが、画面キャプチャは掲載していません。代わりに、メニュー名とショートカットキーを実際の表記のまま書いています。手元でツールを開きながら読める形にしてあります。
1. まず結論
- ProcMonが「履歴」、Process Explorerは「現在」です。Process Explorerは、各プロセスが開いているハンドルと読み込んだDLLを一覧・検索できるツールで、DLLバージョン問題やハンドルリークの調査に向くと公式も明言しています。1
- 「ファイルが消せない・差し替えられない」はFind Handle or DLL(Ctrl+F)が最短です。パスの一部で検索すれば、そのファイルを掴んでいるプロセスが数秒で特定できます(3章)。
- 「ハングした」はプロセスのプロパティ→Threadsタブでスレッドのスタックを見ます。ただしシンボル設定(dbghelp.dllとシンボルパス)をしないとアドレスの羅列しか出ません(4章)。13
- 「だんだん重くなる」はカラムを足して傾きを見ます。Handle Count・USER Objects・GDI Objects・Private Bytesの4つを追加し、時間を置いて増え続けるものを探します。GDI/USERオブジェクトにはプロセスごとの上限があり、達すると描画やウィンドウ作成が壊れ始めます(5章)。4
- CLI版のhandle.exeはスクリプトによる定点観測に向きます。
handle -s -p <プロセス>で種類別のハンドル数を定期記録できます。-cによるハンドル強制クローズは公式が不安定化を警告しており、基本使いません。2 - 「Private Bytesが増え続ける」の内訳はVMMapで割ります。Heapならネイティブ、Managed Heapなら.NET、と犯人の土俵を先に決めてから専用ツールに進みます(6章)。5
- プロセスではなくOS全体のメモリが怪しいときはRAMMapです。6 クラッシュならProcDumpでダンプ採取(「Windowsクラッシュダンプ収集入門」)に切り替えます。
- どのツールもインストール不要で、Sysinternals Liveから直接実行もできます。オフラインの装置PCにも持ち込みやすい一方、シンボルだけは事前準備が要ります(8章)。7
この記事の知識マップ
この記事は、装置連携アプリの「だんだん重くなる」「ファイルが消せない」「ハングした」という三大症状を、Process Explorer・handle.exe・VMMapという状態観察系のSysinternalsツールで切り分ける方法を扱う。ファイル使用中はProcess ExplorerのFind Handle or DLLやhandle.exeで掴んでいるプロセスを特定し、ハンドルリークはHandle Countカラムとhandle -sの定点観測で確認でき、GDI/USERオブジェクトの上限接近も同じ手段で監視できる。ハングはThreadsタブのスレッドスタックで調べるが、読むにはdbghelp.dllとシンボルパスからなるシンボル設定が前提になる。Private Bytesの増加はVMMapでHeap・Managed Heap・Private Dataなどに内訳を割ってから専用ツールに進み、プロセス単位で原因が見えないときはRAMMapでOS全体のメモリ使用状況を見る。
flowchart LR
accTitle: Process Explorer・handle.exe・VMMapによる長期稼働アプリ調査の知識マップ
accDescr: Process Explorer・handle.exe・VMMapがファイル使用中・ハンドルリーク・ハング・メモリ増加といった症状をどう切り分けるか、シンボル設定や関連ツールとの関係を示す図
process_explorer["Process Explorer"]
handle_exe["handle.exe"]
vmmap["VMMap"]
admin_rights["管理者権限"]
handle_leak["ハンドルリーク"]
gdi_user_object_limit["GDI/USERオブジェクトの上限"]
symbol_server["シンボルサーバー"]
windbg["WinDbg"]
memory_mapped_file["ファイルマッピング(メモリマップトファイル)"]
private_bytes_growth["Private Bytesの増加"]
rammap["RAMMap"]
file_lock_in_use["使用中ファイルのロック"]
application_hang["アプリケーションのハング(応答なし)"]
handle_tracing["ハンドルトレーシング(handle tracing)"]
sysinternals_live["Sysinternals Live"]
process_explorer -.->|"前提とする"| admin_rights
handle_exe -->|"前提とする"| admin_rights
vmmap -.->|"前提とする"| admin_rights
handle_leak -->|"で確認できる"| process_explorer
handle_leak -->|"で確認できる"| handle_exe
gdi_user_object_limit -->|"で確認できる"| process_explorer
process_explorer -->|"で構成できる"| symbol_server
symbol_server -->|"利用する"| windbg
memory_mapped_file -->|"で確認できる"| vmmap
private_bytes_growth -->|"で確認できる"| vmmap
vmmap -.->|"より先に行うべき"| rammap
process_explorer -->|"推奨される対応"| file_lock_in_use
handle_exe -->|"推奨される対応"| file_lock_in_use
process_explorer -->|"推奨される対応"| application_hang
windbg -.->|"推奨される対応"| application_hang
handle_leak -->|"で確認できる"| handle_tracing
process_explorer -.->|"利用する"| sysinternals_live
vmmap -.->|"利用する"| sysinternals_live
handle_exe -.->|"利用する"| sysinternals_live
rammap -.->|"利用する"| sysinternals_live
図の実線は常に成り立つ関係、破線は条件付きの関係です(成立条件は詳細ページの各関係の説明に記載)。関係すべての一覧(全20件、根拠・確度つき)と主要概念の定義は知識マップ詳細ページにまとめています。データ: JSON-LD / Turtle
2. Process Explorerの基本 ── 管理者実行・タスクマネージャー置き換え・画面の読み方
Process Explorerはインストール不要で、ZIPを展開して procexp.exe(64bitなら自動的にprocexp64が動きます)を実行するだけです。1 他ユーザーのプロセスやサービスの中身まで見るツールなので、調査のときは必ず「管理者として実行」します。一般権限で起動すると、肝心のプロセスに限って「アクセスが拒否されました」になり、スタックもハンドルも見えません。
調査担当のマシンでは、Options → Replace Task Manager を有効にしておくと、Ctrl+Shift+Escやタスクバーからのタスクマネージャー呼び出しがそのままProcess Explorerに置き換わります。「とっさのときに使い慣れたツールが開く」効果は地味に大きく、当社でも開発機は全台置き換えています(元に戻すのも同じメニューです)。
画面はプロセスツリーが上、選択したプロセスの詳細が下の2ペイン構成です。下ペインはView → Lower Pane ViewでHandlesビュー(開いているハンドルの一覧)とDLLsビュー(読み込んだDLLとメモリマップトファイルの一覧)を切り替えます。1 まず覚えるべきは行の色の意味です(Options → Configure Colorsで確認・変更できます)。
| 色(既定) | 意味 |
|---|---|
| 緑 | 新しく起動したプロセス(既定では約1秒間) |
| 赤 | 終了しつつあるプロセス |
| 薄い青 | 自分と同じユーザーアカウントのプロセス |
| ピンク | サービスをホストするプロセス |
| 紫 | パックされた(圧縮・難読化された)実行ファイルの疑い |
| 濃い灰色 | サスペンドされたプロセス |
「一瞬だけ緑のプロセスが生まれてすぐ赤くなって消える」が延々と繰り返されている、といった異常はツリー表示と色分けだけで見つかります。プロセスの親子関係がツリーで見えるので、「このconhost.exeは誰の子か」「アプリを起動しているのはサービスかタスクスケジューラか」も一目で分かります。
3. 「ファイルが使用中で消せない」── Find Handle or DLLで犯人を特定する
「ログファイルが削除できない」「アップデートしようとしたらexeが使用中」「USBメモリが安全に取り外せない」── この症状の調査は、Find → Find Handle or DLL(Ctrl+F)で終わります。1 操作は次の5ステップです。
- Process Explorerを管理者として実行する(これを忘れると、掴んでいるのがサービスや他ユーザーのプロセスだった場合に見えません)
- メニューの Find > Find Handle or DLL…(ショートカット Ctrl+F)を開く
- 検索文字列の入力欄に、ファイル名かフォルダー名の一部を入力する(例:
report.csv、D:\Data)。完全パスである必要はなく、部分一致で検索されます - Search を押す。その名前を含むハンドルを開いているプロセス、またはそのDLLを読み込んでいるプロセスが一覧に出ます
- 結果の行をクリックすると、上ペインで該当プロセスが選択され、下ペインで該当ハンドルがハイライトされます
見つけた後の対処は「そのプロセスを正しく終了させる」が正攻法です。Process Explorer上でハンドルを右クリックしてClose Handleで強制クローズすることもできますが、後述のhandle -cと同じ理由で本番では推奨しません。
この検索の限界も先に押さえておいてください。ヒットするのは名前を持つオブジェクトだけです。名前なしのイベントやミューテックス、スレッドハンドルは、いくら大量に開かれていても名前で検索できません。「ファイルが消せない」のようにパスという名前がある調査では最強ですが、5章のハンドルリーク調査ではこの方法は使えず、種類別の集計(handle -s)と下ペインのHandlesビューをType順に並べる方法に切り替える必要があります(7章のケーススタディがその実例です)。
DLLsビューは「どの場所のどのバージョンのDLLを実際に読み込んだか」の確認にも使います。「古いDLLを掴んでいた」「配置したはずの修正版が読まれていない」というDLLバージョン問題の確認は、この画面が前編ProcMon記事の5.2節「この環境でだけ起動しない ── DLLの探索を追う」と対になります。あちらは「探索順にNAME NOT FOUNDが並び、どこでSUCCESSするか(あるいは最後まで見つからないか)」を時系列で追う節で、どこを探したかが分かります。こちらのDLLsビューでは結局どこの何を掴んだかが分かります。詳しくは「Process Monitor(ProcMon)実践ガイド」を参照してください。
そもそも「使用中のexe/DLLをどう安全に差し替えるか」は、調査ではなく設計の問題です。同日公開の姉妹記事「使用中のexe/DLLをどう差し替えるか」で、Restart Managerを含む差し替え設計を扱っているので、更新機構を作る側の方はそちらへどうぞ。
4. 「ハングした」── Threadsタブでスレッドのスタックを読む
ウィンドウが白くなって応答なし。強制終了する前に、Threadsタブでスレッドのスタックを見てください。操作は次の4ステップです。
- 上ペインで対象プロセスをダブルクリックする(または右クリック → Properties…)。プロパティのウィンドウが開きます
- Threads タブに切り替える。スレッドごとのCPU使用率・サイクル数・開始アドレスが並びます
- 見るスレッドを選ぶ。CPUを食い続けているスレッドか、開始アドレスがアプリ本体のEXEになっているスレッド(多くの場合これがUIスレッド)が最初の候補です
- Stack ボタンを押す。そのスレッドが今どの関数の呼び出し途中で止まっているかが、呼び出しの新しいほうから順に表示されます
ハングの大半は「UIスレッドが何かを待っている」なので、スタックの上のほうに WaitForSingleObject や EnterCriticalSection、同期的なネットワーク・シリアルI/O待ちが見えれば、それが直接原因です。
ただし、シンボルを設定していないとスタックはアドレスと モジュール名+0x1234 の羅列になり、ほぼ読めません。Options → Configure Symbols で次の2つを設定します。
Dbghelp.dll path:
C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\dbghelp.dll
※ WinDbg(Debugging Tools for Windows)に含まれるものを指定する
Symbols path:
srv*C:\Symbols*https://msdl.microsoft.com/download/symbols
※ C:\Symbols はローカルキャッシュ。2回目以降はここから読まれる
ポイントは2つあります。まず、System32にある既定のdbghelp.dllはシンボルサーバーからのダウンロードに対応していないため、WinDbg付属のものを指す必要があること。そして公式ドキュメントにあるとおり、シンボルサーバーを使う場合は、指定したdbghelp.dllと同じ場所にsymsrv.dllが存在している必要があることです(WinDbgのインストールフォルダーなら両方揃っています)。1 シンボルパスの srv*キャッシュ*サーバーURL という書式と、Microsoftのパブリックシンボルサーバー https://msdl.microsoft.com/download/symbols はWinDbgと共通の仕組みです。3 自社アプリのスタックを読むには自社ビルドのPDBも必要なので、シンボルパスにPDB置き場のフォルダーをセミコロン区切りで足しておきます。
そのdbghelp.dllをどこから持ってくるかが、ここで最初に詰まるところです。上のパスに出てくる C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\ は、Debugging Tools for Windows を入れると作られるフォルダーです。入手経路は3つあります。8
| 入手経路 | 向いている場面 |
|---|---|
| Windows SDKのインストーラーで「Debugging Tools for Windows」だけを選ぶ | 今回の用途にはこれが最短。他の機能のチェックをすべて外して実行すれば、SDK本体を入れずにデバッガーだけが入る8 |
| Windows SDK / WDKの一部として入れる | 開発機で他の用途にもSDKが要る場合 |
WinDbg単体をインストール(winget install Microsoft.WinDbg またはMicrosoft Store) |
WinDbg本体を使いたい場合。ただしパッケージ形式で入るため、上のような固定パスにはなりません9 |
Process Explorerに設定するのはdbghelp.dllのファイルパスなので、インストール後にそのフォルダー内へ dbghelp.dll と symsrv.dll が並んでいることを確認してから指定してください。
.NETアプリの場合、Threadsタブのスタックはネイティブフレーム中心で、マネージドのメソッド名は正確に出ないことがあります。マネージドのハング・デッドロックを本気で追うなら、ハング中にダンプを採ってWinDbg + SOSで見るのが確実です(「WinDbg + SOSでクラッシュダンプを読む」)。Threadsタブは「その場で3分で当たりを付ける」ツール、ダンプは「持ち帰って確定させる」ツールという役割分担です。なおThreadsタブのKill/Suspendボタンは、本番プロセスに対しては押さないでください。観察のつもりが介入になります。
5. 「だんだん重くなる」── ハンドル数・USER/GDIカラムとhandle.exeでリークを監視する
長期稼働で劣化するアプリの定番の原因が、ハンドル・GDI/USERオブジェクト・メモリのリークです。Process Explorerは監視ツールとしても優秀で、View → Select Columns から次のカラムを追加します。
- Process Performanceタブ → Handle Count(カーネルオブジェクトのハンドル数)
- Process Memoryタブ → Private Bytes、Virtual Size、USER Objects、GDI Objects
見るのは絶対値ではなく傾きです。健全なアプリはハンドル数が操作に応じて増減しつつ一定の範囲に収まりますが、リークしていると「操作のたびに増えて、二度と減らない」右肩上がりになります。1時間おき、あるいは朝夕でスクリーンショットを残すだけでも、翌日には傾向が分かります。GDIオブジェクトとUSERオブジェクトにはプロセスごとの上限(既定では1万個、レジストリのGDIProcessHandleQuota等で変更可能)とセッション全体の理論上限65,536個があり、上限に達するとペンやブラシの作成、ウィンドウの作成が失敗し始めます。4 「金曜になると画面の描画が化ける」の正体は、たいていこれです。
GUIを開けない環境やスクリプトでの定点観測には、CLI版のhandle.exeを使います。ハンドルの一覧・検索を行うコマンドラインツールで、管理者権限が必要です。2
:: このファイル/フォルダーを誰が掴んでいるか(名前の部分一致で検索)
handle.exe /accepteula report.csv
handle.exe D:\Data
:: ファイル以外も含む全種類のハンドルを、プロセス名を絞ってダンプ
handle.exe -a -p MyEquipApp
:: 種類別のハンドル数を集計 ── リークの定点観測はこれをログに残す
handle.exe -s -p MyEquipApp.exe >> C:\Logs\handles_%date:~0,4%%date:~5,2%%date:~8,2%.log
3行目の %date:~0,4% について補足します。これは環境変数の部分文字列展開で、バッチファイル内でもコマンドプロンプトに直接打つ場合でも、書き方は同じ % ひとつです(%% に二重化する必要があるのは for の変数と、リテラルの % を書きたいときで、この記法は該当しません)。
ただし%date% の中身はOSの短い日付形式に依存するため、~0,4(先頭4文字=年)といったオフセットが環境によってずれます。日本語版Windowsの既定は yyyy/MM/dd なので上の例で合いますが、装置PCの地域設定が違うと、ファイル名が壊れた形で作られます。環境をまたいで配る定点観測スクリプトなら、日付の生成だけは形式が確定する方法に寄せてください。
:: 環境の日付形式に依存しない書き方(バッチファイルに書く場合)
for /f %%d in ('powershell -NoProfile -Command "Get-Date -Format yyyyMMdd"') do set TODAY=%%d
handle.exe -s -p MyEquipApp.exe >> C:\Logs\handles_%TODAY%.log
for の変数は、バッチファイル内では %%d、コマンドプロンプトに直接打つときは %d と書き分けが必要です。10 上はバッチファイル用なので、そのままコマンドプロンプトに貼ると動きません。直接試すときは %%d を %d にしてください。定点観測はタスクスケジューラから回すことになるので、実運用ではバッチファイル側の書き方を使います。ここが冒頭の %date:~0,4%(どちらでも % ひとつ)と混同しやすい点です。
-s の出力(Event: 1523、File: 88、…という種類別の集計)をタスクスケジューラで1時間ごとに追記しておけば、リモート保守しかできない装置PCでも「どの種類のハンドルが1日にいくつ増えるか」が数字で取れます。種類が分かれば容疑者は一気に絞れます。Eventならイベントの解放漏れ、Fileなら閉じ忘れ、Threadならスレッド終了後のハンドル放置、という具合です。
なお -c オプションで指定ハンドルを強制クローズできますが、公式ドキュメント自身が「ハンドルを閉じるとアプリケーションやシステムが不安定になり得る」と警告しています。2 ロック解除の応急処置としても、本番では基本的に使わない、が当社の運用です。「どの種類がリークしているか」まで分かった後、「どのコードが確保したか」を特定する手順は「産業用カメラ長期稼働クラッシュ調査 - ハンドルリーク編」と「Application Verifierで異常系テストの土台を作る」に続きます。
6. VMMap ── 「Private Bytesが増え続ける」の内訳を割る
ハンドルは横ばいなのにPrivate Bytesだけが増え続ける ── そのときに開くのがVMMapです。プロセスの仮想メモリと物理メモリ(ワーキングセット)を分析するツールで、コミット済み仮想メモリを種類別に分解して見せてくれます。5 起動して対象プロセスを選ぶと、上段に色分けされたサマリー、下段に詳細なメモリマップが出ます。主要な分類はこう読みます。
| 分類 | 中身 | 増え続けるときの典型的な犯人 |
|---|---|---|
| Image | EXE/DLL本体 | プラグインDLLのロードしっぱなし |
| Heap | ネイティブヒープ(new/malloc/HeapAlloc) | C/C++コード、装置SDK、相互運用の解放漏れ |
| Managed Heap | .NETのGCヒープ | マネージドの参照リーク(イベントハンドラー等) |
| Private Data | VirtualAllocによる直接確保など | 画像フレームバッファ、SDK内部バッファ |
| Stack | スレッドのスタック | スレッドの作りっぱなし |
| Shareable / Mapped File | 共有メモリ・マップされたファイル | プロセス間連携のセクション解放漏れ |
操作は次の4ステップです。
- VMMapを管理者として実行する。起動直後にプロセスの選択ダイアログが出るので、対象プロセスを選んで OK を押す
- 上段のサマリー表を見る。行が上の表のType(Image / Heap / Managed Heap / Private Data / Stack …)、列が Size や Committed といったサイズです。この時点の値をメモするか、Fileメニューからエクスポートして控えておくのが肝心です
- 問題の操作(装置の測定1サイクル、画面の開閉など)を決まった回数繰り返す
- F5(Refresh)でスナップショットを更新し、2で控えた値と比べる。増えたTypeの行をクリックすると、下段にその領域の一覧が出ます
使い方の型は「F5でスナップショットを更新しながら、問題の操作を繰り返し、どの分類が増えるかを見る」です。VMMapはスナップショットの比較とタイムライン表示、結果のエクスポートに対応しているので5、「測定100回で Heap が 40MB 増、Managed Heap は横ばい」といった証拠がその場で取れます。ここで土俵が決まれば、あとは専用ツールの出番です。Managed Heapなら.NET側の調査(「PerfViewとdotnet-traceで「遅い」を特定する」)、Heap/Private Dataならネイティブ側の調査(Application Verifierやダンプ解析)に進みます。この切り分けをせずにいきなりGCを疑って数日溶かすのが、.NET+ネイティブSDK構成の装置アプリで最もよくある回り道です。
32bitプロセスでは断片化も観察対象です。Fragmentation Viewでアドレス空間の空きの散らばりが見え、Freeの合計は数百MBあるのに最大の連続空き(Largest)が数十MBしかない、という状態が可視化されます。「メモリは空いているはずなのにOutOfMemory」「大きな画像バッファの確保だけ失敗する」の正体はたいていこれで、対策(64bit化、バッファの再利用設計)の根拠資料になります。
プロセス単位ではなくOS全体のメモリが怪しい(どのプロセスも太っていないのにメモリ不足になる)ときは、物理メモリ全体の使途をタブ別に見せてくれるRAMMapに切り替えます。ファイルキャッシュ・ドライバー・カーネルの使用量まで見えるので、「アプリ以外の犯人」はこちらで探します。6
7. ケーススタディ ── 「毎週金曜に重くなる装置PC」を切り分ける
冒頭の装置PCを、ここまでの道具で実際に切り分けるとこうなります。月曜朝に再起動する運用なので、金曜は「稼働5日目」。つまり時間に比例して増える何かがある、が初期仮説です。
- 水曜あたりにProcess Explorerを置き、カラムを仕込む。Handle Count・USER Objects・GDI Objects・Private Bytesを追加し、対象プロセスの値を記録します。あわせて
handle -s -p 対象アプリ.exeの毎時ログをタスクスケジューラに登録します(5章)。 - 翌日、傾きを見る。このケースではHandle Countが1日で約2万増、種類別ログではEventハンドルが単調増加していました。Private BytesとGDI/USERはほぼ横ばい。この時点で「カーネルオブジェクト(イベント)の解放漏れ」に絞れます。
- Handlesビューで現物を見る。下ペインのHandlesビューをType順に並べると、名前なしのEventが数万個。名前がないのでFind Handleでは追えませんが、種類と増加ペースが分かれば十分です。測定周期(このケースでは装置のポーリング1回につき2個)との対応から、通信ライブラリの待機イベントの解放漏れという仮説が立ち、コードレビューで確定しました。確保箇所をツールで特定するなら、ここからApplication Verifierや!htraceの出番です。
- もしPrivate Bytesが増えていたら、3の代わりにVMMapで内訳を割り(6章)、Heapならネイティブ、Managed Heapなら.NETの調査に分岐します。
- もしどの数字も横ばいでハングだけするなら、Threadsタブでスタックを見て(4章)、待ち先を特定します。
ポイントは、修正前に「グラフの傾き」という証拠を確保しておくことです。修正後に同じ測定をして傾きがゼロになったことを示せば、「直りました」を数字で報告できます。再現待ちが数日単位の長期稼働問題では、この測定の仕込みこそが調査の本体です。
8. 本番PCへの持ち込みと運用の注意 ── Sysinternals Live・EULA・オフラインのシンボル
Sysinternalsツールはいずれもインストール不要のスタンドアロン実行ファイルで、ZIPをUSBメモリで持ち込めばそのまま動きます。初回起動時にEULAへの同意ダイアログが出るので、無人実行やスクリプトからは /accepteula スイッチで同意を明示します。ネットワークが使える環境なら、Sysinternals Liveというサービスで、ダウンロードせずに https://live.sysinternals.com/procexp.exe のようなURLや \\live.sysinternals.com\tools\<ツール名> のUNCパスから直接実行できます。7 「今すぐこの1台で見たい」ときの最短ルートです。
オフラインの装置PCで引っかかるのはシンボルです(4章のスタック表示が使えない)。対処は2つで、(1)インターネットに接続できる開発機で同じOSビルドに対してシンボルを解決させ、ローカルキャッシュ(C:\Symbols)ごと装置PCにコピーして、シンボルパスをローカルフォルダーだけにする。(2)スタック解析はあきらめて、現地ではダンプ採取(ProcDump)と数値記録に徹し、開発機に持ち帰って解析する。確実なのは(2)で、ダンプの採り方は「Windowsクラッシュダンプ収集入門」にまとめてあります。
最後に運用面です。Process ExplorerやVMMapによる観察は低負荷・低リスクですが、プロセスのKill/Suspend、スレッドのKill、Close Handle、handle -c といった介入の操作は本番では原則禁止にしてください。前編のProcMonと同じく、実施は通常の変更作業と同じ承認プロセスに乗せ、「いつ・誰が・何を観察し、何は操作しないか」を決めてから臨むのが安全です。
9. 実務の定石(判断表)
| 症状 | 使うツール | 見る場所 |
|---|---|---|
| だんだん重くなる・数日で不安定になる | Process Explorer | Handle Count / USER・GDI Objects / Private Bytesカラムの傾き(5章) |
| ファイルが削除・差し替えできない | Process Explorer / handle.exe | Find Handle or DLL(Ctrl+F)でパス検索 → 掴んでいるプロセス(3章) |
| ハング・応答なし | Process Explorer | プロパティ → Threadsタブ → スレッドスタック(要シンボル、4章) |
| Private Bytesが増え続ける | VMMap | Heap / Managed Heap / Private Dataのどれが増えるか(6章) |
| どのプロセスも太っていないのにメモリ不足 | RAMMap | Use Counts・File Summaryで物理メモリの使途6 |
| クラッシュする・例外で落ちる | ProcDump + WinDbg | ダンプ収集 → WinDbg + SOS |
| CPUをどこで使っているか・.NETのGC | PerfView / dotnet-trace | 「遅い」の定量調査 |
| 操作の時系列(どのファイルを・いつ・どの順で) | Process Monitor | 前編記事 |
10. まとめ
- ProcMonが「操作の履歴」を記録するのに対し、Process Explorer / Handle / VMMapは「今この瞬間の状態」を見るツールです。長期稼働アプリの調査では両方が必要になります。
- 「ファイルが消せない」はFind Handle or DLL(Ctrl+F)で数秒、「ハングした」はThreadsタブのスタックで当たりを付けます。スタックを読むにはdbghelp.dll(symsrv.dll同梱の場所)とシンボルパスの設定が前提で、そのdbghelp.dllはDebugging Tools for Windowsを入れて手に入れます。
- 名前で検索できるのは、名前を持つオブジェクトだけです。ファイルのように名前があるものには最強ですが、名前なしのイベントやミューテックスは検索でヒットしません。ハンドルリークの調査を「Ctrl+Fで犯人を探す」から始めると空振りするので、種類別の集計(
handle -s)とHandlesビューのType順並べ替えに切り替えてください。 - 「だんだん重くなる」はHandle Count・USER/GDI Objects・Private Bytesのカラムを追加して傾きを見ます。handle -s の定期ログなら、GUIを開けない装置PCでも数字が取れます。
- Private Bytesの増加はVMMapでHeap(ネイティブ)/ Managed Heap(.NET)/ Private Dataに切り分けてから、専用ツールに進みます。32bitプロセスなら断片化も疑います。
- handle -cやClose Handleによる強制クローズは、公式が警告するとおり不安定化のもとです。本番では観察に徹し、介入はしない・するなら承認を通す、を徹底してください。
- ツールはスタンドアロンで持ち込みやすく、Sysinternals Liveなら直接実行もできます。オフライン環境ではシンボルの事前キャッシュか、ダンプの持ち帰りで補います。
関連記事
- Process Monitor(ProcMon)実践ガイド ── 「設定が読まれない」「ACCESS DENIED」を10分で特定する
- 使用中のexe/DLLをどう差し替えるか
- 産業用カメラ長期稼働クラッシュ調査 - ハンドルリーク編
- Application Verifierで異常系テストの土台を作る
- Windowsクラッシュダンプ収集入門 - WER/ProcDump/WinDbg
- WinDbg + SOSでクラッシュダンプを読む
- PerfViewとdotnet-traceで「遅い」を特定する
関連する相談領域
合同会社小村ソフトでは、長期稼働で劣化する業務アプリ・装置連携アプリのリーク調査、ハング・「ファイルが使用中」といった現場障害の原因解析、証拠採取手順の整備と再発防止の改修を扱っています。
参考リンク
-
Microsoft Learn, Process Explorer - Sysinternals. Process Explorerがプロセスの開いているハンドルと読み込んだDLL・メモリマップトファイルを2ペイン(handleモード/DLLモード)で表示すること、特定のハンドルやDLLを持つプロセスを検索できること、DLLバージョン問題やハンドルリークの追跡に有用であること、シンボルサーバー利用時はdbghelp.dllと同じ場所にsymsrv.dllが必要であること、Sysinternals Liveから直接実行できること、動作要件がクライアントはWindows 11以降・サーバーはWindows Server 2016以降であることについて。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7
-
Microsoft Learn, Handle - Sysinternals. handle.exeが開いているハンドルの情報を表示するコマンドラインツールで管理者権限が必要なこと、名前の部分一致検索、-aで全種類のハンドルを対象にすること、-pによるプロセスの絞り込み、-sによる種類別ハンドル数の集計、-cでハンドルを閉じられるが「アプリケーションやシステムの不安定化を招き得る」と警告されていることについて。 ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, Microsoft Public Symbol Server. Microsoftパブリックシンボルサーバーのシンボルパスがsrvローカルキャッシュhttps://msdl.microsoft.com/download/symbolsの書式で指定できること、ダウンストリームストア(ローカルキャッシュ)には一度アクセスしたシンボルだけが保存され、2回目以降はローカルから読まれることについて。 ↩ ↩2
-
Microsoft Learn, GDI Objects. GDIハンドルにセッションあたり理論上限65,536個があること、プロセスごとの既定の上限が存在しレジストリのGDIProcessHandleQuota(256〜65,536の範囲)で変更できることについて。 ↩ ↩2
-
Microsoft Learn, VMMap - Sysinternals. VMMapがプロセスの仮想・物理メモリ分析ツールであり、コミット済み仮想メモリの種類別の内訳と各種類に割り当てられた物理メモリ(ワーキングセット)を表示すること、フィルタと更新(スナップショット)機能、データのエクスポートとコマンドラインオプションによるスクリプト化に対応することについて。 ↩ ↩2 ↩3
-
Microsoft Learn, RAMMap - Sysinternals. RAMMapがOS全体の物理メモリ使用状況を分析するツールで、Use Counts(種類別集計)、Processes(プロセスのワーキングセット)、File Summary(RAM上のファイルデータ)などのタブで使途を表示すること、スナップショットの保存・読み込みに対応することについて。 ↩ ↩2 ↩3
-
Microsoft Learn, Sysinternals. Sysinternals Liveがツールをダウンロードせずに実行できるサービスであり、live.sysinternals.com/<ツール名>のURLまたは\\live.sysinternals.com\tools\<ツール名>のパスで直接実行できること、live.sysinternals.comでツール一覧を参照できることについて。ツール名>ツール名> ↩ ↩2
-
Microsoft Learn, Debugging Tools for Windows SDK and WDK. Debugging Tools for WindowsがWindows SDKおよびWDKに含まれること、Windows SDKのインストーラーを起動して機能一覧から「Debugging Tools for Windows」だけを選び他をすべて外すことでデバッガーのみを単体導入できること、インストーラーの入手先がWindows SDKであることについて。 ↩ ↩2
-
Microsoft Learn, Install WinDbg. WinDbgがクラッシュダンプの解析やユーザーモード・カーネルモードのデバッグに使えること、直接インストーラーを実行する方法・Microsoft Store経由・
winget install Microsoft.WinDbgによる導入方法があること、旧来のWinDbg(classic)はDebugging Tools for Windowsに含まれることについて。 ↩ -
Microsoft Learn, for. バッチファイル内では
%%variable、コマンドプロンプトで直接実行する場合は%variableと書くこと、およびfor /fがコマンドの出力を解析できることについて。 ↩
関連する記事
同じタグを共有する最新の記事です。さらに近い話題で知識を深められます。
Get-WinEventでイベントログを実務的に調べる ── 絞り込みの速さが調査時間を決める
Windowsのイベントログ調査をPowerShellで効率化する方法をまとめます。Where-Objectで絞ると遅い理由、FilterHashtableとXPathの使い分け、再起動やログオン・アプリ異常終了の調査レシピ、複数台からの収集までを解説します。
Windowsの時刻同期(w32time)と業務システム ── 「ログのタイムスタンプが合わない」を仕組みから解決する
装置とPCでログの時刻がずれる原因をWindows Timeサービス(w32time)の仕組みから解説。ドメイン階層とワークグループの既定動作、w32tmコマンドでの診断、高精度時刻や仮想マシンの注意点、StopwatchとUTC併用のログ設計まで整理します。
マルチスレッドの実務ベストプラクティス C言語編 ── Win32 APIの流儀で安全に書く
C言語×Win32のマルチスレッドは、_beginthreadexでのスレッド作成、SRWロックと条件変数、Interlocked、停止イベント+WaitForMultipleObjectsの停止設計が定石。TerminateThreadの危険とDllMainの制約まで整理...
マルチスレッドの実務ベストプラクティス C++編 ── RAIIとjthreadで事故を構造から消す
C++のマルチスレッドはデータ競合が未定義動作になる世界。std::threadのデストラクタの罠、jthreadとstop_tokenによる停止設計、scoped_lockのデッドロック回避、atomicの正しい位置づけ、Win32同期APIとの使い分けまで整理します。
マルチスレッドの実務ベストプラクティス .NET編 ── スレッドを増やす前に決めておくこと
「スレッドを立てたら、たまに落ちる・固まる」を防ぐ設計の定石を.NET/C#向けに整理。スレッドを自分で作らずTaskに乗る、共有可変状態を減らす、ロックの規律、CancellationTokenでの停止設計、UIスレッドの扱いまで解説します。
関連トピック
このテーマと近いトピックページです。記事を起点に、関連するサービスや他の記事へ進めます。
Windows技術トピック
Windows 開発、不具合調査、既存資産活用の技術トピックをまとめた入口です。
不具合調査 / 長期稼働テーマ
再現しにくい不具合、通信停止、長期稼働障害、失敗パス検証を整理するトピックです。
このテーマがつながるサービス
この記事は次のサービスページにつながります。近い入口からご覧ください。
Windowsアプリ開発
業務アプリ、装置連携、通信ツールなどの Windows ソフト開発を支援します。
不具合調査・原因解析
再現しにくい障害、長期稼働後の不具合、通信停止などの調査を支援します。
よくある質問
この記事のテーマについて、相談時によくある質問をまとめています。
- タスクマネージャーがあるのに、Process Explorerをわざわざ使う理由は何ですか?
- タスクマネージャーでは「どのプロセスがどのファイルやオブジェクトを開いているか」「各スレッドが今どのコードで止まっているか」が見えないからです。Process Explorerはプロセスごとのハンドル・DLLの一覧、名前でのハンドル検索(Ctrl+F)、スレッドスタックの表示までできるので、「ファイルが消せない」「ハングした」の調査がタスクマネージャーとは別次元になります。ハンドル数やUSER/GDIオブジェクト数のカラムを追加すれば、リークの監視ツールとしても使えます。OptionsメニューのReplace Task Managerを有効にすると、タスクマネージャーの呼び出しがProcess Explorerに置き換わるので、調査担当のマシンでは置き換えてしまうのが実務的です。
- handle.exeの-cオプションでハンドルを閉じれば、ファイルのロックをすぐ解除できますか?
- 技術的には可能ですが、本番環境では基本的に使わないでください。公式ドキュメント自体が「ハンドルを閉じるとアプリケーションやシステムが不安定になり得る」と警告しています。プロセスの内部状態を無視して外からハンドルを奪うため、そのプロセスが後で同じハンドルを使った瞬間に、別のオブジェクトを指していて誤動作する(ハンドル値の再利用)という最悪の事故もあり得ます。正攻法は、掴んでいるプロセスを特定して正しく終了させることです。使用中のexeやDLLを差し替えたいという文脈なら、Restart Managerなどの設計的な解決を検討してください。
- Private Bytesが増え続けています。最初に何をすべきですか?
- 対象プロセスをVMMapで開き、増えているのがどの分類かを見るのが最初の一手です。Heapが増えるならmalloc/new/HeapAllocによるネイティブリークの疑いが濃く、装置ベンダーのSDKやC++/CLI・相互運用のコードが容疑者になります。Managed Heapが増えるなら.NETのマネージドヒープの問題なので、GCが回収できない参照の調査(イベントハンドラーの解除漏れなど)に進みます。Private Data(VirtualAlloc直接確保)が増える場合は、画像バッファのような大きなブロックを確保するコードやSDKを疑います。切り分けができてから、それぞれ専用のツール(WinDbg、PerfViewなど)に進むと調査が迷子になりません。
- オフラインの装置PCでは、Threadsタブのスタックがアドレスの羅列になって読めません。どうすればよいですか?
- シンボル(PDB)をダウンロードできないためで、対処は「シンボルを持ち込む」か「ダンプを持ち帰る」の2択です。持ち込む場合は、インターネットに接続できる開発機で同じOSビルド・同じアプリ構成に対してシンボルを一度解決させ、ローカルキャッシュフォルダー(例: C:\Symbols)ごと装置PCへコピーし、シンボルパスをそのローカルフォルダーだけにします。持ち帰る場合は、ハング中にダンプを採取して開発機のWinDbgで解析するほうが確実で、マネージドアプリのスタックを正確に読みたいときもこちらが向いています。自社アプリのPDBはビルドごとに保管しておくことが大前提です。
- Process ExplorerやVMMapを本番PC・装置PCに入れても問題ありませんか?
- いずれもインストール不要のスタンドアロン実行ファイルで、レジストリ登録やサービス常駐なしに動くため、持ち込みのハードルは低いツールです。初回起動時にEULAへの同意が必要で、スクリプトから使う場合は/accepteulaスイッチで同意を明示できます。ネットワークが使える環境なら、Sysinternals Live(https://live.sysinternals.com)からダウンロードせずに直接実行することもできます。ただし観察は低リスクでも、プロセスのKill/Suspend、スレッドのKill、ハンドルの強制クローズといった「介入」の操作は事故に直結するので、本番では観察と記録に徹し、通常の作業承認プロセスに乗せて実施してください。