C# Tips | ログ・例外・診断:メモリ使用量取得

C# C#
スポンサーリンク

はじめに:メモリ使用量取得は「どれくらい“盛っている”かを数字で知るためのもの」

パフォーマンスの話をしていると、必ず出てくるのが「メモリ食いすぎてない?」という心配です。
でも、感覚だけで「重そう」と言っていても、何も改善できません。
そこで必要になるのが メモリ使用量取得、つまり「今このプロセスがどれくらいメモリを使っているか」を数字で知ることです。

ここでは、初心者向けに

GC.GetTotalMemory で「マネージドヒープの使用量」を見る
Process クラスで「プロセス全体のメモリ」を見る
ログと組み合わせた「メモリ使用量計測ユーティリティ」
どんな場面で使うと意味があるか

を、例題付きでかみ砕いて説明します。


GC.GetTotalMemory で「.NET が管理しているメモリ量」を見る

マネージドヒープのサイズをざっくり知る

C#(.NET)の世界では、new したオブジェクトは「マネージドヒープ」という領域に置かれ、
ガベージコレクタ(GC)が不要になったタイミングで片付けてくれます。

この「マネージドヒープが今どれくらいのサイズか」を教えてくれるのが GC.GetTotalMemory です。

public static void ShowManagedMemory()
{
    // 第1引数は「すぐに GC を走らせてから測るかどうか」
    // true にすると、できるだけ最新の状態に近づけてから測る
    long bytes = GC.GetTotalMemory(forceFullCollection: true);

    Console.WriteLine($"マネージドヒープ使用量: {bytes} バイト");
    Console.WriteLine($"約 {bytes / 1024.0 / 1024.0:F2} MB");
}
C#

forceFullCollection: true にすると、GC が一度走って「いらないオブジェクト」を片付けてから測ってくれます。
そのため、「今本当に必要なオブジェクトがどれくらいメモリを使っているか」に近い値になります。

ここでの重要ポイントは、「これは“マネージドヒープのサイズ”であって、“プロセス全体のメモリ”ではない」ということです。
ネイティブライブラリや、JIT コンパイルされたコード、スタックなどは含まれていません。


Process クラスで「プロセス全体のメモリ」を見る

実際に OS から見える「このアプリのメモリ使用量」

「タスクマネージャーに出ているような数字」を知りたい場合は、System.Diagnostics.Process を使います。

using System.Diagnostics;

public static void ShowProcessMemory()
{
    using var process = Process.GetCurrentProcess();

    long workingSet = process.WorkingSet64;          // 実メモリ(物理メモリ上に載っている量)
    long privateBytes = process.PrivateMemorySize64; // このプロセス専用の仮想メモリ

    Console.WriteLine($"WorkingSet: {workingSet} バイト(約 {workingSet / 1024.0 / 1024.0:F2} MB)");
    Console.WriteLine($"PrivateBytes: {privateBytes} バイト(約 {privateBytes / 1024.0 / 1024.0:F2} MB)");
}
C#

ざっくり言うと、

WorkingSet
今このプロセスが物理メモリ上に展開している量

PrivateBytes
このプロセス専用に確保している仮想メモリ量

というイメージです。

ここでの重要ポイントは、「プロセス全体のメモリは、.NET 以外の要素も含んでいる」ということです。
ネイティブライブラリ、JIT コード、スタック、OS の管理領域なども含まれるため、
「GC.GetTotalMemory より大きい数字になる」のが普通です。


メモリ使用量をログに残すユーティリティを作る

「どのタイミングでどれくらい使っているか」を後から追えるようにする

コンソールに出すだけだと、その場限りで終わってしまいます。
業務システムでは、「特定の処理の前後でメモリ使用量をログに残しておく」と、
後から「この処理のときだけ急にメモリが増えている」といった分析ができるようになります。

ILogger と組み合わせたユーティリティの例です。

using System.Diagnostics;
using Microsoft.Extensions.Logging;

public static class MemoryLogger
{
    public static void LogCurrentMemory(ILogger logger, string context)
    {
        using var process = Process.GetCurrentProcess();

        long managedBytes = GC.GetTotalMemory(forceFullCollection: false);
        long workingSet = process.WorkingSet64;
        long privateBytes = process.PrivateMemorySize64;

        logger.LogInformation(
            "MemoryUsage Context={Context} Managed={ManagedMB:F2}MB WorkingSet={WorkingSetMB:F2}MB PrivateBytes={PrivateMB:F2}MB",
            context,
            managedBytes / 1024.0 / 1024.0,
            workingSet / 1024.0 / 1024.0,
            privateBytes / 1024.0 / 1024.0);
    }
}
C#

使い方の例です。

MemoryLogger.LogCurrentMemory(_logger, "ユーザー一覧取得前");

// ユーザー一覧取得処理
var users = await LoadUsersAsync();

MemoryLogger.LogCurrentMemory(_logger, "ユーザー一覧取得後");
C#

ログには、例えばこんな行が残ります。

「MemoryUsage Context=ユーザー一覧取得前 Managed=50.23MB WorkingSet=120.45MB PrivateBytes=130.10MB」
「MemoryUsage Context=ユーザー一覧取得後 Managed=80.12MB WorkingSet=150.78MB PrivateBytes=160.30MB」

ここでの重要ポイントは、「メモリ使用量を“文脈(どの処理か)付きで”残す」ことです。
単に数字だけを残しても意味がなく、「どの処理の前後でどう変化したか」が分かるようにしておくことが大事です。


実務での使いどころ:メモリリークや“重い画面”の特定

「どこで増えて、どこで減っていないか」を見る

メモリ使用量取得が本当に役に立つのは、次のような場面です。

特定の画面を何度も開くと、だんだん重くなっていく
長時間動かしていると、メモリ使用量が右肩上がりで増え続ける
バッチ処理の途中で急にメモリが跳ね上がる

こういうときに、「処理の前後で MemoryLogger を呼んでおく」と、
「この画面を開くたびに Managed メモリが 50MB ずつ増えている」
「このバッチのこのステップでだけ WorkingSet が急増している」
といったことが分かるようになります。

そこから、

使い終わったオブジェクトを保持し続けていないか
キャッシュが増え続けていないか
画像や大きな配列を解放していないか

といった観点でコードを見直していくことができます。

ここでの重要ポイントは、「メモリ使用量取得は“原因そのもの”を教えてくれるわけではなく、“怪しい場所を絞り込むためのヒント”になる」ということです。
数字を見て、「この辺が怪しい」と当たりを付けてから、コードやプロファイラで深掘りしていく流れになります。


まとめ:メモリ使用量取得は“重さを数字で見える化する”ためのユーティリティ

メモリ使用量取得の本質を一言で言うと、

「このアプリが今どれくらいメモリを使っているかを、
感覚ではなく、バイト数・MB という数字で把握し、
どこで増えてどこで減っていないかを追えるようにする」

ことです。

押さえておきたいポイントを整理すると、

GC.GetTotalMemory で「マネージドヒープの使用量」をざっくり見られる。
Process.GetCurrentProcess() から WorkingSet64PrivateMemorySize64 を取ると、「プロセス全体のメモリ」を見られる。
ILogger と組み合わせた MemoryLogger を作って、「処理の前後でメモリ使用量をログに残す」と、後から分析しやすい。
メモリ使用量取得は、メモリリークや“重い画面・重い処理”の特定に役立つが、原因そのものではなく“ヒント”として使う。

ここまでイメージできていれば、「なんかメモリ食ってそう」から一歩進んで、
“数字で語れるメモリの健康診断”に踏み出せるようになります。

タイトルとURLをコピーしました