SPSite, SPWeb (SPRequest) オブジェクト解放について

こんにちは SharePoint サポートの森 健吾 (kenmori) です。

今回は、SPSite, SPWeb オブジェクトの解放についてまとめるとともに、SPRequest オブジェクトがどのようなものかについて、よく聞かれる説明をまとめて既存の公開情報を補足する形で説明します。

 

なぜこの情報が重要なのか

SharePoint Server にサードパーティ製品や Visual Studio で開発した独自ソリューションを一切使用しない場合は、本投稿に記載された懸念点によるリスクはほとんどないと言っても過言ではありません。ただし、現実的に現在の運用環境では、カスタム ソリューションの存在しない環境は少ないと思います。

製品パッケージの上に Visual Studio を使用した .NET Framework ベースのソリューションを追加展開できることは、標準機能では実装できないものも、サードパーティ製品やカスタマイズにより、様々なお客様のビジネスにおけるご要望に応えることを可能にできる SharePoint の大きな魅力の一つといっても過言ではありません。

ただし、開発者が SPRequest オブジェクトを正しく理解して実装していない場合、実行プロセス (アプリケーション プール, SharePoint タイマ サービス) の慢性的なパフォーマンス遅延、予期せぬ実行時エラー (SPException など) の発生、最悪の場合は非常に稀ですがプロセスのクラッシュにつながる状況があります。

本投稿に記載された内容は、SharePoint でサーバー サイド オブジェクト モデル (SSOM) を扱う開発者にとっては必須知識ですが、SharePoint 管理者にとっては問題があったときの対策として必ず理解しておきたい内容となります。

補足
SharePoint クライアントサイド開発 (CSOM, JSOM, REST) では、本投稿に記載された懸念点はありません。

 

 

そもそも SPRequest とは何か

SPRequest は、SharePoint API において最も頻繁に利用する基本オブジェクトである SPSite (サイトコレクション)、SPWeb (サイト) オブジェクトが内部的に参照している COM オブジェクトです。

このオブジェクトの由来はリリース当初までさかのぼります。SharePoint は SharePoint Team Service 1.0 (SharePoint Portal Server 2001) として誕生しました。当時は、まだ .NET Framework ベースではなく完全にネイティブ コードで実装され、FrontPage Server Extension を拡張した ISAPI モジュールとして実装されていました。

SPRequest は、その当時使用されていた ISAPI モジュールである owssvr.dll の 1 オブジェクトであり、サイト コレクション、サイト、リスト、ビュー、フォルダ、ファイル、アイテムなどに対する各内部処理を実装した根幹となるコンポーネントです。

タイトル : Overview of the SharePoint Team Services Architecture
アドレス : https://msdn.microsoft.com/en-us/library/ms947858.aspx

現在の SharePoint Foundation 2013 (SharePoint Server 2013) に至るまで、コンテンツ データ格納先の SQL Server 化、.NET Framework ベースの Web アプリケーション化等、時代に合わせて製品が変更されても、根幹部分については多少の改変はあったものの完全に作り替えられることがなく、現存しているのが SPRequest モジュールそのものということになります。

つまり、現在の SharePoint においても、このように長年安定しているコアとなる大規模なコードをそのまま使用し、下記のようなアーキテクチャとして動作しています。
事実上、この構図において大半の処理がネイティブ コード上で実行されます。

 

  

.NET Framework における動的メモリ管理の注意点

上記の通り、SharePoint は純粋なマネージ コード アプリケーションではありません。そのため、.NET 相互運用性に対する理解が必要となります。

以下の様に、マネージ コード上でオブジェクトを生成すると、マネージ オブジェクトがストアされる CLR (Common Language Runtime) 上の GC ヒープ上にオブジェクトが割り当てられるだけでなく、そのオブジェクトが内部使用する COM オブジェクトもネイティブ アドレス空間上にマップされます。

 

図 1 メモリ割り当て

(注意 : 図はわかりやすさを重視するため、いろいろなことを省略しています。)

SharePoint オブジェクト モデルで使用するオブジェクトは、もちろんガベージコレクションによる動的メモリ管理および自動メモリ解放の対象となります。
ガベージ コレクションについては、下記のサイトに基礎部分がまとめられておりますのでご参考にしてください。

タイトル : ガベージ コレクションの基礎
アドレス : https://msdn.microsoft.com/ja-jp/library/ee787088(v=vs.100).aspx

 

ガベージ コレクションについて非常に簡単な説明をすると、マネージコードの実行中に GC ヒープ (メモリ) が圧迫されてきた (自動算出される閾値に達した) 、または処理を実行するために必要なメモリが足らなくなった時に、もう使われていないオブジェクトなどを検出して破棄します。このことにより、利用できるメモリ サイズを増やして、以降の処理を正常に実行させることが可能となります。

 

図 2 GC ヒープの敷居値に達した

 ガベージ コレクションによってオブジェクトが破棄された場合、参照しているネイティブ メモリも破棄されるよう実装されております。

 

図 3 オブジェクトの破棄

ガベージ コレクションによって破棄されるから大丈夫だと安心してはいけません。問題となるのは上記で赤字表記したガベージ コレクションの実行条件です。「マネージコードの実行中に GC ヒープ (メモリ) が圧迫されてきた、または処理を実行するために必要なメモリが足らなくなった時に、もう使われていないオブジェクトなどを検出して破棄します。」

プログラムがまだ使用しているオブジェクトが消えてしまうと、プログラムの動作を破壊してしまいます。つまり、オブジェクトの参照さえ消えれば (もうその変数を使ってないという状況になれば)、ガベージ コレクションによって回収される対象となり、その後ファイナライザーによって破棄される動作となります。

 

問題点

上記の内容をまとめますと、下記のような問題点があります。

・ネイティブ コード実行時にメモリ不足を検出してもガベージコレクションは実行されない
・GC ヒープの圧迫に気付きにくいため、ガベージ コレクションがトリガーされにくくなる (ネイティブ メモリ消費量の方が一般的に多い※1)

そして、この状況から発生する最悪の状況としては、ネイティブ コードの実行時にメモリ不足が検出され、必要な処理を実行することができず、プロセスがクラッシュする現象となります。

対処策

この問題を解決する一般的な対処策は下記となります。
使用されなくなったオブジェクトを適切に解放して、オブジェクトの使用時間を減らす。
・SharePoint オブジェクトモデルが実行される常駐プロセスを定期的に再起動する ※2
・パフォーマンステストを実施し、メモリ不足になりやすい端末上で運用しない ※3

今回は、使用されなくなったオブジェクトを適切に解放し、オブジェクトの使用時間を減らすことで対処する方法を記載します。

補足
※1) SharePoint 2010 以降においては、ネイティブ コードでの細かな処理の後、データ ベース アクセス層の実装がマネージ コード化され、結果をまたネイティブ コードが受け取るような実装となりました。このため、SQL Server とのデータバッファなどが動的メモリ管理の対象となるため、2007 以前よりは本問題による重症化が防げており、安定してきたと考えています。
※2) SharePoint 2010 以降では SharePoint Timer サービスも定期的に再起動されるようになり、リスクが大幅に抑えられています。
※3) 32 ビット OS 環境にも対応している SharePoint Server 2007 以前の際には下記の資料はよく論点となりました。32 ビット OS は使用できるメモリ 4GB 制限があり、極めて厳しい動作環境となります。

タイトル : ASP.NET Web アプリケーションで System.OutOfMemoryException が発生する場合のトラブルシューティング
アドレス : https://support.microsoft.com/kb/954830/ja

上記の通り、SharePoint 2010 以降では、メモリの問題に対する対処策が大幅に強化されています。

 

 

SPRequest オブジェクトの解放方法

SPSite.Dispose や SPWeb.Dispose メソッドを呼び出してネイティブ オブジェクトである SPRequest オブジェクトを即座に解放します。

 

本投稿で細かく方法は記述しません。詳細は下記 MSDN サイトを参照してください。

 

タイトル : オブジェクトの破棄
アドレス : https://msdn.microsoft.com/ja-jp/library/ee557362(v=office.14).aspx

タイトル : アンマネージ リソースをクリーンアップするための Finalize および Dispose の実装
アドレス : https://msdn.microsoft.com/ja-jp/library/b1yfkh5e(v=vs.90).aspx

SPSite, SPWeb クラスは IDisposable インターフェースを継承しています。対処策としては、自分で割り当てたオブジェクトに対して、必ず Dispose メソッドが呼ばれるようにします。try finally で必ずくくってfinally 句で Dispose メソッドを必ず呼ぶか、using ブロックを使用してオブジェクトの使用スコープを明示的に指定するということです。

 

適切なコード例 1

SPSite oSPSite = null;
SPWeb oSPWeb = null;

try
{
   oSPSite = new SPSite("https://server");
   oSPWeb = oSPSite.OpenWeb(..);
   // 処理
}
finally
{
   if (oSPWeb != null)
     oSPWeb.Dispose();
   if (oSPSite != null)
      oSPSite.Dispose();
}

 

適切なコード例 2

using (SPSite siteCollection = new SPSite("https://moss"))
{
    using (SPWeb web = siteCollection.OpenWeb())
    {
        // 処理
    }
}

using ステートメントでは、処理ブロックを抜ける際に内部的に必ず Dispose メソッドを呼び出す処理となりますため、上記コード例は書き方の違いだけで内部処理の差異はありません。 

書き方は理解している場合は多いと思います。ただし、実際にどのオブジェクトを解放すべきかについては、ある程度経験した開発者でも即答できない場合が考えられます。上記で案内したサイトには、このような区別が記載されています。下記にまとめますので、チェックしていただけますと幸いです。

その他

最も安全な実装案としては、新しくインスタンスを作成する際には、ローカル変数として使用しオブジェクトを生成して使用が終われば即座に解放する方法です。こうしておけば、オブジェクトがメモリ上で参照され続ける状況を防ぐことができ、問題となることは少ないでしょう。

SharePoint へのデータ アクセス クラスを作り込み、実装の過程でグローバル変数やクラスのメンバー変数などに保持する実装などでは、.NET Framework 側でこれらが参照されていないと認識されるまで、ガベージ コレクションの対象にはならず、メモリ上への存続時間を押し上げている結果につながることもあります。このような実装には細心の注意をお願いします。

 

 

特筆すべき診断ログ

SPRequest オブジェクトに関連するログには様々なものがあります。もし、下記の診断ログが出力されている場合、オブジェクト解放が適切に実施されていないことが断定されることになります。

 

ログ 1

オブジェクトが実行スレッド上からではなく、ガベージ コレクションによって破棄されていることをあらわすログとなります。このログは、オブジェクト解放漏れの赤信号です。

 

mm/dd/yyyy hh:mm:ss.ff w3wp.exe (0x1234) 0x5678 SharePoint Foundation Performance nask Monitorable An SPRequest object was reclaimed by the garbage collector instead of being explicitly freed. To avoid wasting system resources, dispose of this object or its parent (such as an SPSite or SPWeb) as soon as you are done using it. Allocation Id: {019F5A8E-E151-40BF-8776-4A1F8F9C00D5} To determine where this object was allocated, set Microsoft.SharePoint.Administration.SPWebService.ContentService.CollectSPRequestAllocationCallStacks = true.

 

ログ 2

オブジェクトが実行スレッドが終了するまでに破棄されていないことをあらわすログとなります。このログは、オブジェクト解放漏れの赤よりの黄信号です。
もちろん、生成したのとは別スレッドで、後ほど破棄するという実装も考えられますため黄色とします。

 

"An SPRequest object was not disposed before the end of this thread. To avoid wasting system resources, dispose of this object or its parent (such as an SPSite or SPWeb) as soon as you are done using it. This object will now be disposed. Allocation Id: {GUID}To determine where this object was allocated, create a registry key at HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Shared Tools\Web Server Extensions\HeapSettings. Then create a new DWORD named SPRequestStackTrace with the value 1 under this key."

 

解放漏れの可能性のあるオブジェクトについて、生成した際のスタックを調べることが可能です。

 

SharePoint 2007

キー : HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Shared Tools\Web Server Extensions\HeapSettings
値 : SPRequestStackTrace = 1

 

SharePoint 2010 以降

生成した際のスタックを調べるには下記の PowerShell を実行します。 

$contentService = [Microsoft.SharePoint.Administration.SPWebService]::ContentService
$contentService.CollectSPRequestAllocationCallStacks = $true
$contentService.Update()

この設定変更によって、上記ログ 1. と ログ 2. の末尾の記載が下記の様に変更されます。

変更前)
To determine where this object was allocated, ...
変更後)
This SPRequest was allocated at 場所 Microsoft.SharePoint.Library.SPRequest..ctor() 場所 ...

この設定変更によって、SPRequest のオブジェクト生成時にスタック フレームを生成し、スタック トレースを文字列としてメンバー変数として保持するようになります。
解放漏れが生じた際には、メンバー変数に格納されていたスタック情報を、これまで出力されていたログの末尾に記載する動作となります。
明示的に SPRequest オブジェクトがきちんと解放されている場合は、プロパティに格納されたスタックトレースは使用されず、その情報も含めて SPRequest オブジェクトが解放されます。
この設定変更によって、その他の箇所でログが出力されるなど、診断ログの件数が増えることにはなりません。

ログ 3

上記ログが出ていなくても、下記の通りオブジェクト数が増え続けている状況は危険です。

下記のログはあくまで閾値よりも多くの SPRequest オブジェクトがメモリ内に存在することを表しているだけのログです。

ユーザー アクセス数が多くサーバー負荷が高い状況においても、この値が上昇することはありますため、このログが出現することがオブジェクトの解放漏れと直結することはありません。

 

Mm/dd/yyyy hh:mm:ss.ff w3wp.exe (0x1234) 0x5678 Windows SharePoint Services General 0 Medium Potentially excessive number of SPRequest objects (number of objects) currently unreleased on thread number of thread. Ensure that this object or its parent (such as an SPWeb or SPSite object) is being properly disposed. Allocation Id for this object: {GUID}"

 

閾値は下記のレジストリ キーで変更できます。

キー : HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Shared Tools\Web Server Extensions\HeapSettings
値 : LocalSPRequestWarnCount = desired threshold value

 

 

オブジェクトの適切な解放を順守するために

オブジェクトの適切な解放を診断するためのツールとして SharePoint Dispose Checker と呼ばれるツールがあります。本ツールには弊社がサポートを提供している製品ではありません。

ご利用にあたっては、ツール付属の Read me および後述の技術情報等をご参照いただくとともに、使用方法をお客様の検証環境で十分に検証ください。

なお、このツールのダウンロード先は最近変更されたので、リンク切れページが多数存在しております。下記にまだ存在していますので、ご安心ください。

 

タイトル : SharePoint Dispose Checker Tool
アドレス : https://gallery.technet.microsoft.com/office/SharePoint-Dispose-Checker-01da48e8

 

見たことのないツールを使用することに抵抗を感じる使い方もいらっしゃると思いますので、使用方法を簡単に説明します。

 

SharePoint Dispose Checker Tool 使用方法

1-1. 上述のリンクより、SPDisposeCheck.msi をダウンロードし、SharePoint サーバーにインストールします。
1-2. コマンド プロンプトを起動し、以下のフォルダに移動します。(既定では以下のフォルダにインストールされます。)

> cd C:\Program Files\Microsoft\SharePoint Dispose Check

1-3. 以下のコマンドを実行します。

> SPDisposeCheck <path to assemblies> -debug –xml <file>
(コマンド例) > SPDisposeCheck " C:\WINDOWS\assembly\mywebpart.dll" -debug –xml c:\temp\result.xml

 補足 : <path to assemblies> に指定するモジュールは、dll ファイルまたは exe ファイルになります。

 1-4. 出力された xml ファイルを確認します。

 

レポート結果

対象のソース

 

これだけ、明確にレポートされます。もちろん、正しくは解放している場合においても、レポートされてしまう可能性があるため正確ではない場合もあります。
ただし、製品のリリース前に、本ツールによるチェックをかけるということも品質向上に役立つのではないかと考えます。

 

Visual Studio アドオン

 

上記ツールには Visual Studio のアドオンなども同梱されています。デバッグ時にもコンパイラ メッセージとして表示できるので、とても便利です。

いかがでしたでしょうか。今回の投稿は以上となります。