【Win7】svchostのメモリー大量消費を無理やりなんとかした話
プロローグ
我が家にはWindows7が稼働している家鯖もどきがある。
主にメディアサーバ、svnサーバ、テレビとつないでWMCによる動画音楽鑑賞、メディアプレーヤーのリモートプレイサーバ等として使っている。
外からいろいろいじれるようにsshdが入っていたり、一応httpdも入っているけどあんまり使ってない。
sshdは、会社からrdpしたいときのトンネル役としてそれなりに活躍する場面もあるが。
事件は自宅で起きた
しかしこの鯖、一つ困ったことがある。
MS製サービスの一部がたまに(割と頻繁にという表現のほうが適切かもしれない)メモリリークするのだ。
こんな感じに、svchost.exe が 2.5Gbのメモリーを食ってるのがお分かりかと。
ちなみに、使用メモリーは秒速100kbくらいの速度で増加して、物理メモリーを食いつぶすと 大抵 OSを巻き込んで[DEAD END]となる。
ちなみに、問題のsvchost.exe が管理している service は↓のような感じ。
いろいろ試してみると、[Homegroup Listener] というサービスを再起動すると、これ以上のメモリーリークを抑えることができる場合もある。
しかし、これまでにメモリリークした分は返してくれないぽいし、サービス再起動後、メモリーリークが収まるかというと微妙だったりする。
[homegroup listener]を止めてしまう手もあるのだが、自宅の環境は、homegroup にガリガリ依存してるので、それは×。
解決編
試しに、svchost.exe をタスクマネージャから殺したところ、メモリ使用量は当然のごとくガクッと減った。
しかも、殺したはずのサービスは、勝手に再起動されるし、さらに、メモリーリークも収まっている。ぽい。
なので、メモリーを食いつぶしてそうなsvchost.exeプロセスがあったら、対象プロセスを殺して差し上げればよろしいのではないかという発想。
linux だと、top|grep した結果を awk して、pid とメモリー使用量を引っ張ってきて、対象プロセスのメモリ使用量が 一定以上だったら pkill してあげるという .sh を書くことになると思われますが
これをwindowsでやると下のような感じになります(vb2008くらいでコンパイルできます)
'KillSvcHost.vb Module killSvchost Sub Main() KillProcess(GetCommandLines("C:\Windows\System32\svchost.exe -k LocalSystemNetworkRestricted", 256 * 1024 * 1024)) End Sub Public Function GetCommandLines(ByVal targetCommandline As String, ByVal limitWorkingSetSize As Long) As Long Dim result As Long = 0 Using m As New Management.ManagementClass("Win32_Process"), ps = m.GetInstances() For Each p In ps Dim pid As Long = p("Handle") Dim commandline As String = CStr(p("CommandLine")) If commandline IsNot Nothing Then Dim workingSetSize As Long = p("WorkingSetSize") If (String.Compare(commandline, targetCommandline, True) = 0) And (workingSetSize > limitWorkingSetSize) Then result = pid Console.WriteLine(commandline & ":" & workingSetSize) End If p.Dispose() End If Next End Using Return result End Function Public Function KillProcess(ByVal pid As Long) As Boolean Dim p As System.Diagnostics.Process Dim result As Boolean = False p = System.Diagnostics.Process.GetProcessById(pid) If p IsNot Nothing Then Try Console.WriteLine("Kill:" & CStr(pid)) p.Kill() Console.WriteLine("successful..") result = True Catch ex As Exception Console.WriteLine("failed...") End Try End If Return result End Function End Module
C:\Windows\System32\svchost.exe -k LocalSystemNetworkRestricted
で起動されたプロセスを探し、ワーキングセットサイズ(共有メモリー部分も含めた、プロセスの使用メモリー)が 256Mb 超えていたらとりあえず殺します。
これをタスク(linuxでいうcrond)に登録して、適当な間隔で動かせばよい。
メモリーリークが起きだしてしばらくすると、問題を起こした svchost.exe は勝手に殺されるというわけ。
ちなみに、タスクマネージャーでは、プライベートワーキングセットサイズとして表示されるのですが、これは、プロセスが使用しているメモリーのうち、非共有部分(そのプロセスしか絶対に使っていない部分のメモリー)のサイズを表示している。
ただし、プライベートワーキングセットサイズの計算は微妙にめんどくさいのでそこまではやりません。
プロセスが握っている全ページを取得し、各ページが共有メモリーなのか非共有なのかを調べて、非共有メモリーのページサイズのみを合算する。だけなんだけど。
とりあえず、解決できたのでよしとする。
インサイドWindows 第7版 上 システムアーキテクチャ、プロセス、スレッド、メモリ管理、他 (マイクロソフト公式解説書)
- 作者: Pavel Yosifovich,Alex Ionescu,Mark E. Russinovich,David A. Solomon,山内和朗
- 出版社/メーカー: 日経BP社
- 発売日: 2018/04/27
- メディア: 単行本
- この商品を含むブログ (1件) を見る