2010年5月29日土曜日

Effective javaまとめ6 ファイナライザを避ける3

ipad発売。
ほしいなぁ・・。

-----------------------------------------------------
Effective javaまとめ6 ファイナライザを避ける3

ファイナイライザの正当な使い方は何かあるか、というとこまで話しました。
正当な使い方は以下の通りです。

■安全ネットとして使う
前回説明した明示的終了メソッドの呼び出しを、オブジェクトの所有者が忘れた場合の「安全ネット」として振舞うことです。

ファイナイライザが即座に実行される保証が無いとはいえ、クライアントが明示的終了メソッドを呼び出すことによる契約の終わりを守りことに失敗した場合に、重要な資源を決して解放しないより、後で解放する方が良いです。

身近な明示的終了メソッドの例として、InputStream, OutputStreamクラスではcloseメソッドが提供されていますが、これらのクラスは、安全ネットとしてのファイナライザを保持しています。
(だからといって、明示的終了メソッドをクライアントが呼び出さないで良いわけではないです。なんども記載していますが、ファイナライザが呼び出されるタイミングは不定であるため、パフォーマンスに悪影響を及ぼすと予想されます)

FileOutputStreamのファイナライザはこんなかんじです。
------------------------------------------------
// ファイルをオープンしているとオブジェクトがはいっているぽいです
private FileDescriptor fd;

protected void finalize() throws IOException {
if (fd != null) {
if (fd == fd.out || fd == fd.err) {
flush();
else {
close();
}
}
------------------------------------------------

■ネイティブピアを回収する。
ネイティブピアとは、通常のオブジェクトがネイティブメソッドを通して委譲を行うネイティブオブジェクトです。
ネイティブピアは通常のオブジェクトではないので、ガーベージコレクタはネイティブピアについて知りませんし、通常のオブジェクトが回収される時に、ネイティブピアを回収することもできません。
ネイティブピアが重要な資源を保持していないと仮定すると、ファイナライザはネイティブピアを回収する処理を行うのに適切な手段です。


さて、ファイナライザに関して注意すべき点を記します。

「ファイナライザ連鎖」は自動的に実行されません。
Object以外のクラスがファイナライザを持ち、サブクラスがそれをオーバーライドしているならば、そのサブクラスのファイナライザはスーパークラスのファイナライザを手作業で呼び出さなければなりません。
tryブロック内でファイナライザを呼び出し、finallyブロック内でスーパークラスのファイナライザを呼び出すべきです。それにより、確実にサブクラスのファイナライザを呼び出すことができます。

サブクラスの実装者がスーパークラスのファイナライザを呼び出すのを忘れた場合、もしくは悪意を持って呼び出すことを行わなかった場合、スーパークラスのファイナライザは絶対に呼び出されません。

上記のような事象を防御するための方法として、ファイナライズを行う必要があるクラスにファイナライザを書く代わりに、エンクロージングインスタンス(内部クラスにとっての外部クラスのインスタンス)をファイナライズする事だけを目的とした無名クラスにファイナライザを書く、という方法が挙げられます。

その無名クラスの1つのインスタンスは、ファイナライザガーディアン(カコイイ!)と呼ばれ、エンクロージングクラスの個々のインスタンスに対して生成されます。

エンクロージングインスタンスは、privateのインスタンスフィールドにそのファイナライザガーディアンへの唯一の参照を保持するようにします。それにより、ファイナライザガーディアンはエンクロージングインスタンスと同時にファイナライズ対象となります。

ガーディアンがファイナライズされる時に、ガーディアンのファイナライザが、エンクロージングクラスに対するメソッドであるかのように、ガーディアンはエンクロージングインスタンスに必要なファイナライズ処理を行います。

ファイナライザガーディアンのイメージ
---------------------------------------------
public class IamAHero {
 private final Object finalizeGuardian = new Object() {
  protected void finalize() throws Throwable {
   // IamAHeroをファイナライズする
  }
 }
}
---------------------------------------------

この技法は、ファイナライザを持つ全てのfinalではないpublicのクラスに対して検討すべきです。

■まとめ
安全ネット、あるいはネイティブピアオブジェクトを解放する以外の目的でファイナライザを使用するべきではありません。
使用するときは、スーパークラスのファイナライザを呼び出すことを忘れてはいけません。
publicでfinalではないクラスにファイナライザを提供する必要がある場合、ファイナライザガーディアンの使用を検討してください。

----> おわり

2010年5月18日火曜日

Effective javaまとめ6 ファイナライザを避ける2

チャリンコを家の中で保管することに成功しますた。
後輪を外すのが意外と簡単で感動した。そしてBBをピカピカにした。
快感・・・

-----------------------------------------------------
■項目6 ファイナライザを避ける2

ファイナライザの使用を避けるべき理由は前回の内容以外にまだあります。

ファイナライズの最中に例外が発生した場合、その例外は無視され、ファイナライズは終了します。
キャッチされなかった例外は、そのオブジェクトを不正な状態のままにする可能性があります。
また、通常、キャッチされなかった例外は、スレッドを終了させスタックトレースを表示しますが。ファイナライザ内ではそのようになりません。ワーニングさえ表示しません。

さて、ファイナライザの使用を控えるべき理由を記載してきましたが、ファイルやスレッドなどの終了を必要とする資源を持っているオブジェクトのクラスに対して、ファイナライザを各代わりに、何をするべきでしょうか。

明示的終了メソッドを提供すべきです。

各インスタンスが必要なくなった時点で、クライアントに明示的終了メソッドを呼び出すことを要求するのです。注意すべき点は、そのインスタンス自身が、「自分は終了した」という事実を保存しておく必要があるという点です。終了した後にオブジェクトが利用された場合、IllegalStateExceptionをスローすべきです。
(APIによると、IllegalStateExceptionは、不正または不適切なときにメソッドが呼び出されたことを示します。)

明示的終了メソッドの典型的な例は、InputStreamやOutputStreamに対するcloseメソッドです。

明示的終了メソッドは、大抵try finallyブロックと伴に利用されます。
finallyブロック内で明示的終了メソッドを呼び出します。
以下の2点がメリットとして挙げられるためです。

 ・ オブジェクトが必要な範囲をtry finallyブロックで囲むことで、オブジェクトが不要になり次第、迅速に後始末ができる。
 ・例外が発生しても確実に後始末を行うことが可能

では、ファイナライザが有効なときはどんな時でしょうか・・。

正当な使い方は2つあります。

--> つづーく

2010年5月13日木曜日

Effective javaまとめ6 ファイナライザを避ける1

忙しいほど覚醒するパターン

---------------------------------------------------
■項目6 ファイナライザを避ける

ファイナライザには有効な使用方法が多少ありますが、基本的には使用することを避けるべきです。

ファイナライザはC++のデストラクタと違い、呼び出されるタイミングを予測することができません。

デストラクタは、オブジェクトに関連付けられているメモリ領域を回収する通常の方法として利用されます。
javaではオブジェクトが到達不可能な状態になると、ガーベージコレクタにより自動的に関連付けられているメモリ領域が回収されます。

デストラクタは、メモリ以外の資源を回収するためにも使用されます。
javaでは、それらの目的で、try finally ブロックを利用します。

オブイェクトが到達不可能な状態になってからファイナライザがjvmによって実行させるまでの時間は、どれだけの長さにもなりえます。jvm実装ごとに大きく異なっています。
これは、時間的な制約のある事象をファイナライザで行うべきではないことを示してます。

クラスにファイナライザを提供することで、そこクラスのインスタンスの回収が遅れる可能性もあります。
ファイナライザを実行するためのスレッドが、そのアプリケーションの他のスレッドより低い優先順位で動作している場合、オブジェクトがファイナライズ対象となる速さが、オブジェクトがファイナライズされる速度を上回る可能性があります。そうすると、どんどん回収されないメモリ領域が増えていき、最終的にアウトオブメモリーとなる可能性があります。

さらに、ファイナライザは必ず実行されるとは限りません。(javaの言語仕様では、ファイナライザの実行を保証していないためです)結果として、ファイナライザで重要な永続性のある状態を更新するような処理をお行うことは望ましくない、と言えます。

System.gc()やSystem,runFinalizer()は、ファイナライザが実行される可能性を高めるものではありませうが、実行されることを保証するものではありません。System.runFinalizersOnExit()とRuntime.runFinalizeOnExit()は、ファイナライズを保証するらしいです。ただ、致命的な欠陥があるようで、推奨されていません。(どんな欠陥かはなんとなくしか調べなかったので記載しません)

---> 続く

2010年5月11日火曜日

Effective javaまとめ5 廃れたオブジェクト参照を取り除く3

ミッドナイトミートエクスプレスという映画を見ました。
ウェスクレイヴンというホラー映画監督のです。
ほんとーに久々におもろかったでぇ

前回の自分のブログを読んでみましたが、とても意味がわかりにくい・・
自分が理解するより、アウトプットする方が難しいです・・

------------------------------------------------------
■項目5 廃れたオブジェクト参照を取り除く3

前回、意味をなさなくなったキャッシュオブジェクトをガーベージコレクトする方法を1つ記しました。
今回はもう1つの方法を記します。

それは、「オブジェクトをキャッシュしてからある程度の時間が経過したら、そのキャッシュオブジェクトを破棄する」という方法です。

上記は、java.util.Timer APIを使用し、バックグランドのスレッドにより行うこともできますし、java.util.LinkedHashMapのremoveEldestEntryメソッドのように、キャッシュに新たなオブジェクトを追加した際の副作用として実現することもできます。

LinkedHashMapのputメソッドは、自身に新たなエントリを追加する際、removeEldestEntryを呼び出します。
removeEldestEntryがtrueを返した場合、自身のキャッシュに含まれる一番古いオブジェクトを削除します。
removeEldestEntryはデフォルトではfalseを返しますので、オーバーライドし、目的に応じてtrueを返すようにします。javadocでは以下のような実装方法をサンプルとして挙げています。

----------------------------------------------------
private static final int MAX_ENTRIES = 100;

protected boolean removeEldestEntry(Map.Entry eldest) {
return size() > MAX_ENTRIES;
}
----------------------------------------------------

特定のサイズまでキャッシュしたら、以降は古いものから破棄していく、という方法です。
これなら、キャッシュがどんどん肥大していくことは無いです。

--> なんかまとまりませんでしたが、おわりです。

2010年5月8日土曜日

Effective javaまとめ5 廃れたオブジェクト参照を取り除く2

いろいろ忙しい・・。
友達が家の近くに引越してきました。
意外と家賃が安くてショックです。

-----------------------------------------------------
■項目5 廃れたオブジェクト参照を取り除く2

前回、廃れた参照を排除する最善の方法は、その参照が含まれた変数を再利用するか、その変数のスコープ外にでること、という事まで書きました。

現在のjvm実装では、変数が定義されているブロックから抜けるだけでは充分ではなく、参照を消すためには、その変数が含まれるメソッドから抜け出す必要があるので注意が必要です。

では、どのような時に参照にnullを設定するべきでしょうか。

簡単に言えば、前回例として記述したstackのように、プログラマが独自にメモリ管理を行っている場合です。
stackは、配列のある要素が無効になった場合、そのことをガーベージコレクタに知らせるためにnullを設定します。
それ以外、ガーベージコレクタがstackの要素が無効になった事を知る術はありません。つまり、nullを設定しないと、そのメモリは開放されないため、メモリリークの原因となります。

上記以外によくあるメモリリークの原因は、キャッシュです。

オブジェクト参照を一旦キャッシュにいれてしまうと、オブジェクト参照がそこにあることを忘れがちですし、そのオブジェクト参照の意味がなくなったかなり後でも、キャッシュに残したままにしがちです。

この問題に対しては、2つの解決方法があります。

1つは、キャッシュ対象のエントリに対するキーへの参照を、キャッシュ自身以外が保持していることに意味がある場合、そのキャッシュをWeakHashMapで実現することです。

WeakHashMapはキーを弱参照で保持するため、エントリーに対するキーへの参照がキャシュ外に存在しなくなった場合、そのエントリーはガーベージコレクト対象となります。
他のMapはキーを強参照で保持するため、上記のような状態となっても、エントリーはガーベージコレクト対象となりません。

弱参照と強参照の違いは、以下のコードを見るとわかりやすいです。
-----------------------------------------------------------
■ 実験コード

1 Integer key = new Integer(1);
2
3 Map cache = new HashMap(); //キャッシュをHashMapで実装
4 cache.put(key, "entry");
5
6 key = null; //エントリーに対するキー値の参照にnullを設定
7
8 System.gc(); //強制的にGCを実施
9
10 System.out.println(cache.get(new Integer(1)));
-----------------------------------------------------------
実行結果として、「entry」が出力されます。
3行目のキャッシュをWeakHashMapで実装した場合、「null」が出力されます。
6行目でエントリーに対するキーへの参照が存在しなくなるため、紐づくエントリーがガーベージコレクトされたためです。

---> 更に続く・・意外と長い;;

2010年5月1日土曜日

Effective javaまとめ5 廃れたオブジェクト参照を取り除く1

京都は良い。

--------------------------------------------------
■項目5 廃れたオブジェクト参照を取り除く

javaはガーベジコレクションを持つ言語であるため、メモリ管理は意識しなくなりがちですが、それは問題があります。

例えば、スタック実装を持つ以下のクラスを見てください。

--------------------------------------------------

public class SampleStack () {

 private Object[] stack = new Object[] {};
 private int size = 0;

 public void push (Object obj) {
  stack[size++] = obj;
 }

 public Object pop () {
  return stack[--size];
 }

}
--------------------------------------------------

※sizeが0の時popすると落ちますが、例なので気にせずに・・。

このプログラムはメモリリークを抱えています。
どこが問題なのでしょうか。

・・・・。

stackが大きくなった後に小さくなっても、stack自身がそのオブジェクトに対する参照を保持し続ける、という点です。

例えば、pushが10回行われ、popが5回行われたとします。
このとき、stack[5] ~ stack[9]に格納されているオブジェクトは外部のプログラムからアクセスする方法はなくなります。しかし、stack自身は参照を保持し続けているため、これらのオブジェクトはガーベージコレクト対象となりません。

あるオブジェクト参照が保持され続けると、そのオブジェクトが参照しているオブジェクトも回収されず、更にその先も回収されません。

上記に対する解決方法は単純です。

参照が不要になった時点で、参照にnullを設定すれば問題ありません。

改善するとこんな感じです。
--------------------------------------------------
 public Object pop () {
stack [--size] = null;
  return stack[size];
 }
--------------------------------------------------

nullを設定する利点はもう1つあります。

もし使用する意図のない参照を誤って使用した場合、プログラムは間違った挙動をとらず、ヌルポが発生するため、プログラミングエラーを早めに発見するこができる、という点です。

しかし、nullを設定する、という対処方法をいつも採用すれば良いわけではありません。
なぜなら、上記対処はプログラムを不必要に複雑にし、パフォーマンスが低下することも考えられるからです。

最善の方法は、その参照が含まれていた変数を再利用するか、その変数のスコープの外に出ることです。

--> つづく