環境
Webアプリケーション
- Java 8
- Java EE 7 (Payara 4)
- JSF 2.2
現象
OutOfMemoryでアプリケーションが死んだ。
閉じた画面の管理Beanがずっと生存しててR.I.P.
怪しい部分
- セッションタイムアウト時間が長い
- 一部管理ビーンがメモリ喰う
当初の流れ
よくある説明
アノテーション | 名前空間 | 生存期間 |
---|---|---|
@RequestScoped |
javax.enterprise.context.RequestScoped | 1回のHTTPリクエスト/レスポンス |
@ViewScoped |
javax.faces.view.ViewScoped | ビューが同じである間 |
@SessionScoped |
javax.enterprise.context.SessionScoped | セッション開始~終了 |
@ApplicationScoped |
javax.enterprise.context.ApplcationScoped | アプリケーションが起動している間 |
@Dependent |
javax.enterprise.context.Dependent | インジェクトされた先のスコープに依存 |
@ConversationScoped |
javax.enterprise.context.ConversationScoped | 開始と終了を任意に指定できる |
@FlowScoped |
javax.enterprise.context.FlowScoped | あらかじめ定義したフローの開始から終了まで |
自分の理解
ほ~ん。
@ViewScoped
は画面開いたら生成して、閉じたら廃棄してくれるんか便利。
@SessionScoped
にログインユーザーの情報持たせて、@ViewScoped
で業務画面作ればいいんだな。
web.xml
でセッションが保持するView
数も指定できると。
/web.xml
<context-param>
<param-name>javax.faces.STATE_SAVING_METHOD</param-name>
<param-value>sever</param-value>
</context-param>
<context-param>
<param-name>com.sun.faces.numberOfLogicalViews</param-name>
<param-value>20</param-value>
</context-param>
<context-param>
<param-name>com.sun.faces.numberOfViewsInSession</param-name>
<param-value>20</param-value>
</context-param>
上限超えたら解放されるんか把握。
とかやってたら、破棄されたはずの@ViewScoped
の管理ビーンがめっちゃ生存しててメモリが圧殺された。
実際
どうやら以下のパターンでないと@ViewScoped
の管理ビーンは解放されないらしい。
- 結果が得られないポストバック
- セッションの有効期限切れ
だから以下のパターンでは生存する。
- 別のページへのGET
- ページ更新
- セッション内の最大論理ビュー数を超えたとき (期限切れになるけど破棄されない)
解決策
標準の@ViewScoped
じゃなくてOmniFaces @ViewScopedを使う。
OmniFaces
の@ViewScoped
アノテーション使うだけで、ブラウザのunload
イベントを拾ったら解放してくれる。
デフォルトの依存関係になるべく影響を与えないように作られてるので@ViewScoped
使うためだけにでも導入していいかなと。
あとがき
Viewっていわれると画面単位に管理してくれるのかなと安直に考えちゃったけど、そもそも画面単位じゃなくてコンポーネントツリー単位だから画面閉じたら解放してくれるとか期待するのが間違ってるよねって話。
最大論理ビュー数を超えても破棄されないのは罠な挙動だけど。
そもそもセッション長く保持するな&巨大にするな
参考文献
jsf-2 – セッションが期限切れになるまで期限切れの@ViewScoped Beanが破棄されない理由
stack overflow : JSF 2.2 Memory Consumption: Why does Mojarra keep the ViewScoped Beans of the last 25 Views in Memory?