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

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

--> つづく

2010年4月22日木曜日

Effective javaまとめ4 重複したオブジェクトを生成するのを避ける2

前回の例は、対象となるオブジェクトが初期化後に変更されないため、再利用できることは明白でした。
今回は、アダプターの場合について考えます。

アダプターとは、内部で保持しているオブジェクトに対する代替のインターフェースを提供しながら、その内部で保持しているオブジェクトへの委譲を行っているオブジェクトです。

アダプターは内部で保持しているオブジェクトの状態以外の状態をもっていないため、ある特定のオブジェクトに対するアダプターのインスタンスを2つ以上生成する必要がありません。

MapインターフェースのkeySetメソッドは、そのマップ内の全てのキーから構成されているMapオブジェクトのsetビューを返します。

keySetメソッドの返すsetビューは、なんど呼び出さいても同一のインスタンスを返して問題ありません。
なぜなら、setビューが内包しているMapオブジェクトは、同一のインスタンスであるべきだからです。
setビュー経由で操作したMapオブジェクトの変更は、全てのsetビューで共有されるべきです。

以下はHashMapのkeySetメソッドの実装です。
------------------------------------------------------------

public Set keySet() {
Set ks = keySet;
return (ks != null ? ks : (keySet = new KeySet()));
}

------------------------------------------------------------
HashMapのフィールド変数であるkeySetが初期化されていない場合のみKeySetのインスタンス化を行っています。

本項目は、オブジェクトの生成はコストがかかるため避けるべきである、ということを意味しているわけではありません。
コンストラクタで明示的な処理をしていないオブジェクトの生成はほとんどコストがかかりません。(家マシンでは100万回インスタンス化して、26ms程度)また、オブジェクトをつかいまわすことで、思わぬバグやセキュリティホールを埋め込む可能性があります。また、プログラムの保守性、可読性も低下する可能性があります。

明らかにコストがかかるオブジェクト(DBコネクション生成等)や、複数のオブジェクトを生成することに全く意味が無い場合に、オブジェクトを使いまわすべきです。

--> 終わり

2010年4月17日土曜日

Effective javaまとめ4 重複したオブジェクトを生成するのを避ける1

過去のブログを読み返してましたが、日本語が崩壊しているところが多々ありますね・・。
直そうかと思いましたが、めんどーくさいので放置します。ごめんなさい。

---------------------------------------
項目4 重複したオブジェクトを生成するのを避ける

機能的に同じオブジェクトが必要となる都度、新たに生成する代わりに、1つのオブジェクトを再利用することが、大抵の場合適切です。オブジェクトが不変(状態を変更することができない)であれば常に再利用できます。

不変オブジェクトの代表とも言えるStringを利用した以下の例を見てみます。

---------------------------------------
【パターン1】

String s = new String("kows");

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

これは、実行される度に新たなオブジェクトが生成されます。
これを、以下のように変更します。

---------------------------------------
【パターン2】

String s = "kows";

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

これは、実行される度に新たなオブジェクトを生成せず、同一のStringオブジェクトを使いまわします。どちらのコードが効率が良いでしょうか。

---------------------------------------
【パターン1を100万回繰り返す:重複オブジェクトを生成する】

public static void main(String[] args) {
long start = System.currentTimeMillis();
for(int i = 0; i < 1000000; i++) {
String s = new String("mackagy");
}
long end = System.currentTimeMillis();
System.out.println(end - start);
}

結果:409

【パターン2を100万回繰り返す:オブジェクトを使いまわす】

public static void main(String[] args) {
long start = System.currentTimeMillis();
for(int i = 0; i < 1000000; i++) {
String s = "mackagy";
}
long end = System.currentTimeMillis();
System.out.println(end - start);
}

結果:40

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

オブジェクトを使いまわす方が効率が良いみたいです。

※超余談ですが、パターン1を String s = new String() で実行すると、一番遅いです。
String()は、内部で更にchar配列をnewするためです。

不変クラスにはだいたいstaticファクトリーメソッドが提供されています。自分でクラスを設計する場合も提供するべきです。コンストラクタは呼び出される度に新たなオブジェクトを生成しますが、staticファクトリーメソッドは新たなオブジェクトを生成しません。

不変オブジェクトを再利用する以外にも、決して修正されることが分かっている可変オブジェクトを再利用することもできます。

以下の例を見てください
----------------------------------------
【パターン1】

public class Person {
private final Date birthDate;

public Person (Date birthDate) {
this.birthDate = birthDate;
}

public boolean bornAfter2000() {
Calendar base = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
base.set(2000, Calendar.JANUARY, 1, 0, 0, 0);
Date baseDate = base.getTime();
return baseDate.compareTo(birthDate) >= 0;
}

}
----------------------------------------
bornAfter2000メソッド(役割がかなり変ですがあんまり気にしないでください)は呼び出されるごとに、Calendar、Dateオブジェクトを生成します。しかし、これは毎回生成しなくても良いはずです。(必ず2000年1月1日固定となっているため)

これは、以下のように変更することができます。
----------------------------------------
【パターン2】

public class Person {
private final Date birthDate;
private static Date baseDate;

static {
Calendar base = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
base.set(2000, Calendar.JANUARY, 1, 0, 0, 0);
baseDate = base.getTime();
}

public Person (Date birthDate) {
this.birthDate = birthDate;
}

public boolean bornAfter2000() {
return birthDate.compareTo(baseDate) >= 0;
}
}
----------------------------------------
staticイニシャライザは、クラスがロードされた時点で一度だけ実行されるため、不必要にCalendar, Dateオブジェクトが生成されなくなります。

それぞれ100万回実行すると、パターン1は2912ミリ秒かかりますが、パターン2はたった19ミリ秒です。かなり効果があるといえると思います。

ただし、パターン1ではbornAfter2000が呼ばれない限りCalendar,Dateのインスタンスが生成されることはありませんでしたが、パターン2では必ずbornAfter2000を呼び出さなくても、必ずインスタンスが生成されることになります。このへんはちょっと注意が必要です。

---> 続く

2010年4月12日月曜日

Effective javaまとめ3 インスタンス化不可能を強制

FFくりーちゃーず2が6末に発売。待ちきれません。
http://www.amiami.com/shop/ProductInfo/product_id/158244/
今回のシークレットはなんだろうか・・・

------------------------------------------------------------
項目3 privateのコンストラクタでインスタンス化不可能を強制する

staticなメソッドとstaticなフィールドだけからなるクラスを作成するケースとして、
java.lang.Math、java.util.Arrays、java.Util.Collectionsのようなユーティリティクラスを作成する場合が挙げられます。

こういったクラスはインスタンス化する設計にはなっていません。(インスタンス化するメリットや意味がありません)しかし、明示的にコンストラクタを定義していない場合、コンパイラはpublicでパラメータ無しのデフォルトコンストラクタを提供します。つまり、インスタンス化するためのクラスではないのにインスタンス化が可能になってしまいます。

クラスを抽象化することでインスタンス化不可能を実現しようとしてもうまくいきません。そのクラスのサブクラスでインスタンス化可能とすることができるためです。更に、継承させるために設計されていると使用者に誤解を与えます。

そこで、ベストな方法としてprivateなコンストラクタを定義する、という方法があります。

public class BearBrick {
 private BearBrick() {}
}

「インスタンス化させたくない!」という意図をコメントしておくと親切です。

注意点として、privateなコンストラクタのみを保持するクラスはサブクラスが作成できません。
明示的、あるいは暗黙的に、すべてのコンストラクタはアクセス可能なスーパークラスのコンストラクタを呼び出さなければならないためです。
------------------------------------------------------------

参考に・・・

------------------------------------------------------------
java.lang.Math
------------------------------------------------------------
public final strictfp class Math {

/**
* Don't let anyone instantiate this class.
*/
private Math() {}

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

------------------------------------------------------------
java.util.Arrays
------------------------------------------------------------
public class Arrays {
// Suppresses default constructor, ensuring non-instantiability.
private Arrays() {
}
------------------------------------------------------------

本ではユーティリティクラスに対して言及してましたが、全章でもあったように、シングルトンとして設計されたクラスでもprivateコンストラクタは使用しますですね。

どうでもえーですが、Mathのコンストラクタはprivateなのにjavadeoc形式なんですね。僕もMath派な人です。

2010年4月9日金曜日

oracle データベースバッファキャッシュ2

食中毒になった。色々出てしまった・・。あんなに辛かったのは小6の時に40度の熱が出て以来だ。
来週の水曜の勉強会のために調べた内容を一部公開!

----------------------------------------------------------
■ データベースバッファキャッシュの構成
----------------------------------------------------------
データベースバッファキャッシュは以下の2つのリストで管理されています。
・ 書き込みリスト
・ LRUリスト

書き込みリストは使用済みバッファを保持します。
使用済みバッファとは、「修正されたが、まだディスクに書き込まれていないデータを含むバッファ」を指します。
LRUリストは、使用可能バッファ、使用中バッファおよび書込みリストに移動していない使用済バッファを保持します。
使用可能バッファとは、空、もしくはデータファイルと同期がとれているブロックが含まれるバッファを指します。
使用中バッファとは、現在アクセスされているバッファを指します。
LRUリストに含まれる使用済みバッファは特定のタイミングで書き込みリストへ移動します。

LRUリストにはLRU側とMRU側が存在し、LRU側であるほど、そのバッファに含まれるブロックの要求頻度が低く、
MRU側であるほど、そのバッファに含まれるブロックの要求頻度が高いことを表します。

----------------------------------------------------------
■ データベースバッファキャッシュの仕組み
----------------------------------------------------------
ユーザープロセスからデータの要求があると、まず、サーバープロセスは、データベースバッファキャッシュ内のデータを検索します。
キャッシュ内にデータが見つかった場合(キャッシュヒット)、プロセスは、キャッシュからデータを返します。
キャッシュヒットが発生すると、そのブロックは、LRUリストのMRU側へ移動します。

さらに多くのバッファがMRU側へ移動されるにつれて、アクセスが古いバッファはLRU側へ移動していきます。
(使用頻度の低いバッファほどLRU側へ移動しやすいです)

キャッシュ内にデータが見つからなかった場合(キャッシュミス)、
プロセスはディスク上のデータファイルからキャッシュ内のバッファにデータブロックをコピーする必要があります。
データブロックをコピーするには、コピーしたブロックを保持するためのバッファ(使用可能バッファ)を見つける必要があります。
その際、プロセスはLRUリストのLRU側から使用可能バッファの検索を行います。
検索は、以下の何れかの条件を満たすまで続きます。
・ 使用可能バッファを見つける
・ 検索したバッファ数が制限閾値に達する
LRUリスト検索時に使用済みバッファを見つけた場合、そのバッファを書き込みリストへ移動してから検索を続けます。
この際、書き込みリストのサイズが閾値を越えると、書き込みリスト上の使用済みバッファがDBWRによってディスクに書き込まれます。
使用可能バッファが見つかると、プロセスはディスクからバッファにデータブロックを読み込んで、バッファをLRUリストのMRU側へ移動します ※1

使用可能バッファが見つからず、検索したバッファ数が制限しきい値に達すると、プロセスはLRUリストの検索を停止し、データベースライターに使用済みバッファをデータファイルに書き込むよう指示します。
この際、使用済みバッファの書き込みリストへの移動は発生せず、直接LRUリストからディスクへの書き込みが行われます。
こうすることで、使用可能バッファを確保し、そこにデータブロックを読み込みます。

※1
厳密には、インデックススキャンによるランダムリードが実施された場合はMRU側とLRU側の境目にあるコールドポインタという位置に挿入されます。
フルスキャンによるシーケンシャルリードが実施された場合はLRU側に挿入され、次の瞬間には破棄される状態となります。
こういった仕組みにより、本当に重要なデータがLRUリストから押し出されることを防ぎます。
-------------------------------------------------------------------------------------
実際は図もいれてるからもっとわかりやすいだよ!でも掲載の仕方が分からない。みんなすごいなー

あと、まえの投稿でデータベースバッファキャッシュのキャッシュを消す方法を書いたけど、正確にsqlの性能を調べるには、ディクショナリキャッシュ、ライブラリキャッシュの情報も消す必要があるはずなので、以下のコマンドも実行したほうが良い気がしてきた。

ALTER SYSTEM FLUSH SHARED_POOL
(共有プールのクリア)

本番環境で・・(略

2010年4月5日月曜日

oracle データベースバッファキャッシュ1

来週の水曜にプロジェクトで実施してる勉強会の発表があるので、えふぇくちぶjavaが停止中。
ブログの更新を止めたくないのでjava意外の話題で更新してみます。

---- oracle データベースバッファキャッシュについて ----

データベースバッファキャッシュとは、ディスク(データファイル)への書き込み、読み込みを減らす(ディスクI/Oの低減)目的で使用されるメモリ領域。

ユーザープロセスからデータの要求があると、まず、サーバープロセスは、データベースバッファキャッシュ内のデータを検索する。

キャッシュ内にデータが見つかった場合(キャッシュヒット)、サーバープロセスは、データをメモリから直接読み取ることができる。

キャッシュ内にデータが見つからなかった場合(キャッシュミス)プロセスはデータにアクセスする前に、 ディスク上のデータファイルからキャッシュ内のバッファにデータブロックをコピーする必要がある。

キャッシュヒットによるデータのアクセスは、ディスクI/Oが発生しないため、キャッシュミスによるデータのアクセスよりも高速。

SQLの性能を調べてる時とか、キャッシュをクリアしないと正確な実行時間がわからないので注意。

ALTER SYSTEM FLUSH BUFFER_CACHE

でデータベースバッファキャッシュがクリアされまする。(10gから)
9iの場合はインスタンス停止が必要らしい。ふべんだね!
注意:データベースバッファキャッシュは全てのプロセスで共有されているため、本番環境とかでやると死ねます。