2012년 1월 28일 토요일

Windows의 메모리를 관리해보자.

일반적으로 Windows 사용자 입장에서는 메모리 관리를 OS에 맡기거나 (즉 아무것도 하지 않거나), 저처럼 Freeware 를 사용하여 메모리를 관리하겠죠. 저는 IOBit Smart RAM 을 사용합니다만, 워낙 많은 Freeware 가 있으니 뭘 쓰던 그리 큰 문제는 아닙니다.

오늘 얘기하고자 하는 방향은 서버 관리자 입장에서 본 윈도우즈의 메모리 관리를 말하고자 합니다. 개인용 PC와는 달리 서버 시스템 특히 운영 서버의 경우에는 Freeware 같이 검증(?)되지 않은 것을 함부로 깔 수도 없습니다. 근데 이런 문제가 가끔 발생합니다.

  1. 캐시 시스템을 사용하는데, 이 놈이 Max 값으로 올라간 후 내려오지 않아요 (분명 Cache Entry 는 expires가 되어 메모리에서 해제된 것을 확인 했는데도)
  2. RDBMS 등에서 미리 여유분으로 잡아 먹고 있어요.
  3. 자주 사용하지 않는 시스템이 한번 실행 된 후 메모리를 반환하지 않아요

뭐 더 많은 상황이 있겠지만, 제가 간접적으로 겪은 1번 상황에 대해서만 얘기하겠습니다. 2번은 성능을 위해서라도 그냥 냅두시는 게 좋을 것이고, 3번은 1번에 묻어서 같이 해결 보는 걸로 하겠습니다.

자 그럼 우선 윈도우즈의 메모리 관리에 대해 알아봐야겠지요? 윈도우즈 OS 라는 놈이 메모리 관리에 대해서 그리 좋은 시스템이 못 되는 것은 유명합니다만, 그래도 쓸만은 합니다. 즉 특수한 극한 상황이 아니라면, 훌륭한 역할을 수행하고 있다는 얘기지요.

다만 메모리를 많이 쓰는 프로세스가 많은 일을 하고자 할 때, 메모리를 미리 선점한 놈들에게 정리 시키도록 해주면 훨씬 좋은 성능의 시스템이 될 수 있습니다.

위의 1번의 경우처럼 캐시시스템은 자신이 사용할 메모리를 예측하여, 미리 할당해서 사용하고, 메모리 사용을 더 이상 할 필요가 없어도, 향후 또 필요할 것을 대비해서 그냥 잡아둡니다. 그게 캐시 시스템 입장에서는 더 효과적이죠. 메모리를 반환하고, 필요할 때 다시 할당하는 일은 2중의 비용이 들어가니까요. (캐시 시스템 만든 개발자들이 똑똑하게 만들었겠죠) 다만 Memcached 나 SharedCache 처럼 메모리만을 기반으로 하는 캐시 시스템의 경우, 특별한 상황에서는 과도한 메모리를 사용하여, 운영자의 간담을 서늘케 하는 경우가 가끔 있습니다. (우린 이런 사람들을 새가슴 혹은 원리를 모르는 사람 이라고 하지요^^ – 농담입니다.)

다만 1번 경우 때문에 운영서버를 하루에 한번씩 RESET 하는 회사가 있어서요… 참 답답할 노릇이죠…

자 그럼 이런 상황에서 강제로 메모리를 반환 받을 수는 없을까요? OS 라면 가능하겠지요? OS가 대빵인데, 지들이 무슨 힘이 있겠어요?

자 여기서 보면 그럼 OS가 효율적으로 메모리 관리를 하면 1번과 같은 문제가 안 생기겠네요? 네 대부분 안 생깁니다. 다만 급격하게 메모리를 많이 사용해야 하는 프로세스가 늘어나면, Pagefile을 써야하고, 그럼 실행속도가 느려집니다. 이때서야 OS가 아… 너 메모리가 많이 부족하구나… 다른 놈들 메모리 좀 정리해서 방 비워줄께…

즉 뒷북이란 말이죠…

그럼 처음으로 돌아가서 메모리 관리용 Freeware를 사용하면 되잖아요… 운영지침상 서버에는 깔지 못한다니까요^^

그럼 어떻하지요? OS가 방을 비워주기를 기다리지 말고, 메모리 관리 Freeware 처럼 내 프로그램에서 OS에게 실행중인 프로세스들에게 사용하지 않는 메모리 비워라!!! 라고 요청할 수 있으면 되겠지요?

말이 참 길었죠? 실제 메모리를 반환하도록 하는 코드입니다.

public static class ProcessTool
{
#region << logger >>

private static readonly NLog.Logger log = NLog.LogManager.GetCurrentClassLogger();
private static readonly bool IsDebugEnabled = log.IsDebugEnabled;

#endregion

/// <summary>
///
시스템에서 실행중인 모든 프로세스에 대해, 사용하지 않는 WorkingSet 메모리를 OS에게 반환하도록 합니다.
/// </summary>
/// <param name="excludeThisProcess">
현재 프로세스를 제외할 것인가 여부 (기본값은 제외)</param>
/// <param name="excludeProcessNames">
메모리 반환을 하지 않을 프로세스 명의 컬렉션</param>
public static void TrimAllProcessMemory(bool excludeThisProcess = true, string[] excludeProcessNames = null)
{
if(log.IsInfoEnabled)
log.Info("컴퓨터의 모든 프로세스에 대해 사용하지 않는 메모리를 OS에 반환하도록 합니다...");

var currentProcess = Process.GetCurrentProcess();

Parallel.ForEach(Process.GetProcesses(),
process =>
{
if(excludeThisProcess && (process.ProcessName == currentProcess.ProcessName))
return;

if(excludeProcessNames != null &&
excludeProcessNames.
Any(procName => procName == process.ProcessName))
return;

TrimProcessMemory(process);
});
}

/// <summary>
///
지정된 프로세스의 사용하지 않는 WorkingSet 메모리를 OS에게 반환하도록 합니다.
/// </summary>
/// <param name="process">
메모리 해제를 할 프로세스</param>
/// <returns>
메모리 해제 여부</returns>
public static bool TrimProcessMemory(Process process)
{
if(IsDebugEnabled)
log.Debug("프로세스의 Working Memory 중에 사용하지 않는 부분을 OS에 반환하도록 합니다.");

if(process == null)
return false;

bool _result;

try
{
long oldWorkingSet64 = process.WorkingSet64;
_result =
EmptyWorkingSet((long) process.Handle);

var targetProcess = Process.GetProcessById(process.Id);

if(_result)
{
if(log.IsInfoEnabled)
log.Info("프로세스[{0}]의 WorkingSet 메모리를 비웠습니다. 기존=[{1}], 현재=[{2}], 반환값=[{3}]",
process.
ProcessName, oldWorkingSet64, targetProcess.WorkingSet64, oldWorkingSet64 -
}
}
catch(Exception ex)
{
if(log.IsWarnEnabled)
{
log.Warn("Process[{0}]의 메모리를 정리하는데 예외가 발생했습니다...", process.ProcessName);
log.Warn(ex);
}
_result =
false;
}

return _result;
}

[
DllImport("psapi")]
internal static extern bool EmptyWorkingSet(long hProcess);
}




핵심은 psapi.dll  에 있는 EmptyWorkingSet(long hProcess) 입니다. 이 놈이 특정 프로세스의 사용하지 않는 메모리를 반환하게끔 합니다.



혹시 Win32 API 사용에 어려움이 있으시다면, PInvoke.net 사이트를 참조하세요. .NET에서 Win32 API 를 사용할 때, 많은 도움을 줄 수 있는 사이트입니다.



저는 위의 ProcessTool .TrimAllProcessMemory()  를 CPU 활용률이 5% 미만일 경우 1시간마다 실행하게끔 웹 어플리케이션에서 Background Service로 돌립니다. 그럼 운영자는 메모리를 별로 사용하지 않는데도 잘 돌아간다고 좋아합니다^^

댓글 1개:

sayheart :

사용시. 리소스가 사라지는 현상이 있었습니다. ㅇ_ㅇ;; 주의하셔야 합니다.
게다가. 다른 프로세스를 접근하는것은 문제가 있어보입니다.

EmptyWorkingSet() 과 CacheMem 로 메모리 최적화 | ETC
http://www.devpia.com/MAEUL/Contents/Detail.aspx?BoardID=51&MAEULNO=20&no=8839&page=1