Recently I've had some problems with my system running out of memory. It took a while to discover what was going on, but I eventually determined that when I copied large amounts of data to my machine from a file share an equivalently large amount of memory was put into 'Standby'. Task Manager doesn't appear to show the Standby memory usage, but Resource Monitor does. At first I could only get the memory back by rebooting, but I eventually I discovered that the SysInternals guys had written a great utility to free the memory (link below).
Here is a brief blurb on Standby memory:
The Standby list contains unmodified pages that have been removed from process working sets, which effectively makes the Standby list a cache. If a process needs a page that is on the Standby list, the memory manager immediately returns the page to its working set. All pages on the Standby list are available for memory allocation requests. If a process requests memory, the memory manager can take a page from the Standby list, initialize it, and allocate it to the calling process. This is called repurposing a page. Pages on the Standby list are often from recently used files. By keeping these pages on the Standby list, the memory manager reduces the need to read information from the disk. Disk reads can decrease system responsiveness.
(this is from the document here: Memory Sizing Guidance
Here is a link to the tool: RAMMap
My Question Is:
Does anyone have an idea how do this programmatically? Ideally I'd like to use C#, but I would appreciate any pointers that might help me get to an answer.
Thanks!
Here is my code, it is a console application that has to be run with administrator privileges. Code is C#. Uses EmptyWorkingSet and MemoryPurgeStandbyList.
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Principal;
using System.Threading;
using System.Collections.Generic;
namespace FreeMemory
{
//Declaration of structures
//SYSTEM_CACHE_INFORMATION
[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct SYSTEM_CACHE_INFORMATION
{
public uint CurrentSize;
public uint PeakSize;
public uint PageFaultCount;
public uint MinimumWorkingSet;
public uint MaximumWorkingSet;
public uint Unused1;
public uint Unused2;
public uint Unused3;
public uint Unused4;
}
//SYSTEM_CACHE_INFORMATION_64_BIT
[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct SYSTEM_CACHE_INFORMATION_64_BIT
{
public long CurrentSize;
public long PeakSize;
public long PageFaultCount;
public long MinimumWorkingSet;
public long MaximumWorkingSet;
public long Unused1;
public long Unused2;
public long Unused3;
public long Unused4;
}
//TokPriv1Luid
[StructLayout(LayoutKind.Sequential, Pack = 1)]
internal struct TokPriv1Luid
{
public int Count;
public long Luid;
public int Attr;
}
public class Program
{
//Declaration of constants
const int SE_PRIVILEGE_ENABLED = 2;
const string SE_INCREASE_QUOTA_NAME = "SeIncreaseQuotaPrivilege";
const string SE_PROFILE_SINGLE_PROCESS_NAME = "SeProfileSingleProcessPrivilege";
const int SystemFileCacheInformation = 0x0015;
const int SystemMemoryListInformation = 0x0050;
const int MemoryPurgeStandbyList = 4;
const int MemoryEmptyWorkingSets = 2;
//Import of DLL's (API) and the necessary functions
[DllImport("advapi32.dll", SetLastError = true)]
internal static extern bool LookupPrivilegeValue(string host, string name, ref long pluid);
[DllImport("advapi32.dll", SetLastError = true)]
internal static extern bool AdjustTokenPrivileges(IntPtr htok, bool disall, ref TokPriv1Luid newst, int len, IntPtr prev, IntPtr relen);
[DllImport("ntdll.dll")]
public static extern UInt32 NtSetSystemInformation(int InfoClass, IntPtr Info, int Length);
[DllImport("psapi.dll")]
static extern int EmptyWorkingSet(IntPtr hwProc);
//Function to clear working set of all processes
public static void EmptyWorkingSetFunction()
{
//Declaration of variables
string ProcessName = string.Empty;
Process[] allProcesses = Process.GetProcesses();
List<string> successProcesses = new List<string>();
List<string> failProcesses = new List<string>();
//Cycle through all processes
for (int i = 0; i < allProcesses.Length; i++)
{
Process p = new Process();
p = allProcesses[i];
//Try to empty the working set of the process, if succesfull add to successProcesses, if failed add to failProcesses with error message
try
{
ProcessName = p.ProcessName;
EmptyWorkingSet(p.Handle);
successProcesses.Add(ProcessName);
}
catch (Exception ex)
{
failProcesses.Add(ProcessName + ": " + ex.Message);
}
}
//Print the lists with successful and failed processes
Console.WriteLine("SUCCESSFULLY CLEARED PROCESSES: " + successProcesses.Count);
Console.WriteLine("-------------------------------");
for (int i = 0; i < successProcesses.Count; i++)
{
Console.WriteLine(successProcesses[i]);
}
Console.WriteLine();
Console.WriteLine("FAILED CLEARED PROCESSES: " + failProcesses.Count);
Console.WriteLine("-------------------------------");
for (int i = 0; i < failProcesses.Count; i++)
{
Console.WriteLine(failProcesses[i]);
}
Console.WriteLine();
}
//Function to check if OS is 64-bit or not, returns boolean
public static bool Is64BitMode()
{
return Marshal.SizeOf(typeof(IntPtr)) == 8;
}
//Function used to clear file system cache, returns boolean
public static void ClearFileSystemCache(bool ClearStandbyCache)
{
try
{
//Check if privilege can be increased
if (SetIncreasePrivilege(SE_INCREASE_QUOTA_NAME))
{
uint num1;
int SystemInfoLength;
GCHandle gcHandle;
//First check which version is running, then fill structure with cache information. Throw error is cache information cannot be read.
if (!Is64BitMode())
{
SYSTEM_CACHE_INFORMATION cacheInformation = new SYSTEM_CACHE_INFORMATION();
cacheInformation.MinimumWorkingSet = uint.MaxValue;
cacheInformation.MaximumWorkingSet = uint.MaxValue;
SystemInfoLength = Marshal.SizeOf(cacheInformation);
gcHandle = GCHandle.Alloc(cacheInformation, GCHandleType.Pinned);
num1 = NtSetSystemInformation(SystemFileCacheInformation, gcHandle.AddrOfPinnedObject(), SystemInfoLength);
gcHandle.Free();
}
else
{
SYSTEM_CACHE_INFORMATION_64_BIT information64Bit = new SYSTEM_CACHE_INFORMATION_64_BIT();
information64Bit.MinimumWorkingSet = -1L;
information64Bit.MaximumWorkingSet = -1L;
SystemInfoLength = Marshal.SizeOf(information64Bit);
gcHandle = GCHandle.Alloc(information64Bit, GCHandleType.Pinned);
num1 = NtSetSystemInformation(SystemFileCacheInformation, gcHandle.AddrOfPinnedObject(), SystemInfoLength);
gcHandle.Free();
}
if (num1 != 0)
throw new Exception("NtSetSystemInformation(SYSTEMCACHEINFORMATION) error: ", new Win32Exception(Marshal.GetLastWin32Error()));
}
//If passes paramater is 'true' and the privilege can be increased, then clear standby lists through MemoryPurgeStandbyList
if (ClearStandbyCache && SetIncreasePrivilege(SE_PROFILE_SINGLE_PROCESS_NAME))
{
int SystemInfoLength = Marshal.SizeOf(MemoryPurgeStandbyList);
GCHandle gcHandle = GCHandle.Alloc(MemoryPurgeStandbyList, GCHandleType.Pinned);
uint num2 = NtSetSystemInformation(SystemMemoryListInformation, gcHandle.AddrOfPinnedObject(), SystemInfoLength);
gcHandle.Free();
if (num2 != 0)
throw new Exception("NtSetSystemInformation(SYSTEMMEMORYLISTINFORMATION) error: ", new Win32Exception(Marshal.GetLastWin32Error()));
}
}
catch (Exception ex)
{
Console.Write(ex.ToString());
}
}
//Function to increase Privilege, returns boolean
private static bool SetIncreasePrivilege(string privilegeName)
{
using (WindowsIdentity current = WindowsIdentity.GetCurrent(TokenAccessLevels.Query | TokenAccessLevels.AdjustPrivileges))
{
TokPriv1Luid newst;
newst.Count = 1;
newst.Luid = 0L;
newst.Attr = SE_PRIVILEGE_ENABLED;
//Retrieves the LUID used on a specified system to locally represent the specified privilege name
if (!LookupPrivilegeValue(null, privilegeName, ref newst.Luid))
throw new Exception("Error in LookupPrivilegeValue: ", new Win32Exception(Marshal.GetLastWin32Error()));
//Enables or disables privileges in a specified access token
int num = AdjustTokenPrivileges(current.Token, false, ref newst, 0, IntPtr.Zero, IntPtr.Zero) ? 1 : 0;
if (num == 0)
throw new Exception("Error in AdjustTokenPrivileges: ", new Win32Exception(Marshal.GetLastWin32Error()));
return num != 0;
}
}
//MAIN Program
static void Main(string[] args)
{
//Clear working set of all processes
EmptyWorkingSetFunction();
//Clear file system cache
ClearFileSystemCache(true);
//Waiting for input of user to close program
Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}
}
The secret seems to be in the Process Hacker source code (which is in c language). Looking at the code, you'll see a promising command MemoryPurgeStandbyList which seems to be called when we choose the "empty standby list" option in the GUI.
memlists.c(227, 35): command = MemoryPurgeStandbyList;
ntexapi.h(1475, 5): MemoryPurgeStandbyList,
http://processhacker.sourceforge.net/
Also available here as a command line version.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With