2014年10月27日

RE:FileReader が必ず close されるのかどうか

Codeへの愛とCuriosity:FileReader が必ず close されるのかどうかの続き について
twitterでコメントするには書ききれなかったので久しぶりにここを使う
まずは鍋谷氏のブログを読んで頂きたい
以下の文はそれを前提にして記述している

そもそもJava7のtry-with-resourcesはinterface Closeableを保持しているclassで使用できる記述で(javap -cで逆アセンブルした限りでは)こちらのinterfaceを保持しているclassはtryブロック終了時にコンパイラが自動的にcloseメソッドをコールする記述を埋め込んでくれるようだ
このcloseの順番は、どうやら「最後から順にclose」のようなので例えば
FileReader fr = new FileReader(path);
BufferedReader br = new BufferedReader(fr);

をtry-with-resourcesに入れた場合closeの順はBufferedReader→FileReaderとなる
またcloseタイミングはGCタイミングではなくtryブロック終了(=finallyブロック)のタイミングであることにも注意が必要だ(もしGCタイミングでcloseするのであればgcの明示的発生を記述しないと「ファイル読んで同じファイルに書いて」という処理を記述すると後者がopen lock状態のため書けないということになってしまう)
なおBufferedReaderのcloseの実装はコンストラクタ引数のinに対して
synchronized (lock) {
 if (in == null)
  return;
 in.close();
 in = null;
 cb = null;
 }

となっている

さて
FileReader fr = new FileReader(path);
BufferedReader br = new BufferedReader(fr);


BufferedReader br = new BufferedReader(new FileReader(path));

という記述での動作だがtryブロック終了時の動作は
上の場合:
・BufferedReaderのcloseによってReaderがcloseされる
・FileReaderのcloseによってReaderをcloseしようとする(がcloseできない)
下の場合:
・BufferedReaderのcloseによってReaderがcloseされる
となる
ではFileReaderのcloseが何をやっているのかも追いかけてみる
実はFileReaderはInputStreamReaderをextendsしておりclose処理はoverrideせずにInputStreamReader.close()を継承利用している
InputStreamReaderのcloseは内部保持しているStreamDecoderのcloseを行っている
StreamDecoderのcloseは
synchronized (this.lock) {
 if (!this.isOpen)
  return;
 implClose();
 this.isOpen = false;
}

となっている
今回のケースではここでsynchronized lockを使用している意味はない(コード上シリアル処理になるため)がthis.isOpenをチェックしているのは前者のようにBufferedReaderでcloseしておきながらFileReaderでもcloseするという処理をされたときに余計なcloseを起こさないためと思われる

さて実際の処理としてFileReaderの生成に成功しBufferdReaderの生成に失敗した場合
これはメモリが足りなくなったケース以外は考えにくくFullGCを用いてもメモリが解放できないという状態だ
このようなケースには遭遇したことがないため想像になってしまうことを断りつつ前者と後者ではどのような動きの違いがあるのか考えてみよう

前者の場合FileReaderはclosableインターフェースのためcloser処理が走る
具体的にはFileReader.close()すなわちInputStreamReader.close()が呼ばれるがこのような状況においてはclose処理に必要なメモリが確保されている保証はなくclose用のメモリがあったとしてもStreamDecoder.close()が処理できるメモリが確保されている保証がない(特にsynchronized lockコストが払えるのか疑問)。
よって前者のような記述をしたとしてもFileReaderがcloseされる保証はない
後者の記述の場合BufferedReaderは生成されておらず(逆アセンブルした限りでは)FileReader.close()はコールされずリソースリークあるいはファイルロックが発生するものと思われるが例えcloseされたとしてもサービスを継続することは不可能で結局はプロセスを落とす(=リソースリークもファイルロックも解放される)しか打つ手はない
これはマルチスレッド環境であっても同様でGCエリアは全てのスレッドで共通のためWebサービスなどのマルチスレッド環境であれば既にあちこちでOut Of Memory Errorが多発しておりもはやサービスとして成立しなくなっているため同様にプロセスを落とすしかない
つまり異常系の場合はFileReaderとBufferedReaderのnewを分けたとしてもBufferedReaderの生成に失敗するような状況では結果としては分けない場合と同じこと(サービスの継続は不可能。プロセスを落とすしかない)になることが想定される

では正常系の場合はどうだろう
前者の場合は余分なメソッド遷移をした挙句StreamDecoderでsynchronized lockコストを払って実際には何もしない
後者の場合は当然ながら余分なメソッド遷移もsynchronized lockコストも発生しない

以上より前者の記述と後者の記述では
・リソースリークの危険度はたいして変わらない。
・前者のほうがコスト高
ということが言え2つに分けて記述する意味があるとは思えない

なおjava.nio.file.Files.newBufferedReaderの内部実装でもtry-catchしないままBufferedReaderを生成しており後者の記述と同じ動作になることを記述しておく
posted by もな兄 at 16:37| Comment(0) | TrackBack(0) | 日記

2012年02月08日

ちょうど1年

あれから1年の月日が流れた
その間に色々なことが起こり色々なことが変わって行った
私もまた変わった

もなみの誕生日が来たということはバレンタインが近い
ちゆデーが近いということだな

さてどうしよう
posted by もな兄 at 09:27| Comment(7) | TrackBack(0) | 日記

2011年02月08日

もなみ9歳の作り方

もなみ9歳は作れない。

ちょっと待て、タイトル詐欺じゃないか!と思う気持ちはよく判るが、まあ最後まで読んでくれ。

プロダクトライフサイクルという考え方がある。
経済用語なので知らない人もいるかも知れないので、簡単に説明すると、ビジネスにおいては、商品は「導入期」「成長期」「成熟期」「衰退期」の4つのフェーズに分かれるという考え方だ。
このへんをさらに拡張するとBCGのプロダクトポートフォリオなんかになったりするのだがそれはちょっと脱線しすぎなので置いておいて、このプロダクトライフサイクルはサイト運営にも適用できるのだ。
そこで、VNIサイトを例に、プロダクトライフサイクルを説明しよう。
まず、ちゆ12歳が誕生したのが「導入期」だ。
そして、量産型VNIが増殖した時期が「成長期」。
RO系VNIが大量に誕生した時期が「成熟期」。
現在は「衰退期」しかも末期に近い。
サイトを運営する場合は、こういう「大きな括り」で、自分が「どの期にいるのか」を意識する必要がある。
「導入期」において重要なことは、自分が参入しようとしているサイト形態がどう進化するのか、またどのくらい成長しうるかという嗅覚だ。
この嗅覚がないと、参入してもあっという間に廃れてしまうようなサイト形態を取ってしまうことになる。
「成長期」において重要なことは、他者との差別化だ。
成長期では既に多数のライバルサイトが存在しており、自サイトが埋没しないように他サイトには無い特色を打ち出し、次に来る成熟期での過当競争を生き抜くための準備が必要なのだ。
「成熟期」において重要なことは、維持する力だ。
他サイトとの生存競争で生き残るためには、自らが打ち立てた特色をさらに伸ばし、他の追随を許さないナンバーワンサイトになるしかない。
また、この時期に参入するのは非常に危険だが、どうしても参入するのであれば、まだ誰も手を出していないようなエリアで参入し、オンリーワンを目指すべきだろう。
ただし、その労力があるなら別の導入期や成長期のサイトに力を注いだほうたいいだろう。
「衰退期」において重要なことは、いつ撤退するかのタイミングだ。
衰退期の初期に撤退すれば綺麗に終われるが、タイミングをずるずると伸ばし続ければ、よほどのサイトでない限り閉鎖しても気づかれないような状況になってしまうだろう。
この時期に新規参入したいのであれば、他の成長期の形態と組み合わせる等の工夫を凝らす必要がある。
もなみ9歳の誕生はVNIの歴史における「導入期」から「成長期」にかけてであり、実質的な更新が始まったと言える「ぽぉたる」に移った頃も、末期とはいえまだ「成長期」だった。
そういう時代、温度だったからこそ、もなみ9歳は誕生しえたし、発展しえた。
「衰退期」のしかも末期の現在では、もなみ9歳の当時のスタッフを集めたとしても同じことはできない。
おそらく、まったく別の形のサイトになっただろう。

だから、もう二度と、もなみ9歳は作れないのだ。
posted by もな兄 at 00:00| Comment(0) | TrackBack(0) | 日記