戯言

つらつらと気づいたことを書いていきます。人狼とか。

OutOfMemoryを発生させない実装上の注意点


Javaはガベージコレクションは自動で行われますが、ガベージコレクションの対象は参照されなくなったオブジェクトのみであるため、適当なプログラムを組むといつまでも参照される可能性があるオブジェクトが残り続けて、OutOfMemoryが発生してしまいます。
きちんと、使わなくなったオブジェクトはそれを明示することで、ガベージコレクションの対象としてあげることが重要です。
実際の実装で注意した内容(メモリリークが発生していたが改善を行った内容)を列挙しておきます。

1.データベースのコネクションをcloseしているか

SQLを発行するメソッドのtry~catch~finally句は、以下のように書きます。
finally句にcloseを書き、例外発生時も実行されるようにします。
また、各close処理ごとに分けてtry~catch句で囲み、非nullチェック後に実施します。
Connection conn = null;
Statement state = null;
ResultSet rs = null;

String query = "select * from TBL_RECORD where ~";
try {
	conn = createConnection(); //コネクション作成するprivateメソッド
	state = conn.createStatement();
	rs = state.executeQuery(query); //SQL実行
} catch (SQLException ex) {
	ex.printStackTrace();
} finally {
	try {
		if (rs != null) rs.close();
	} catch (Exception ex) {
		ex.printStackTrace();
	}
	try {
		if (state != null) state.close();
	} catch (Exception ex) {
		ex.printStackTrace();
	}
	try {
		if (conn != null) conn.close();
	} catch (Exception ex) {
		ex.printStackTrace();
	}
}


2.newしたスレッドが不要になった場合に、終了処理をしているか

Linuxの場合、次のようにprocを利用して、現時点のスレッド数を確認できます。
細かく確認したい場合はjconsoleを使用して下さい。

ls -l /proc/(tomcatのPID)/task | wc -l

スレッド数が増え続けている場合は、停止していないスレッドが存在しています。
スレッド1つで500KB程度の使用であっても、2000個貯まれば1GB使ってしまいます。

Caused by: java.lang.OutOfMemoryError: unable to create new native thread
at java.lang.Thread.start0(Native Method)
at java.lang.Thread.start(Thread.java:640)
が発生している場合は、ネイティブメモリが不足しています。


3.実行に時間がかかる(10秒以上)SQLがないか

大容量のI/Oでは対象データの一時保持用のバッファ領域を多く必要とするため、Cヒープを圧迫してOutOfMemoryを誘発する場合があります。また、SQLの書き方が悪くて、実行に時間かかっている場合も同じことが発生します。
100万件程度のテーブルしかないのに10秒以上も要すSQLがある場合は、インデックスが適切で無いことが多いです。簡単な調べ方として、使用しているはずのインデックスを削除してSQL実行時間を測ります。実行時間に変化がなければ、そのインデックスは使われていません。(実行計画とる前に手軽に判別できます)


4.File入出力部分で、入出力関連オブジェクトをcloseしているか。大きなファイルをオープンしていないか



Windows上のEclipseで実行しているTomcatに対してjconsoleを使う方法


Eclipseで[実行]→[実行構成]→Apache Tomcatの引数タブのVM引数に以下を設定
-Dcom.sun.management.jmxremote.port=22222(ポート番号は任意)
-Dcom.sun.management.jmxremote.ssl=false
-Dcom.sun.management.jmxremote.authenticate=false

DOSプロンプトで、
> cd %JAVA_HOME%\bin
> jconsole

jconsole

ローカルプロセスのBootstrap を選択して、「接続」
※PIDはタスクマネージャからも調べられます

Jconsole実行後

こんな感じで、スレッド数やメモリ使用量を確認できます。

derbyのバックアップとリストア


バックアップ

ijからもできますが、javaプログラム内から定期的に自動で取るために、以下のようなコードを組みました。
import java.sql.CallableStatement;
import java.sql.Connection;

String backupDirectory = "・・・・・・";
File dir = new File(backupDirectory);

Connection conn = createConnection();  // DB接続用オブジェクトを取得するprivateメソッド
String query = "CALL SYSCS_UTIL.SYSCS_BACKUP_DATABASE(?)";
CallableStatement cs = conn.prepareCall(query);
cs.setString(1, backupDirectory);
cs.execute();

リストア

ijから
connect 'jdbc:derby:(戻し先のパス);restoreFrom=(バックアップのパス)';

例えば、以下の様な感じです。
connect 'jdbc:derby:C:\db\werewolf;restoreFrom=C:\dbBackup\2013.08.15\werewolf';

この1命令だけでOKです。手軽です。