I normally keep the Win11 25H2 session going for at least a week at a time without rebooting. In that time, XY is never closed. This allows GDI Objects (visible in Task Manager, Details; or Process Explorer) to grow over time. It starts for XY at about 1289, which is unusual in itself (no other program is even close, and I have a lot of them), but if it stayed there or at least stayed under 10K (the hard limit, unless you edit the Registry), there wouldn't be a problem. v28 does not, however, at least for me. GDI grows for XY at a pace of about 500 every 4 hours (rate of increase may vary by usage--average use yields about this figure for me).
When it gets to 10K, you can't use it anymore. It's very hard to describe, but when you click it to use it (from its minimized state), you see a kind of fractured XY window, which makes sense given what GDI Objects are. It's not crashed, since you can close it, but this is classically what happens (with any program) in a GDI leak hitting the ceiling.
Since GDI Objects are the very definition of obscure, I have a Powershell script that shows the top processes for not only GDI but also User Obj, Threads, and Handles. This is the first thing I run when something is unexplained. To be clear, XY has NO problem with the other three, but I like to include them since they're known troublemakers in general (especially Handles).
Code: Select all
if(-not ('Win32' -as [type])){Add-Type -TypeDefinition 'using System; using System.Runtime.InteropServices; public static class Win32 { [DllImport("user32.dll")] public static extern int GetGuiResources(IntPtr hProcess,int uiFlags); }'}; $esc=[char]27; $red=if(Test-Path variable:PSStyle){$PSStyle.Foreground.Red}else{"$esc[31m"}; $grn=if(Test-Path variable:PSStyle){$PSStyle.Foreground.Green}else{"$esc[32m"}; $dim=if(Test-Path variable:PSStyle){$PSStyle.Foreground.BrightBlack}else{"$esc[90m"}; $rst=if(Test-Path variable:PSStyle){$PSStyle.Reset}else{"$esc[0m"}; $b=if(Test-Path variable:PSStyle){$PSStyle.Bold}else{"$esc[1m"}; $boff=if(Test-Path variable:PSStyle){$PSStyle.BoldOff}else{"$esc[22m"}; $fmt={param([double]$x) if($x -ge 1GB){"{0:N1}GB" -f ($x/1GB)} elseif($x -ge 1MB){"{0:N0}MB" -f ($x/1MB)} elseif($x -ge 1KB){"{0:N0}KB" -f ($x/1KB)} else{"{0:N0}B" -f $x}}; $p=Get-Process|ForEach-Object{ $g=$null;$u=$null;$n=$null; if($_.Id -eq 4){$n='System (ntoskrnl.exe)'} elseif($_.Id -eq 0){$n='System Idle Process'} else { try{$n=$_.MainModule.ModuleName}catch{}; if(-not $n){$n="$($_.ProcessName).exe"} }; try{$g=[Win32]::GetGuiResources($_.Handle,0)}catch{}; try{$u=[Win32]::GetGuiResources($_.Handle,1)}catch{}; [pscustomobject]@{Process=$n;Id=$_.Id;Handles=$_.HandleCount;Threads=$_.Threads.Count;GDI=$g;USER=$u} }; $rows=@($p|Sort Handles -Desc|Select -First 3|%{[pscustomobject]@{Order=1;Metric='Handles';Process=$_.Process;Count=[int]$_.Handles;Id=[int]$_.Id}})+@($p|Sort Threads -Desc|Select -First 3|%{[pscustomobject]@{Order=2;Metric='Threads';Process=$_.Process;Count=[int]$_.Threads;Id=[int]$_.Id}})+@($p|?{$_.GDI -ne $null}|Sort GDI -Desc|Select -First 3|%{[pscustomobject]@{Order=3;Metric='GDI Obj';Process=$_.Process;Count=[int]$_.GDI;Id=[int]$_.Id}})+@($p|?{$_.USER -ne $null}|Sort USER -Desc|Select -First 3|%{[pscustomobject]@{Order=4;Metric='User Obj';Process=$_.Process;Count=[int]$_.USER;Id=[int]$_.Id}}); $mw=(@('Metric')+($rows.Metric|%{$_.ToString()})|Measure-Object Length -Maximum).Maximum; $pw=(@('Process')+($rows.Process|%{$_.ToString()})|Measure-Object Length -Maximum).Maximum; $cw=(@('Count')+($rows.Count|%{$_.ToString()})|Measure-Object Length -Maximum).Maximum; $iw=(@('Id')+($rows.Id|%{$_.ToString()})|Measure-Object Length -Maximum).Maximum; $hdr=("Metric".PadRight($mw)+" "+"Process".PadRight($pw)+" "+"Count".PadLeft($cw)+" "+"Id".PadLeft($iw)); $sep='-'*$hdr.Length; $out=New-Object System.Collections.Generic.List[string]; $out.Add($hdr); $out.Add($sep); $prev=$null; foreach($r in ($rows|Sort Order,@{Expression='Count';Descending=$true})){ if($prev -and $r.Order -ne $prev){$out.Add($sep)}; $prev=$r.Order; $thr=1000; switch($r.Metric){'Handles'{$thr=10000}'Threads'{$thr=500}'GDI Obj'{$thr=5000}'User Obj'{$thr=5000}}; $cnt=$r.Count.ToString().PadLeft($cw); $cntColor=$grn; if($r.Count -ge $thr){$cntColor=$red}; $cntc=$cntColor+$cnt+$rst; $idc=$dim+($r.Id.ToString().PadLeft($iw))+$rst; $out.Add($r.Metric.PadRight($mw)+" "+$r.Process.PadRight($pw)+" "+$cntc+" "+$idc) }; $svchostPids=@($rows | Where-Object{$_.Process -ieq 'svchost.exe'} | Select-Object -ExpandProperty Id -Unique); if($svchostPids.Count -gt 0){ $out.Add(''); $out.Add('svchost.exe contents (top list only):'); foreach($spid in $svchostPids){ $svcs=Get-CimInstance Win32_Service -Filter ("ProcessId={0}" -f $spid) -ErrorAction SilentlyContinue | Sort-Object Name | Select-Object Name,DisplayName; if($svcs){ $out.Add((" PID {0}: {1}" -f $spid,(($svcs|ForEach-Object{ "{0} ({1})" -f $_.Name,$_.DisplayName }) -join ', '))) } else { $out.Add((" PID {0}: <none found>" -f $spid)) } } }; $cs=(Get-Counter '\Memory\Available Bytes','\Memory\Committed Bytes','\Memory\Commit Limit','\Memory\Cache Bytes','\Memory\Pool Paged Bytes','\Memory\Pool Nonpaged Bytes').CounterSamples; $cv={param([string]$suffix) [double]($cs|Where-Object{$_.Path -like "*$suffix"}|Select-Object -First 1 -ExpandProperty CookedValue)}; $tp=[double](Get-CimInstance Win32_ComputerSystem).TotalPhysicalMemory; $avail=& $cv '\Memory\Available Bytes'; $inuse=$tp-$avail; $comm=& $cv '\Memory\Committed Bytes'; $clim=& $cv '\Memory\Commit Limit'; $cache=& $cv '\Memory\Cache Bytes'; $pp=& $cv '\Memory\Pool Paged Bytes'; $np=& $cv '\Memory\Pool Nonpaged Bytes'; $out.Add(''); $out.Add(("Memory: "+$b+"InUse"+$boff+" $(& $fmt $inuse) "+$b+"Avail"+$boff+" $(& $fmt $avail)")); $out.Add((" "+$b+"Commit"+$boff+" $(& $fmt $comm)/$(& $fmt $clim) "+$b+"Cache"+$boff+" $(& $fmt $cache)")); $out.Add((" "+$b+"PagedPool"+$boff+" $(& $fmt $pp) "+$b+"NonPagedPool"+$boff+" $(& $fmt $np)")); ([Environment]::NewLine + ($out -join [Environment]::NewLine) + [Environment]::NewLine)
XYplorer Beta Club