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 の作成に関連するリソースを節約できます。