Windowsのタスク マネージャーに表示される「ハードウェア予約済み(Hardware reserved)」は、アプリから“そのまま”取得できるAPIが用意されていません。しかし「搭載RAM」と「OSが利用可能な物理メモリ」の差分として扱うと、C#から近い値を再現できます。ここではWin32 APIとWMIの2通りで、算出方法とハマりどころをまとめます。
タスク マネージャーの「ハードウェア予約済み」とは
タスク マネージャーの[パフォーマンス]→[メモリ]に出てくる「ハードウェア予約済み」は、OSが物理メモリとして使えない領域(または起動時点で予約されている領域)を大まかに示す表示です。代表例は、内蔵GPU(iGPU)の共有メモリ(UMAフレームバッファ)、BIOS/UEFIやファームウェアが確保する領域、PCI/PCIeデバイスのMMIO(メモリマップドI/O)領域、各種の予約領域などです。
重要なのは、これは「空きメモリ」ではなく、そもそもOSの管理対象に入らない(または入れにくい)領域が含まれるという点です。そのため、プロセスのメモリ使用量を減らしても減りませんし、GCやキャッシュ解放では改善しません。値が大きいときは、設定やハードウェア構成の影響を疑うのが近道です。
| 表示/用語 | 意味(ざっくり) | よく混同するポイント |
|---|---|---|
| 搭載メモリ(Installed RAM) | 物理的に装着されているRAM容量 | OSから見えない領域も含み得る |
| OSが利用可能な物理メモリ(Total Phys/Visible) | OSが「物理メモリ」として管理できる総量 | これがタスク マネージャーの「使用中/利用可能」計算の母数になりやすい |
| ハードウェア予約済み(Hardware reserved) | 搭載量とOS可視量の差分として表現されることが多い | 環境により“差分”の定義が完全一致しないことがある |
C#から「Hardware reserved」を直接取れない理由
タスク マネージャーは、複数の情報源(カーネルが持つメモリマップ、ファームウェア予約、ドライバの予約、場合によっては補正/丸め)を統合して表示します。一方で、開発者向けに「Hardware reserved」という単一の数値だけを返す公式APIは一般的ではありません。
そこで実用的なのが、「搭載量」と「OSが使える物理メモリ総量」の差分を取り、Hardware reserved相当として扱う方法です。完全一致を保証するものではありませんが、監視・診断・レポート用途では十分に役立ちます。
近い値を算出する基本方針
計算はシンプルです。ポイントは「搭載メモリ」をどのAPIで取るか、そして「OSが使える物理メモリ」をどの値にするかです。
| 要素 | 意味 | 候補 | 単位 |
|---|---|---|---|
| Installed | 物理的に搭載されている総メモリ | GetPhysicallyInstalledSystemMemory / Win32_ComputerSystem.TotalPhysicalMemory | KB or Bytes |
| OS Visible | OSが利用可能な物理メモリ総量 | GlobalMemoryStatusEx.ullTotalPhys / Win32_OperatingSystem.TotalVisibleMemorySize | Bytes or KB |
| Hardware Reserved | 差分(Installed − OS Visible) | 算出 | Bytes(任意でMB/GB表示) |
この考え方に沿って、C#では次の2パターンが実装しやすいです。
- 方法A(推奨):Win32 API(P/Invoke)で「搭載」と「OS可視」を取り、差分を算出
- 方法B:WMI(System.Management)で「総物理」と「可視物理」を取り、差分を算出
方法A(推奨):Win32 APIで算出する
タスク マネージャーの表示に寄せたいなら、まず試したいのがWin32 APIの組み合わせです。特にGetPhysicallyInstalledSystemMemoryは「搭載量(Installed)」に近い値が取りやすく、差分計算が安定します。
使用するAPIと役割
| API | 取得できるもの | 返却単位 | 用途 |
|---|---|---|---|
| GetPhysicallyInstalledSystemMemory | 搭載されている物理メモリ量 | KB | Installed RAM相当 |
| GlobalMemoryStatusEx | メモリ統計(物理/仮想/ページファイルなど) | Bytes | ullTotalPhysをOS可視量として使用 |
サンプルコード(Win32 APIでHardware reserved相当を算出)
以下は、搭載量(KB)とOS可視量(Bytes)を取り、差分をBytesで返す実装例です。タスク マネージャーと同様にMB/GB表記にしたい場合は、最後に単位変換して表示します。
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
public static class HardwareReservedMemory
{
public readonly struct Result
{
public Result(ulong installedBytes, ulong osVisibleBytes, ulong reservedBytes)
{
InstalledBytes = installedBytes;
OsVisibleBytes = osVisibleBytes;
ReservedBytes = reservedBytes;
}
public ulong InstalledBytes { get; }
public ulong OsVisibleBytes { get; }
public ulong ReservedBytes { get; }
}
// 搭載メモリ(Installed RAM)をKBで取得
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool GetPhysicallyInstalledSystemMemory(out ulong totalMemoryInKilobytes);
// OSが利用可能な物理メモリ(Total Phys)を含む統計を取得
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool GlobalMemoryStatusEx(ref MEMORYSTATUSEX lpBuffer);
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
private struct MEMORYSTATUSEX
{
public uint dwLength;
public uint dwMemoryLoad;
public ulong ullTotalPhys;
public ulong ullAvailPhys;
public ulong ullTotalPageFile;
public ulong ullAvailPageFile;
public ulong ullTotalVirtual;
public ulong ullAvailVirtual;
public ulong ullAvailExtendedVirtual;
}
public static Result Get()
{
if (!GetPhysicallyInstalledSystemMemory(out ulong installedKb) || installedKb == 0)
{
throw new Win32Exception(Marshal.GetLastWin32Error(),
"GetPhysicallyInstalledSystemMemory failed.");
}
var mem = new MEMORYSTATUSEX
{
dwLength = (uint)Marshal.SizeOf(typeof(MEMORYSTATUSEX))
};
if (!GlobalMemoryStatusEx(ref mem))
{
throw new Win32Exception(Marshal.GetLastWin32Error(),
"GlobalMemoryStatusEx failed.");
}
ulong installedBytes = installedKb * 1024UL;
ulong osVisibleBytes = mem.ullTotalPhys;
// 差分が負になる環境(丸め/実装差)に備えて0下限にする
ulong reservedBytes = installedBytes > osVisibleBytes
? installedBytes - osVisibleBytes
: 0UL;
return new Result(installedBytes, osVisibleBytes, reservedBytes);
}
public static string FormatBytes(ulong bytes)
{
const double KiB = 1024d;
const double MiB = KiB * 1024d;
const double GiB = MiB * 1024d;
if (bytes >= (ulong)GiB) return (bytes / GiB).ToString("0.##") + " GB";
if (bytes >= (ulong)MiB) return (bytes / MiB).ToString("0.##") + " MB";
if (bytes >= (ulong)KiB) return (bytes / KiB).ToString("0.##") + " KB";
return bytes + " B";
}
}
// 使い方例
// var r = HardwareReservedMemory.Get();
// Console.WriteLine("Installed : " + HardwareReservedMemory.FormatBytes(r.InstalledBytes));
// Console.WriteLine("OS Visible : " + HardwareReservedMemory.FormatBytes(r.OsVisibleBytes));
// Console.WriteLine("Reserved : " + HardwareReservedMemory.FormatBytes(r.ReservedBytes));
計算結果をタスク マネージャーに寄せるコツ
- x64で実行する:32bitプロセスでもAPI自体は呼べますが、周辺の扱い(型、丸め、外部ライブラリ)で不利になりやすいので、基本はx64を推奨します。
- 単位変換を揃える:タスク マネージャーの表記はGB/MBと表示されますが、内部的には2進(1024系)で丸めていることがあります。比較するときはMB(MiB)単位に落として比べると差が見えやすいです。
- 差分が小さくズレるのは正常範囲:数十MB程度の差は、予約領域の扱い方や表示の丸めで起きます。監視では「桁が違うズレ」かどうかに注目すると判断が早いです。
| 比較のしかた | おすすめ | 理由 |
|---|---|---|
| そのままBytesで比較 | △ | 表示の丸め差が目立ちやすい |
| MB(1024×1024)に変換して比較 | ◎ | タスク マネージャーの見た目と近い粒度 |
| GB(1024×1024×1024)に変換して比較 | ○ | 大きな差分の有無を素早く確認できる |
方法B:WMI(System.Management)で算出する
Win32 APIが使いにくい(ポリシー上P/Invokeを避けたい、既存資産がWMI中心、リモートの情報も取りたい)場合は、WMIでの算出が現実的です。WMIは値の取り回しが楽な反面、速度・依存関係・例外の出方が異なるため、用途に合わせて選びます。
利用するWMIクラスとプロパティ
| WMIクラス | プロパティ | 意味 | 単位 |
|---|---|---|---|
| Win32_ComputerSystem | TotalPhysicalMemory | 総物理メモリ(システム全体) | Bytes |
| Win32_OperatingSystem | TotalVisibleMemorySize | OSから見える物理メモリ総量 | KB |
サンプルコード(WMIでHardware reserved相当を算出)
.NET Frameworkでは標準で利用できることが多く、.NET(Core/5+/6+/8+)系ではWindows向けにSystem.Managementを参照する構成が一般的です。実行環境がWindowsであることを前提にしてください。
using System;
using System.Management;
public static class HardwareReservedMemoryWmi
{
public readonly struct Result
{
public Result(ulong totalPhysicalBytes, ulong osVisibleBytes, ulong reservedBytes)
{
TotalPhysicalBytes = totalPhysicalBytes;
OsVisibleBytes = osVisibleBytes;
ReservedBytes = reservedBytes;
}
public ulong TotalPhysicalBytes { get; }
public ulong OsVisibleBytes { get; }
public ulong ReservedBytes { get; }
}
public static Result Get()
{
ulong totalPhysicalBytes = 0;
ulong totalVisibleKb = 0;
using (var cs = new ManagementObjectSearcher(
"SELECT TotalPhysicalMemory FROM Win32_ComputerSystem"))
{
foreach (ManagementObject mo in cs.Get())
{
totalPhysicalBytes = (ulong)(mo["TotalPhysicalMemory"] ?? 0UL);
break;
}
}
using (var os = new ManagementObjectSearcher(
"SELECT TotalVisibleMemorySize FROM Win32_OperatingSystem"))
{
foreach (ManagementObject mo in os.Get())
{
totalVisibleKb = (ulong)(mo["TotalVisibleMemorySize"] ?? 0UL);
break;
}
}
ulong osVisibleBytes = totalVisibleKb * 1024UL;
ulong reservedBytes = totalPhysicalBytes > osVisibleBytes
? totalPhysicalBytes - osVisibleBytes
: 0UL;
return new Result(totalPhysicalBytes, osVisibleBytes, reservedBytes);
}
}
WMIで算出するときの注意点
- 取得が遅いことがある:WMIはCOMやサービス経由で情報を集めるため、短周期ポーリングには不向きなことがあります。監視用途なら取得間隔を長めにする、キャッシュする、必要なときだけ取得するなどの工夫が有効です。
- 例外処理とリソース解放:WMIは環境差が出やすいので、タイムアウトや例外を想定し、usingで確実に破棄するのが安全です。
- 「搭載量」と完全一致しないことがある:Win32_ComputerSystem.TotalPhysicalMemoryが、BIOS表示のInstalled RAMと微妙にズレる機種もあります。タスク マネージャーに寄せたいなら方法Aを先に試すのが無難です。
方法Aと方法Bの比較
| 観点 | 方法A(Win32 API) | 方法B(WMI) |
|---|---|---|
| タスク マネージャーへの近さ | ◎(一致しやすい) | ○(環境差が出ることがある) |
| 実装の手軽さ | ○(P/Invokeが必要) | ◎(プロパティ取得で完結) |
| 実行速度 | ◎(高速) | △(遅いことがある) |
| 依存関係 | Windows API(kernel32) | System.Management / WMIサービス |
| リモート取得 | △(別途仕組みが必要) | ○(WMIの仕組み次第で可能) |
「一致しない」「異常に大きい」ときに疑うポイント
差分が数百MB〜数GBと大きい場合は、アプリの問題というよりハードウェア構成やファームウェア設定の影響が濃厚です。以下は現場で遭遇しやすい原因とチェック観点です。
| 原因の例 | 起きやすい状況 | 目安の増え方 | 確認・対処の方向性 |
|---|---|---|---|
| 内蔵GPU(UMA/共有メモリ) | ノートPC/省電力CPU、iGPU有効 | 数百MB〜数GB | BIOS/UEFIのUMA Frame Buffer設定、GPUメモリ割当の見直し |
| PCIeデバイスのMMIO領域 | 高機能GPU、複数デバイス、周辺機器が多い | 数十MB〜数百MB | 構成変更で増減するか、デバイス構成(GPU/拡張カード)を確認 |
| メモリリマップ関連の設定 | 古いPC/特殊なBIOS設定 | GB単位で欠けることも | Memory Remap機能の有無や設定を確認(機種依存) |
| 仮想化/ハイパーバイザ | Hyper-V有効、仮想マシン、VBSなど | 環境により変動 | ホスト/ゲストのどちらで見ているかを整理し、要件に合わせて計測 |
| ファームウェア/ドライバ予約 | 特定機種、特定ドライバ更新後 | 数十MB〜 | BIOS更新・ドライバ更新の影響を切り分け、メーカー情報も参照 |
なお、業務で「ハードウェア予約済みが増えた=メモリリーク」と誤解されることがあります。差分算出は起動構成に依存する固定成分を見ているので、アプリの挙動と切り分けるためにも、同時に「使用中」「コミット」「利用可能」などの指標も併記すると説明がスムーズです。
実運用で役立つ表示・ログの工夫
Hardware reserved相当を取得できたら、サポートや監視で使いやすい形に整えます。特に有用なのは同じ単位・同じ丸めで揃えてログに残すことです。
- Bytesのまま保持:計算や比較の基準はBytesに統一する(後で自由に表示変更できる)
- 表示はMB(1024×1024)で丸める:タスク マネージャーと見比べやすい
- Installed / OS Visible / Reservedをセットで出す:差分だけだと原因調査が進まない
| ログ項目例 | 例 | 意図 |
|---|---|---|
| InstalledBytes | 34359738368 | 搭載量の生値 |
| OsVisibleBytes | 33639288832 | OS可視量の生値 |
| ReservedBytes | 720449536 | Hardware reserved相当(差分) |
| ReservedMB | 687 | 運用で見やすい単位に丸めた値 |
また、同一端末での比較だけでなく、複数端末の棚卸しにも使うなら「CPU型番」「GPU構成」「メモリ構成(枚数/容量)」「BIOSバージョン」なども合わせて記録しておくと、後から差分が説明しやすくなります。
よくある質問(つまずきポイントの整理)
Reservedが0になる/マイナスになりそう
差分が0になるのは、搭載量として取得した値とOS可視量がほぼ同じ(あるいは搭載量の取得に失敗して0になっている)可能性があります。コードでは、丸めや取得元の差で負になるケースに備え、0下限にするのが安全です。まずは「Installed」と「OS Visible」をそれぞれログに出して、どちらが期待値と違うかを確認してください。
WMIの値とWin32 APIの値が合わない
WMIのTotalPhysicalMemoryは機種依存でズレることがあり、TotalVisibleMemorySizeもOS側の見え方に影響されます。タスク マネージャーに寄せる目的ならWin32 APIを優先し、WMIは「P/Invokeが難しい環境の代替」「リモート取得の選択肢」として割り切ると設計がブレません。
値が大きすぎて不安
数GB単位のHardware reservedは珍しくありません。特に内蔵GPUの割当が大きい構成や、特殊なメモリマップ(デバイスやBIOS設定)では増えます。アプリ対策ではなく、構成・設定・更新履歴(BIOS/ドライバ/Windows更新)を含めて切り分けるのが近道です。
まとめ
タスク マネージャーの「ハードウェア予約済み(Hardware reserved)」は、C#から単一APIで直接読むよりも、「搭載メモリ」と「OSが利用可能な物理メモリ」の差分として算出するのが実用的です。タスク マネージャーに近い値を狙うなら、GetPhysicallyInstalledSystemMemoryとGlobalMemoryStatusExを組み合わせた方法Aをまず試し、要件に応じてWMIの方法Bを使い分けると、開発・運用の両方で扱いやすくなります。

コメント