...

Java Webアプリケーションを 高速化する技術 2009年11月13日(金曜日)

by user

on
Category: Documents
163

views

Report

Comments

Transcript

Java Webアプリケーションを 高速化する技術 2009年11月13日(金曜日)
Java Webアプリケーションを
高速化する技術
2009年11月13日(金曜日)
渋谷テクニカルナイト
日本アイ・ビー・エム(株) 東京基礎研究所
高瀬俊郎
自己紹介
•
•
•
•
高瀬俊郎(たかせとしろう)
2000年入社
東京基礎研究所 主任研究員
XML, Webサービスのパフォーマンス最適化の
研究に従事
• 情報学博士
– 「WebサービスにおけるXML処理の高速化」(2008)
はじめに
•
•
ここでは研究の話は(あまり)しません
研究の過程で得た雑多な(しかし、おそらく多くの人にとって有用な)高速化
の知識の話をします
•
コードの可読性>コードの高速化
– コードの可読性とコードの高速化はしばしば両立しない
– コードの可読性、保守性を優先し、高速化は必要不可欠なものにとどめる
• 高速化のための変更をしたら必ず効果を確認する
• 効果のなかった高速化のための変更は必ず元に戻す!
•
現時点で効果のある高速化手法を過信しない
– 現在の環境で効果のある高速化手法が将来の環境でも効果があるとは限らない
•
Disclaimer(免責事項)
– この資料は日本アイ・ビー・エム株式会社の正式なレビューを受けていません
目次
•
HTTP関連
–
–
–
–
–
–
•
Java関連
–
–
–
–
–
–
–
–
–
•
高級言語と高速化
処理時間の計測と副作用
JITコンパイラの挙動
ガベージコレクション
オブジェクトの生成
同期化の有無
バッファの利用
不変オブジェクト
可変長のデータ
XML関連
–
–
–
–
–
–
•
キャッシュ検証(Validation)モデル: If-Modified-Since, 304 Not Modified
キャッシュ期限(Expiration)モデル: Cache-Control, Expires, Pragma
圧縮: Accept-Encoding, Content-Encoding: gzip
メッセージサイズの指定: Content-Length, Transfer-Encoding: chunked
※紛らわしいヘッダ: Content-Transfer-Encoding (MIMEヘッダ)
持続的接続: Connection: Keep-Alive
DOMパーサ
SAXパーサ
StAXパーサ
再使用可能
XMLパーサプール
JAX-WS, JAXB
Coffee break
–
–
仕様を読もう
ソースコード,javadocを読もう
目次
• HTTP関連
–
–
–
–
–
–
キャッシュ検証(Validation)モデル: If-Modified-Since, 304 Not Modified
キャッシュ期限(Expiration)モデル: Cache-Control, Expires, Pragma
圧縮: Accept-Encoding, Content-Encoding: gzip
メッセージサイズの指定: Content-Length, Transfer-Encoding: chunked
※紛らわしいヘッダ: Content-Transfer-Encoding (MIMEヘッダ)
持続的接続: Connection: Keep-Alive
キャッシュ検証(Validation)モデル
IF-Modified-Since, 304 Not Modified
•
キャッシュ検証(Validation)モデル
–
–
•
キャッシュを使用してよいかを最終更新日時を用いて検証する
検証のためにメッセージのやり取りは必ず起きる(レスポンスボディは空になる)
1回目
–
リクエスト
•
–
GETリクエスト
レスポンス
•
Last-Modifiedヘッダ
–
•
•
レスポンスボディあり
2回目
–
リクエスト
•
•
GETリクエスト
If-Modified-Sinceヘッダ
–
–
例: If-Modified-Since: Sat, 29 Oct 1994 19:43:31 GMT
レスポンス
•
•
•
例: Last-Modified: Sat, 29 Oct 1994 19:43:31 GMT
HTTPステータスコード 304 Not Modified
レスポンスボディなし(キャッシュを使う)
※日時のかわりにエンティティタグ(そのコンテンツに固有の値)を使う方法もある
–
Last-ModifiedのかわりにETag, If-Modified-SinceのかわりにIf-None-Matchヘッダを使う
•
例: ETag: "1dba6-131b-3fd31e4a", If-None-Match: "1dba6-131b-3fd31e4a"
IF-Modified-Since, 304 Not Modified の
メッセージ例
リクエスト(1回目)
GET /sample.html HTTP/1.1
Accept: */*
Accept-Language: ja
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0)
Host: 127.0.0.1:9081
Connection: Keep-Alive
レスポンス(1回目)
HTTP/1.1 200 OK
Last-Modified: Thu, 27 Sep 2007 06:02:48 GMT
Date: Thu, 27 Sep 2007 08:02:23 GMT
Content-Type: text/html
Content-Length: 45
Content-Language: ja-JP
<html><body>
Hello world!
</body></html>
リクエスト(2回目)
GET /sample.html HTTP/1.1
Accept: */*
Accept-Language: ja
Accept-Encoding: gzip, deflate
If-Modified-Since: Thu, 27 Sep 2007 06:02:48 GMT
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0)
Host: 127.0.0.1:9081
Connection: Keep-Alive
レスポンス(2回目)
HTTP/1.1 304 Not Modified
Content-Language: ja-JP
Content-Length: 0
Date: Thu, 27 Sep 2007 08:42:14 GMT
IF-Modified-Since, 304 Not Modified の
Servletでの処理例
public class SampleServlet extends HttpServlet {
private long lastModified;
public void doGet( HttpServletRequest request, HttpServletResponse response )
throws ServletException,IOException {
long ifModifiedSince = request.getDateHeader("If-Modified-Since");
if(lastModified <= ifModifiedSince) {
response.setStatus( HttpServletResponse.SC_NOT_MODIFIED );
return;
}
If-Modified-Sinceヘッダのチェック
ステータスコード 304 Not Modifiedのセット
PrintWriter out = response.getWriter();
out.println("<html><body>Hello world!</body></html>");
response.setDateHeader("Last-Modified", lastModified);
response.setStatus( HttpServletResponse.SC_OK );
Last-Modifiedヘッダのセット
}
}
HttpServlet.doGetでの実装例
ServletFilterでも実装可能
または
HttpServlet.getLastModifiedをオーバーライド
しておくとServletエンジンが処理してくれる
public class SampleServlet extends HttpServlet {
private long lastModified;
public void init() throw ServletException {
lastModified = System.currentTimeMillis()/1000*1000;
...
}
public long getLastModified(HttpServletRequest request) {
return lastModified;
}
}
キャッシュ期限(Expiration)モデル
Cache-Control, Expires, Pragma
•
キャッシュ期限モデル
– キャッシュの有効期限を明示的に指定
•
HTTP/1.1
– Cache-Controlヘッダ
• Expires, Pragmaヘッダに優先
• 例: Cache-Control: no-cache
– キャッシュの使用禁止
• 例: Cache-Control: max-age=3600 (秒)
– コンテンツが有効な最大経過時間
•
HTTP/1.0
– Expiresヘッダ
• コンテンツの有効期限を指定
• 例: Expires: Thu, 01 Dec 1994 16:00:00 GMT
– Pragmaヘッダ
• 例: Pragma: no-cache
– キャッシュの使用禁止
•
※実際にはあまり活用されてない
圧縮
Accept-Encoding, Content-Encoding: gzip
•
圧縮
–
–
•
(Pros.) HTTPメッセージを圧縮することで通信データ量を減らす
(Cons.) 圧縮処理のため計算量は増えることに注意
レスポンスのボディを圧縮
–
リクエスト
•
Accept-Encodingヘッダ
–
–
–
レスポンス
•
Content-Encodingヘッダ
–
–
•
クライアントが受信可能な圧縮方式を列挙
例: Accept-Encoding: compress, gzip, deflate
サーバが実際にボディを圧縮した方式を記述
例: Content-Encoding: gzip
両方を同時に使うことも可能
リクエストのボディを圧縮
–
リクエスト
•
Content-Encodingヘッダ
–
–
–
クライアントが実際にボディを圧縮した方式を記述
例: Content-Encoding: gzip
レスポンス
•
•
指定された圧縮方式をサポートしていれば,通常のレスポンスを返す
指定された圧縮方式をサポートしていなければ,ステータスコード415(Unsupported Media Type)を返す
Accept-Encoding, Content-Encoding: gzipの
メッセージ例
リクエスト(レスポンスを圧縮)
GET /sample.html HTTP/1.1
Accept: */*
Accept-Language: ja
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0)
Host: 127.0.0.1:9081
Connection: Keep-Alive
レスポンス(レスポンスを圧縮)
HTTP/1.1 200 OK
Last-Modified: Thu, 27 Sep 2007 06:02:48 GMT
Date: Thu, 27 Sep 2007 08:02:23 GMT
Content-Type: text/html
Content-Length: 38
Content-Language: ja-JP
Content-Encoding: gzip
// binary octets for gzip
リクエスト(リクエスト/レスポンス両方を圧縮)
POST /sample.html HTTP/1.1
Accept: */*
Accept-Language: ja
Accept-Encoding: gzip, deflate
Content-Encoding: gzip
Content-Length: 38
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0)
Host: 127.0.0.1:9081
Connection: Keep-Alive
// binary octets for gzip
レスポンス(リクエスト/レスポンス両方を圧縮)
HTTP/1.1 200 OK
Last-Modified: Thu, 27 Sep 2007 06:02:48 GMT
Date: Thu, 27 Sep 2007 08:02:23 GMT
Content-Type: text/html
Content-Length: 38
Content-Language: ja-JP
Content-Encoding: gzip
// binary octets for gzip
Accept-Encoding, Content-Encoding: gzip の
ServletFilterでの実装例
CompressionFilter (TomcatのExampleから抜粋)
public class CompressionFilter implements Filter{
public void doFilter ( ServletRequest request, ServletResponse response, FilterChain chain ) throws IOException, ServletException {
boolean supportCompression = false;
if (request instanceof HttpServletRequest) {
Enumeration e = ((HttpServletRequest)request).getHeaders("Accept-Encoding");
while (e.hasMoreElements()) {
String name = (String)e.nextElement();
if (name.indexOf("gzip") != -1) {
Accept-Encodingヘッダのチェック
supportCompression = true;
}
}
gzipが含まれているかどうか
}
if (!supportCompression) {
gzipするためにresponseをwrap
chain.doFilter(request, response);
return;
} else {
if (response instanceof HttpServletResponse) {
CompressionServletResponseWrapper wrappedResponse = new CompressionServletResponseWrapper((HttpServletResponse)response);
wrappedResponse.setCompressionThreshold(compressionThreshold);
try {
chain.doFilter(request, wrappedResponse);
} finally {
wrappedResponse.finishResponse();
}
return;
}
}
}
}
メッセージサイズの指定
Content-Length, Transfer-Encoding: chunked
•
メッセージサイズの指定
– 可変長のメモリの確保にはコストがかかるため予めメッセージサイズが
分かっていると受信側が効率的に処理可能
– ※送信側は送信前にメッセージサイズを知る必要があることに注意
•
Content-Lengthヘッダ
– コンテンツ(メッセージボディ)のバイト長を10進数で示す
– 例: Content-Length: 4891
•
Transfer-Encodingヘッダ
– 例:Transfer-Encoding: chunked
– コンテンツ(メッセージボディ)を複数のチャンクに区切り、先頭に16進数
でバイト長を示す、0はコンテンツの終わりを示す
– 送信側は処理中にコンテンツの長さが分からない場合でも、バッファの
更新タイミングなどでデータを送ることができる
Content-Length, Transfer-Encoding: chunked
のメッセージ例
リクエスト
GET /sample.html HTTP/1.1
Accept: */*
Accept-Language: ja
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0)
Host: 127.0.0.1:9081
Connection: Keep-Alive
レスポンス(Content-Length)
リクエスト
GET /sample.html HTTP/1.1
Accept: */*
Accept-Language: ja
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0)
Host: 127.0.0.1:9081
Connection: Keep-Alive
レスポンス(Transfer-Encoding: chunked)
HTTP/1.1 200 OK
Last-Modified: Thu, 27 Sep 2007 06:02:48 GMT
Date: Thu, 27 Sep 2007 08:02:23 GMT
Content-Type: text/html
Content-Length: 45
Content-Language: ja-JP
HTTP/1.1 200 OK
Last-Modified: Thu, 27 Sep 2007 06:02:48 GMT
Date: Thu, 27 Sep 2007 08:02:23 GMT
Content-Type: text/html
Content-Language: ja-JP
Transfer-Encoding: chunked
<html><body>
Hello world!
</body></html>
1234
(16進数で1234バイトのデータ)
9ab
(16進数で9abバイトのデータ)
0
(コンテンツの終了)
※紛らわしいヘッダ
Content-Transfer-Encoding (MIMEヘッダ)
•
Content-Encodingヘッダ
–
–
•
Transfer-Encodingヘッダ
–
–
•
HTTPヘッダ
chunked など
Content-Transfer-Encodingヘッダ
–
–
•
HTTPヘッダ
gzip, compress, deflate など
MIMEヘッダ
7bit, base64, quoted-printable, 8bit, binary など
MIME (Multipurpose Internet Mail Extensions)
–
–
もともとは電子メール(7bit)にファイルを添付するための仕様
HTTPにもあるContent-TypeヘッダはMIME由来
•
•
–
–
MIME media type
text/plane, application/soap+xml, image/jpeg, multipart/related など
Content-Transfer-Encodingヘッダで8bit, binaryも指定可能
SOAPのバイナリ添付の仕様 MTOM/XOP でも使われている
•
•
MTOM: SOAP Message Transmission Optimization Mechanism
XOP: XML-binary Optimized Packaging
MIMEのメッセージ例
HTTP上のSOAPでMTOM/XOPにMIMEが使われている例
MIME-Version: 1.0
Content-Type: multipart/related; boundary=MIME_boundary; type="application/xop+xml";
start="<[email protected]>"; startinfo="application/soap+xml; action=¥"ProcessData¥""
Content-Description: A SOAP message with my pic in it
--MIME_boundary
Content-Type: application/xop+xml; charset=UTF-8; type="application/soap+xml; action=¥"ProcessData¥""
Content-Transfer-Encoding: 8bit
文字コードがUTF-8なので8bit
Content-ID: <[email protected]>
<soap:Envelope xmlns:soap='http://www.w3.org/2003/05/soap-envelope' xmlns:xmlmime='http://www.w3.org/2004/11/xmlmime'>
<soap:Body>
<m:data xmlns:m='http://example.org/stuff'>
<m:photo xmlmime:contentType='image/png'>
<xop:Include xmlns:xop='http://www.w3.org/2004/08/xop/include' href='cid:http://example.org/me.png'/>
</m:photo>
</m:data>
</soap:Body>
</soap:Envelope>
--MIME_boundary
Content-Type: image/png
Content-Transfer-Encoding: binary
Content-ID: <http://example.org/me.png>
// binary octets for png
--MIME_boundary
PNGの画像ファイルなのでbinary
持続的接続
Connection: Keep-Alive
• 持続的接続
– 1回のTCP接続で複数のHTTPリクエストを処理する
– HTTP/1.1では特に指定されなくてもKeep-Alive
• Connectionヘッダ
– 持続的接続を行なうかどうかを指定
• 例: Connection: Keep-Alive
• 例: Connection: closed
• Webサーバ(Apacheなど)で設定
– タイムアウト値の設定
Connection: Keep-Aliveのメッセージ例
リクエスト
GET /aaa.html HTTP/1.1
Host: 127.0.0.1:9801
Connection: Keep-Alive
GET /bbb.html HTTP/1.1
Host: 127.0.0.1:9801
Connection: close
レスポンス
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 1234
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
(aaa.html のコンテンツ)
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 1234
Keep-Alive: timeout=5, max=99
Connection: close
(bbb.html のコンテンツ)
HTTPのメッセージ例
www.google.co.jpのトップページの通信から抜粋
HTTPのメッセージ例
www.google.co.jpのトップページの通信から抜粋(2回目)
※使用ツール
Apache TCPmon (http://ws.apache.org/commons/tcpmon/)
ここでは www.google.co.jp:80 を localhost:8081 でフック
目次
• Java関連
– 高級言語と高速化
– 処理時間の計測と副作用
– JITコンパイラの挙動
–
–
–
–
–
–
ガベージ・コレクション
オブジェクトの生成
同期化の有無
バッファの利用
不変オブジェクト
可変長のデータ
高級言語と高速化
•
高級言語とは
– 高級言語とは細かいことは良きにはからってくれる言語である
• 例:自分でメモリ管理しなくて良い
– 裏を返せば,高級言語とは細かいことができない言語である
• 例:自分でメモリ管理できない(本当は頑張ればある程度はできる)
•
スーパープログラマと標準的なプログラマ
–
–
–
–
–
–
•
スーパープログラマによるアセンブラプログラム
スーパープログラマによるC言語プログラム
スーパープログラマによるJavaプログラム
標準的なプログラマによるJavaプログラム
標準的なプログラマによるC言語プログラム
標準的なプログラマによるアセンブラプログラム
高級言語における高速化
– ボトルネックになっている部分を見つけて,そこを中心に頑張る
処理時間の計測と副作用
•
原始的な計測方法
–
–
System.currentTimeMillis()で計測したい処理を挟む
注意点
•
•
System.currentTimeMillis()自体にも処理時間がかかる
多くのOSで最小精度が10ms程度
–
–
ループが何重にもネストしている部分は可能ならループの外側を計測する
ある程度の大きさのブロックを計測する
System.{out|err}.println()で標準またはエラー出力へ表示
注意点
•
•
–
System.out.println()自体にも処理時間がかかる
大量に画面に表示すると描画にCPU時間を食われて正確に計測できない
解決策
•
•
計測終了後に出力する
大量に出力させない
–
–
バックグラウンドにして画面上に描画させない,ファイルに書き出すなどすると幾分ましになりますが,大量に出力
させないのが好ましい
(余談ですが) 単なる大量のログ出力がボトルネックになっていることはしばしばあります
プロファイルを利用する
–
•
Java5以降はSystem.nanoTime()が利用可能
原始的な結果出力方法
–
–
•
System.out.println( end-start + “ms”);
解決策
•
•
•
long start = System.currentTimeMillis();
// 時間を計測したい処理
long end = System.currentTimeMillis();
(経験則ですが) 小さいメソッドなどの値は誤差が大きくなるように思います
ボトルネックを見つけてコードの変更を行なったら,できるだけ実際の実行環境に近い
環境で再計測する
JITコンパイラの挙動
•
Java JIT (Just-In-Time) コンパイラ
– JVMでのバイトコード実行時に,繰り返し実行される部分を部分的にネ
イティブコードにコンパイルすることで高速化を図るもの
– コンパイル時間が実行時間に含まれることに注意
– サーバのように長時間起動し続けるアプリケーションでは時間経過にし
たがって比較的多くの部分がコンパイルされる
ソースコード
javac
バイトコード
実行
JVM (インタプリタ)
実行
JITコ
ンパイ
ラ
Javaランタイム
ネイティブコード
実行
プラットフォーム (OS, CPU)
JITコンパイラの挙動
SAX Parserを10000回ずつ連続で実行した時の計測時間
No.
1- 10000:
No. 10001- 20000:
No. 20001- 30000:
No. 30001- 40000:
No. 40001- 50000:
No. 50001- 60000:
No. 60001- 70000:
No. 70001- 80000:
No. 80001- 90000:
No. 90001-100000:
No.100001-110000:
No.110001-120000:
No.120001-130000:
No.130001-140000:
No.140001-150000:
No.150001-160000:
No.160001-170000:
No.170001-180000:
No.180001-190000:
No.190001-200000:
10000loop(s): 1072ms
10000loop(s): 744ms
10000loop(s): 746ms
10000loop(s): 740ms
10000loop(s): 674ms
10000loop(s): 873ms
10000loop(s): 671ms
10000loop(s): 649ms
10000loop(s): 636ms
10000loop(s): 636ms
10000loop(s): 655ms
10000loop(s): 684ms
10000loop(s): 645ms
10000loop(s): 641ms
10000loop(s): 666ms
10000loop(s): 652ms
10000loop(s): 631ms
10000loop(s): 637ms
10000loop(s): 630ms
10000loop(s): 632ms
最初はあまりコンパイルされていないため速くない
1回目にはクラスロードの時間も含まれる
急に時間がかかっている部分にはJITコンパイルの
時間が含まれている可能性もある
じわじわと高速化されこの時点で最初の10000回の
60%程度の実行時間に
連続稼動するようなサーバアプリケーションを計測する場合には
ウォームアップ実行をしておくことが必要
ガベージ・コレクション
•
ガベージ・コレクション(Garbage Collection, GC)
– JavaVMによる使用していないメモリ領域を自動的に解放する仕組み
•
GCの動作
– 生成されたJavaオブジェクトがヒープ領域に順次格納される
– ヒープ領域が足らなくなるとGCが起きる
– GCはどこからも参照されていないJavaオブジェクトを見つけてそのメモリ
領域を開放する
•
Full GCの間はJavaの動作が停止
– java -verbose:gc オプション
• GCの実行ログを出力
– 頻繁にFull GCが起きる場合
• 不必要なオブジェクトを無駄に保持していないかチェック
• 処理に必要なメモリ領域のワーキングセットが大きい場合
– ヒープサイズの拡張 java -Xmx, -Xmsオプション
– 例:-Xmx256m (ヒープ領域の最大値を256MBにする)
オブジェクトの生成
•
Javaオブジェクトの生成はコストが高い
– Objectの生成がローカル代入の980倍
– 配列の生成がローカル代入の3100倍
•
オブジェクト・プール
– 特にコストの高いオブジェクトでかつ再使用可能なオブジェクトは一旦生
成したら何度も使いまわすことを考える
– 後述のXMLパーサプールで具体例を示す
処理
コード例
時間
ローカル代入
i=n;
1.0
メソッド呼び出し
funct( );
5.9
例外のthrow, catch
try{ throw e; } catch(e){ }
320
synchronizedメソッド呼び出し
synchronized funct( );
570
Objectのnew
new Object( );
980
配列のnew
new int[10];
3100
「Bruce EckelのJavaプログラミングマスターコース」より
同期化の有無
•
Mapインタフェース
– Hashtable
• 同期化あり,get, putする毎にオブジェクト全体をロック
– HashMap
• 同期化なし,スレッドセーフでない
– ConcurrentHashMap
• 同期化あり,部分表であるセグメント単位にロックすることで高速化
•
Listインタフェース
– Vector
• 同期化あり,get, addする毎にオブジェクト全体をロック
– ArrayList
• 同期化なし,スレッドセーフでない
– CopyOnWriteArrayList
• 同期化あり,iteratorを取り出した時点ではオブジェクトを共有,書き込みがあった時点
でオブジェクトをコピーする
インタフェース
同期化あり
同期化なし
java.util.concurrent
Map
Hashtable
HashMap
ConcurrentHashMap
List
Vector
ArrayList
CopyOnWriteArrayList
同期化が必要でない場合はHashMapなど,同期が必要な場合はConcurrentHashMapなどを検討する
バッファの利用
•
バッファ
– readやwriteが繰り返し呼び出される場合に有効
– 入力元,出力先へのアクセスに負荷がかかる場合に有効
• ファイルアクセス,ネットワークアクセス
– 例:
• BufferedInputStream, BufferedOutputStream,
• BufferedReader, BufferedWriter
new BufferedOutputStream(new FileOutputStream("file_name"));
ファイルへのアクセス回数が減る可能性
new BufferedOutputStream(new ByteArrayOutputStream());
意味なし
(逆にコピー,オブジェクト生成が増える)
入力元,出力先のデータの扱い方を確認して適用するかどうかを決める
不変オブジェクト
•
不変(Immutable)オブジェクト
–
–
–
–
–
例: String型, primitive型(int, booleanなど)
値を変えられないのでコピーする必要がなく,参照を共有することが可能
定数文字列はConstantsクラスなどにstaticに集約
String.intern() することで参照の比較で同値判定が可能
逆にほんの少し異なるだけの値でも別のオブジェクトを生成する必要がある
⇒ StringBufferの利用
x = "a" + 4 + "c";
=
x = new StringBuffer().append("a").append(4).append("c").toString();
Stringの連結はそれ毎にStringBufferオブジェクトの生成,append()実行,新たな
Stringオブジェクトの生成が必要,繰り返されるようなら予めStringBufferを使う
可変長のデータ
• 可変長のデータ
– 例:
最悪の場合,本来必要なデータ
サイズの2倍の領域が必要
value[]
expand
• StringBuffer(16)
• ArrayList(10), HashMap(16), HashSet(16)
• ByteArrayOutputStream(32), CharArrayWriter(32)
– データが増えると内部データのキャパシティが拡張される
StringBufferのキャパシティの拡張
void expandCapacity(int minimumCapacity) {
int newCapacity = (value.length + 1) * 2;
if (newCapacity < 0) {
newCapacity = Integer.MAX_VALUE;
} else if (minimumCapacity > newCapacity) {
newCapacity = minimumCapacity;
}
char newValue[] = new char[newCapacity];
System.arraycopy(value, 0, newValue, 0, count);
value = newValue;
}
キャパシティを2倍に
新しい内部データ用の配列を生成
今までのデータを新しい配列にコピー
予めデータサイズの予想がついていれば生成時にコンストラクタで
initialCapacityを設定する
目次
• XML関連
– DOMパーサ
– SAXパーサ
– StAXパーサ
– 再利用可能
– XMLパーサプール
– JAX-WS, JAXB
DOMパーサ
• DOM (Document Object Model) パーサ
– XML文書を読み込み,DOMと呼ばれるオブジェクトツリーを生
成する
– オブジェクトツリーを生成するためメモリ領域を消費する
– 木構造データが作成されるため比較的理解しやすい
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = dbf.newDocumentBuilder();
Document doc = builder.parse(inputStream);
Element root = doc.getDocumentElement();
Element elem = (Element)root.getElementsByTagNameNS("uri:ns", "elem1").item(0);
.....
Document doc
Element root
XML文書
DOMパーサ
(DocumentBuilder)
Element elem1
SAXパーサ
• SAX (Simple API for XML) パーサ
–
–
–
–
XML文書を読み込み,ユーザの実装したHandlerにイベントを送る
Event-drivenパーサ
メモリ消費量は比較的少ない
Pushパーサ(StAXとの比較において)
• SAXパーサがユーザのHandlerにイベントをPushする
• 途中で止められない (Exceptionを投げる以外では)
DefaultHandler myHandler = new MyHandler();
SAXParserFactory saxFactory = SAXParserFactory.newInstance();
SAXParser saxParser = saxFactory.newSAXParser();
saxParser.parse(inputStream, myHandler);
.....
※Attributesの中身はこのメソッドの間しか保証されない
(SAXParserはattributesオブジェクトを再使用してよい)
イベントをPush
class MyHandler extends DefaultHandler {
public void startElement(String uri, String localName,
String qName, Attributes attributes) { ... }
public void characters(char[] ch, int start, int length) { ... }
.....
}
※char[]の中身はこのメソッドの間しか保証されない
(SAXParserはchar[]を再使用してよい)
StAXパーサ
• StAX (Streaming API for XML) パーサ
– XML文書を読み込み,ユーザのコードがStAXパーサからデータ
を取り出す
– Event-drivenパーサ
– メモリ消費量は比較的少ない
– Pullパーサ
• ユーザのコードがStAXパーサからイベントをPullする
• 途中で止められる
XMLInputFactory staxFactory = XMLInputFactory.newInstance();
XMLStreamReader staxParser = staxFactory.createXMLStreamReader(inputStream);
int eventType = staxParser.getEventType();
QName elemQName = staxParser.getName();
eventType = staxParser.next();
次のイベントをPull
.....
※イベント毎に最低限必要なオブジェクトはeventTypeのintのみ
StAXパーサはユーザのコードが要求した情報のみ生成すればよい
再使用可能
•
再使用可能(Reusable)
– プログラムの実行終了後,再び実行を繰り返すことができる
– reset()メソッドを持つオブジェクトは再使用可能である可能性が高い
– 例:
• DOMパーサ(DocumentBuilder)
• SAXパーサ(SAXParser)
• ※ StAXパーサ(XMLStreamReader)は再使用不可
– 入力InputStreamをresetする方法がない
•
※参考
– スレッドセーフ(Thread-safe)
• 複数のスレッドから同時にアクセスされても問題が発生しない
– 再入可能(Reentrant)
• 複数のスレッドから同時に実行することができる
• 再入可能であれはスレッドセーフ
XMLパーサプール
• DOMパーサ,SAXパーサはパーサ自体の生成コストが
高く,また再使用可能である
• オブジェクト・プールを使って再利用することを考える
DOMパーサの生成コスト
(1) DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
(2) DocumentBuilder builder = dbf.newDocumentBuilder();
(3) Document doc = builder.parse(inputStream);
(1)+(2)+(3)
(2)+(3)
(3)
10000loop(s): 15175ms
10000loop(s): 13246ms
10000loop(s): 1626ms
(a)+(b)+(c)
(b)+(c)
(c)
10000loop(s): 14046ms
10000loop(s): 12129ms
10000loop(s):
655ms
SAXパーサの生成コスト
(a) SAXParserFactory saxFactory = SAXParserFactory.newInstance();
(b) SAXParser saxParser = saxFactory.newSAXParser();
(c) saxParser.parse(inputStream, handler);
XMLParserPoolの実装例
SAXParserのPool実装
public class XMLParserPool {
Poolするパーサの最大数
static private int MAX_POOL_SIZE = 100;
static private SAXParserFactory saxFactory = SAXParserFactory.newInstance();
Factoryは一つだけ
static private SAXParser[] saxParserPool = new SAXParser[MAX_POOL_SIZE];
static private int saxParserPoolSize = 0;
Poolからパーサを取得
static public SAXParser getSAXParser() throws ParserConfigurationException, SAXException {
synchronized (saxParserPool) {
if (saxParserPoolSize > 0) {
saxParserPoolSize--;
Poolにパーサがあればそれを返す
return saxParserPool[saxParserPoolSize];
}
}
synchronized (saxFactory) {
Poolにパーサがなければ新しく生成
return saxFactory.newSAXParser();
}
}
Poolにパーサを返還
static public void releaseSAXParser(SAXParser saxParser) {
synchronized (saxParserPool) {
if (saxParserPoolSize < MAX_POOL_SIZE) {
返還されたパーサをreset
saxParser.reset();
saxParserPool[saxParserPoolSize] = saxParser;
saxParserPoolSize++;
}
}
}
DOMパーサに対しても全く同じように実装できる
}
JAX-WS, JAXB
•
JAX-WS 2.0
–
–
–
–
–
•
JSR224 - Java API for XML-Based Web Services 2.0
JAX-RPCの後継となるJavaでのWebサービス(SOAP)のAPI
WSDLとJavaクラスのマッピング
従来のRPCに加え、メッセージ交換のプログラミングモデルをサポート
JavaとXMLのマッピングは JAXB2.0 仕様に委譲
JAXB 2.0
–
–
JSR222 - Java Architecture for XML Binding 2.0
JavaクラスとXMLスキーマのデータバインディングを行う仕様
•
•
–
XMLスキーマ ⇔ Javaクラス
XML文書 ⇔ Javaオブジェクト
XMLスキーマに準拠し、スキーマ検証をサポート
Javaクラス
WS
JAX-
WSDL
JAXB
XML
Schema
JAXWS
JAXB
Javaクラス
JavaBean
Customer.java
Address.java
サービス
リクエスタ
※XMLを意識する必要なし
SOAP/XML
SOAP/XML
サービス
プロバイダ
※XMLを意識する必要なし
目次
• Coffee break
– 仕様を読もう
– ソースコード,javadocを読もう
- Coffee break 仕様を読もう
• HTTP/1.1
– RFC 2616 Hypertext Transfer Protocol -- HTTP/1.1
• http://www.ietf.org/rfc/rfc2616.txt
• http://www.w3.org/Protocols/rfc2616/rfc2616.html
– Studying HTTP(日本語サイト)
• http://www.studyinghttp.net/
• http://www.studyinghttp.net/cgi-bin/rfc.cgi?2616
(HTTP1.1日本語訳)
• XML1.0
– Extensible Markup Language (XML) 1.0
• http://www.w3.org/TR/xml/
– 日本語訳
• http://www.doraneko.org/xml/xml10/19980210/Overview.html
- Coffee break ソースコード,javadocを読もう
• ソースコードを読もう(ライセンスの許す範囲で)
– src.zip (JDK)
– apache
• javadocを読もう/書こう
SAXParserのjavadoc記述
SAXParser の実装は、2 つ以上のスレッドで同時に使用された場合、仕様どおりに動作することは保証「されません」。ス
レッド当たり SAXParser の 1 つのインスタンスにすることをお勧めします。複数のスレッドから SAXParser が使用される
かどうかの確認はアプリケーションで行います。
SAXParserのreset()メソッドの記述
public void reset()
この SAXParser を元の設定にリセットします。
SAXParser は、SAXParserFactory.newSAXParser() で作成された時点の状態にリセットされます。 reset() は、既存の
SAXParser の再使用を許可するように設定されているため、新しい SAXParser の作成に関連するリソースを節約できます。
Fly UP