guidox / PC

Diagnose a Memory Leak in a Long-Running App

TL;DR

Sample **PrivateMemorySize64** every minute with **Get-Process**, then watch the handle count. A line that only goes up is a leak; a sawtooth is a cache.

Before you start

Requirements

  • PowerShell
  • The app that grows
  • 30 minutes
Step by step

Instructions

01

Establish the baseline

Working set includes memory that can be paged out; private bytes is what the process owns. A leak shows in private bytes, while a cache shows in working set.

Get-Process app | Select-Object Name,Id,@{n='WS_MB';e={[math]::Round($_.WorkingSet64/1MB)}},@{n='Private_MB';e={[math]::Round($_.PrivateMemorySize64/1MB)}},HandleCount,Threads.Count
Get-CimInstance Win32_PerfFormattedData_PerfProc_Process | Where-Object Name -like 'app*' | Select-Object Name,PrivateBytes,WorkingSet,HandleCount
02

Sample over time

A single number proves nothing. Sampling every minute for half an hour while the app does its normal work shows the trend.

$log = @()
1..30 | ForEach-Object { $p = Get-Process app -ErrorAction SilentlyContinue; if ($p) { $log += [pscustomobject]@{ T=(Get-Date -Format HH:mm:ss); PrivateMB=[math]::Round($p.PrivateMemorySize64/1MB); Handles=$p.HandleCount } }; Start-Sleep -Seconds 60 }
$log | Format-Table -AutoSize
$log | Export-Csv "$env:USERPROFILE\leak.csv" -NoTypeInformation
03

Distinguish a leak from a cache

A cache plateaus or drops when memory pressure rises. A leak keeps climbing until the process dies, and the handle count often climbs with it.

$log = Import-Csv "$env:USERPROFILE\leak.csv"
$first = [int]$log[0].PrivateMB; $last = [int]$log[-1].PrivateMB
"start $first MB, end $last MB, delta $($last-$first) MB over $($log.Count) samples"
$log | Select-Object -First 1 -Last 1
04

Check the system-wide pressure

A leak in one process shows up as rising committed bytes for the whole machine. If the machine commits more than it has RAM plus pagefile, everything slows.

Get-Counter '\Memory\Committed Bytes','\Memory\Available MBytes','\Memory\Pool Nonpaged Bytes' -SampleInterval 2 -MaxSamples 3 | Select-Object -ExpandProperty CounterSamples | Select-Object Path,CookedValue
Get-CimInstance Win32_OperatingSystem | Select-Object TotalVisibleMemorySize,FreePhysicalMemory,TotalVirtualMemorySize,FreeVirtualMemory
05

Find the leaking component

A managed app leaks in one module. The module list with sizes, taken twice, shows which library grew between the samples.

(Get-Process app).Modules | Sort-Object ModuleMemorySize -Descending | Select-Object -First 10 ModuleName,ModuleMemorySize
Start-Sleep -Seconds 300
(Get-Process app).Modules | Sort-Object ModuleMemorySize -Descending | Select-Object -First 10 ModuleName,ModuleMemorySize
06

Act on the result

A restart schedule buys time; a vendor report with the numbers gets a fix. Both are legitimate outcomes, and the data is what makes the report useful.

Get-Process app | Select-Object Id,StartTime,@{n='Private_MB';e={[math]::Round($_.PrivateMemorySize64/1MB)}}
Get-WinEvent -LogName Application -MaxEvents 50 | Where-Object Message -match 'app' | Select-Object TimeCreated,Id,LevelDisplayName -First 5
# schedule a nightly restart only as a stopgap
Troubleshooting

Common issues

The process restarts by itself

A leak that hits the address limit crashes it. Check the Application log for the crash and the faulting module, which often names the leak.

Private bytes stay flat but the machine slows down

The leak is in the kernel pool or in a driver. Check Pool Nonpaged Bytes and look for a driver update.

Get-Process shows several processes with the same name

The app uses a multi-process model. Sample the parent by id, or sum the private bytes of the group.

The numbers jump at random

Garbage collection or a large allocation. Sample more often and look at the trend over hours, not minutes.

FAQ

Frequently asked

Does a large working set mean a leak?

No. Windows trims it under pressure. Private bytes and the handle count are the better indicators.

Is a restart schedule a fix?

It is a workaround. It hides the symptom and loses the evidence, so collect the numbers first.

Can the Task Manager show this?

It shows the current values. A trend needs repeated samples, which is why the script exists.

How to undo this

Nothing is changed. Delete leak.csv when you no longer need it, and remove any restart schedule you added.

Changelog · 1 entry
  1. 2026-11-01
    First version
Interactive version: checklist, feedback, related guides →

Free · no account needed · tracks your progress