JavaOne参加レポート (9/25)

JavaOne 4 日目となる 9/25 のレポートです。JJUGJavaOne 報告会の準備でこちらの作業を後回しにしてしまい、こんなに遅い公開となりました。ごめんなさい。
この日に参加したセッションは次の通りです。この日は夜に Oracle Open World と一緒に開催される Appreciatition Event があったので、夕方まででセッションが終わりました。

  • In-Database Container for Hadoop: When MapReduce Meets RDBMS [CON9240]
  • Java 8 Streams: Lambda in Top Gear [CON7942]
  • Java 8 Collections and Concurrency [CON7962]
  • Optimizing JavaFX Applications [CON3141]
  • Top 10 Web Application Defenses for Java Developers [CON5523]
  • The State of Java Web Container Security [CON8016]

JavaFXJava SE ではとても濃い内容の話を聞けました。また、Hadoop という JavaOne ではちょっと異色のセッションにも参加しています。

In-Database Container for Hadoop: When MapReduce Meets RDBMS [CON9240]

Hadoop についてのセッションです。Oracle では何と HadoopOracle のコンテナ上で動かしてしまおうという試みを行っているようで、そのアーキテクチャ、利用方法についてデモを交えて解説するというものでした。

Hadoop を利用する上で大きな課題がデータの移動です。多くの場合、Hadoop を使って解析したいデータは何らかの別のストレージ (多くはデータベース) に置かれています。
これを Hadoop (HDFS) 側に持ってくる必要がありますが、何せ対象は「ビッグデータ」。移動するだけでも大変ですが、セキュリティ、データの整合性などにも気をつかう必要があり、相当な運用スキルが要求されます。

そこで Oracle が考えたのは、いっそのこと Oracle の上で Hadoop を動かしちゃおう!というものです。
Oracle DB は JVM も動かしていますが、この上で Apache Hadoop を動かします。Oracle の Parallel Query エンジンがデータのパーティショニングやジョブのスケジューリングを行うというアーキテクチャです。
データストレージとしては通常のファイルの他に Oracle 上のテーブルやビューも対象とします。Oracle のテーブルやビューを読み書きするために専用の TableReader、TableWriter クラスを用意しています。ダイレクトに Oracle のテーブル上のデータを読み書きできるので、わざわざデータを移動する必要がないということになります。

MapReduce ジョブの実行方法についてですが、Mapper と Reducer は通常の Hadoop の開発と同じように作ります。この MapReduce ジョブは Java でドライバクラスを作って実行する方法 (通常の Hadoop におけるジョブ実行方法) と、SQL から実行する方法があります。
Java で記述する場合、oracle.sql.hadoop パッケージにある Job クラスを使って、ジョブの実行設定を記述します。Job#setMapOutputKeyDBType("VARCHAR2(10)") とか記述しますw Job#run() の引数に読み込み元のテーブルと出力先のテーブルを指定します。
Java で作ったジョブは Oracle に登録し、Java ストアドプロシージャとして実行することになります。
SQL の場合、SELCT * FROM TABLE (HREDUCE_JP_WORDCOUNT(:RedConf, ... (HMAP_JP_WORDCOUNT(:MapConf, ... という感じで、Map ジョブや Reduce ジョブの結果をテーブルに見立てて SQL を記述します。うまく書けば SQL でデータのパイプラインを組み立てられることになります。
これもやはりストアドプロシージャとして登録して実行することになります。

実際にデモも行ってもらいました。MapReduce を使ってアクセス数を集計するというものです。SQL* Plus から MapReduce ジョブを実行する様はなかなかシュールでしたw
MapReduce タスクが読み込みやすいような形のビューを作って実行させていました。

なかなかすごいのですが、現時点ではプロトタイプのみで製品にはなっていないようです。ですがデータ移動に困っている人は結構多いはずですので、製品化を期待して待っています。
でも高そう。製品化するとなると恐らく Big Data Appliance や Exadata といったアプライアンス製品に載るんでしょうね。

なお、先日行われたJavaOne 2013 サンフランシスコ報告会 TokyoでもLTでこのセッションについてのお話しをしました。当日の発表資料を以下に示しておくのでご参考までに。

JavaOne2013報告会 LT資料 Hadoopの話を聞いてきた from Takashi Aoe

Java 8 Streams: Lambda in Top Gear [CON7942]

Brian Goetzさんによる、Stream についてみっちり解説するセッションです。大変人気のあるセッションで、立ち見が発生していました。

Java SE 8 になって導入される Lambda 式ですが、その Lambda 式の導入で最も大きな変化があるのがコレクションの操作です。Stream の導入により、Java でもデータ操作を宣言的に行えるようになります。

Stream はデータセットに対する処理の抽象化です。データ構造ではありません。
無限リストを取り扱うことができ、処理は UNIX のコマンドのようにパイプラインをつなげるような形で処理を行います。
Stream のパイプラインはソース、0かそれ以上の中間操作、終端操作で構成されます。基本的に中間操作を行うメソッドパイプラインを組み立てるだけで、可能な限り遅延処理されます。パイプラインの実行を行うのは終端操作となります。

Stream<Txn> s1 = txns.stream();                                          // ソース
Stream<Txn> s2 = s1.filter(t -> t.getBuyer().getState().equals(“NY”)); // 中間操作
IntStream   s3 = s2.mapToInt(Txn::getPrice);                             // 中間操作
int         sum = s3.sum();                                              // 終端操作

ソースの生成方法は色々あります。通常はコレクションの stream() メソッドを呼び出して作りますが、IntStream.range() や Files.walk() みたいなのもあります。
このソースによって SIZED、ORDERED、DISTINCT、SORTED といった Stream の性質が決まります。例えば ArrayList だったら SIZED と ORDERED という性質を持ちます。
また、ソースによって Decomposition (ばらしやすさ) が異なり、並列処理に影響します。例えば LinkedList はばらせないので、並列処理の効果がありません。
中間操作の処理は先ほども述べたように遅延されます。また、中間操作はパイプラインの性質に影響を与えます。例えば map() は SIZED を保持しますが、DISTINCT や SORTED の性質は消えます。
そして終端操作がパイプラインを実行することになります。この処理がシーケンシャルもしくはパラレルに実行されることになります。

また注意点として、パイプラインは一度きりの実行で分岐や再利用はできないこと、実行中はソースをいじらないこと、処理はステートレスに行う必要があることを挙げていました。
最後の点についてですが、例えば次のように forEach() の中で外部のコレクションに値を追加するようなことは NG です。こうするとスレッドセーフでなくなり、並列化できなくなります。

List<Person> sellers = new ArrayList<>();
txns.map(Txn::getSeller).forEach(s -> sellers.add(s));

次のように畳み込み処理のために用意されたメソッドを使うようにします。

txn.map(Txn::getSeller).collect(Collectors.toList());

並列処理を行う場合は stream() メソッドの代わりに parallelStream() メソッドを使います。当然のことながら並列化してもしなくても結果が同じになるようにしなければいけません。 *1
また、タスクごとに互いの結果に触れることもできません。
Java SE 7 で導入された Fork/Join フレームワークをベースに作られている *2 ので、分割統治法で処理されます。

最後に並列化の性能について触れていましたが、データセットのサイズを N 、パイプライン処理のコストを Q として、N * Q が高い値になるほど並列化の効果が得られやすいとのことでした。
実データの比較結果を見せてもらいましたが、だいたいレコード数が 10,000 を超えたあたりで逆転していました。

とても濃い内容のセッションでした。仕様策定に関わった人から直接こういう濃い内容の話を聞けるというのはいいですねえ。

Java 8 Collections and Concurrency [CON7962]

Java SE 8 におけるクラスライブラリの細かい改善について解説するセッションです。具体的には JSR-335、JEP-142、155、171、180 についての解説です。

JEP-142 は @Contended というアノテーションを追加し、False Sharing 問題を解決するというものです。
Java でフィールドを複数持つクラスを定義すると、それぞれのフィールドが連続したメモリアドレスに配置されますが、マルチコアの場合、共有していないデータを CPU キャッシュ上の同一ラインで共有してしまうという、False Sharing 問題を発生させることがあります。 *3
そこで新たに加わった @Contended アノテーションを片方のフィールドに付与すると、別々のラインに配置してくれるようになるとのことです。

JEP-155 は並行処理に関する小さなアップデートを集めたものです。以下に概要を列挙します。

  • 複数のスレッドから更新され、参照頻度が少ない場合に従来の AtomicLong や AtomicDouble などの代わりに使うことのできる、LongAccumulator や LongAdder といったクラスが追加された。
    • Accumulator は汎用的な更新に使う。
    • Adder には increment() や add()、sum() といった演算メソッドが用意されている。
  • ConcurrentHashMap が強化されている。大きな要素数になった場合のスキャン性能が強化され、バルク操作もできるようになる。よりキャッシュ向きになった。
  • ForkJoin の改善。多数のタスクをコミットしたときのスループットが向上している。
  • ForkJoin 共有プール。Java SE 8 からは明示的に ForkJoinPool を作らなくても、デフォルトで ForkJoinTask の共有プールが用意されている。
  • ReadWriteLock に対する StampedLock というものが新たに追加される。これはいわゆる楽観ロック。
    • StampedLock#tryOptimistcRead() して stamp をとり、StampedLock#vaidate() に渡す、という使い方をする。
  • CompletionStage というインターフェースが加わる (実装クラスとして CompletableFuture がある) 。これは Deferred オブジェクトに相当するもの。
    • stage.thenApply(Lambda 式).thenAccept(Lambda 式).thenRun(Lambda 式) という風に書ける。

JEP-171 はメモリ操作の順序性を保証する機能 (Fence) を追加するというもののようで、sun.misc.Unsafe クラスに読み込み、書き込み、読み書き両方のそれぞれに相当する loadFence()、storeFence()、fullFence() というメソッドが追加されるとのことです。
でも sun パッケージに入っているので表には出てこないということですね。

JEP-180 は HashMap の改善に関するもので、これまではキーのハッシュの衝突が発生した場合、LinkedList に格納していましたが (O(n) オーダーの検索になる) 、8 ではエントリが Comparable の場合に限って、B ツリーに格納するように改善されたようです。
もっとも、良いハッシュアルゴリズムを使うことが一番大事だよと話していました。まあそうですねえ。

JSR-335 はコレクションの拡張です。次のような改善が行われています。

  • sort() にデフォルト実装が入る。
  • コアクラスの実装がより最適化されている。
  • map に computeIfAbsent() が入る!
    • これは個人的に嬉しかったです。次のようなことができます。
map.computeIfAbsent(key, k -> new ArrayList()).add(elm)

このような感じで、細かいアップデートについてカバーするというセッションでした。こういう細かい情報は案外得られなかったりするので、とってもありがたかったです。

Optimizing JavaFX Applications [CON3141]

JavaFXレンダリング処理の詳細について解説するという実にディープな内容のセッションでした。

JavaFX は主に 2 つのスレッドが使われています。1 つは FX アプリケーションスレッド (以下 FX スレッド) 。こちらはアプリケーションの入力イベントや後述の pulse イベントを処理するスレッドです。もう 1 つはレンダースレッドで、こちらはシーングラフの状態をレンダリングする処理のために使われます。

そして pulse についての説明です。pulse とは 16ms おきにタイマーで起動されるイベントで、シーングラフの状態を調べて、どうペイントすべきかを決定する処理を行います。タイマーチェックの際に次の条件を満たしていれば実行されます。

  • アニメーションが実行中である
  • シーン上で変更が発生している (dirty scenes が存在する、という言い方をしていました)
  • 明示的に pulse の実行が要求された
  • 前の pulse がまだ実行中ではない

pulse の中では、アニメーションの実行 -> CSS の評価 -> レイアウトの評価 -> コンポーネント境界の更新 -> (前回の pulse の結果発生した) レンダリング完了の待機 -> Scene 上の Node の同期 -> RenderJob の作成、の順で処理が行われます。そして、最後の処理で作った RenderJob をレンダースレッドに渡して、レンダースレッドに描画を行ってもらいます。
なお、JavaFX 2.x では FX スレッドとレンダースレッドの並行性に問題があり、レンダースレッドでの RenderJob の実行が完了しなければ次の pulse が実行されないようになっていました。JavaFX 8 では pulse の途中でレンダリング完了を待つフェーズを挟み込むようにし、より並行性を高めているとのことです。

続いて、ハードウェアアクセラレーションが効いている場合とそうでない場合の違いについての説明に入りました。
Windows では Direct3D を使いますが、その場合、 pulse で作られた RenderJob はレンダースレッドに渡され、レンダースレッドにて paint して D3DSwapChain#present() をコールします。
これに対し、ソフトウェアレンダリングになった場合は、レンダースレッドで paint した後、ピクセルをバッファにコピーして FX スレッドに戻してピクセルを更新します。つまり、D3D の場合と違って描画処理の一部が FX スレッドを使うことになります。
Mac の場合は OpenGL を使いますが、レンダースレッドで paint 後、 FX スレッドに戻して、FX スレッドで OpenGL のコンテキストを使ってテクスチャを描画します。
また、JFXPanel を使って Swing 上に JavaFX のシーングラフを置いた場合、Java2D を使ってシーングラフを描画することになります。この場合はレンダースレッドが AWT の Component#repaint() をコールして、AWT に描画させます。

WebView については、WebKit に対して代理のタイマーを渡しているそうです。このタイマーは常に空のアニメーションを実行しており、JavaScript によるアニメーションをハンドルします。
また、Web ページ上でダーティリージョンが発生した場合、レンダリングキューを作って処理をレンダースレッドに渡しているとのことです。

このような JavaFX の動きについて説明した上で、パフォーマンスの計測方法についての解説を行いました。
PerformanceTracker というクラスが com.sun.javafx.perf パッケージにあります。このクラスの getSceneTracker() メソッドの引数に Scene を渡してインスタンスを作ります。このクラスには getInstantFPS() や getAverageFPS() といった性能測定に必要なメソッドが用意されています。
setOnPulse() や setOnRenderedFrameTask() といった、処理フェーズの合間に処理を挟み込めるようなメソッドも用意されています。
また pulse の状態を見るには PulseLogger というものがあり、-Djavafx.pulseLogger=true を設定することで出力されるようになります。pulse の処理情報、実行時間、カウンタなどが出力されます。

最後に JavaFX ランタイムの挙動を変更するいくつかのオプションを紹介していました。

  • Glass Robot。com.sun.glass.ui.Robot クラスがそれで、JavaFX 専用の Robot クラス。JemmyFX が使用している。
  • フルスピードモードというものがある。-Djavafx.animation.fullspeed=true を設定すると有効化され、pulse の間の待ち時間がなくなる。
    • つまり 60FPS 以上を出すことが可能になります。使うとしたらベンチマークの時くらいでしょうね。
  • -Dquantums.norenderjobs=true を設定するとシングルスレッドモードになる。
  • GLTrace という OpenGL/EGL API の呼び出し状況をトレースするツールがあり、Linux 版及び MacOSX 版がある。
    • openjfx/rt/gltrace にソースがある。

最後に JavaFX におけるプロファイリングについてのアドバイスがあり、JavaFX は多くの処理を GPU に行わせており、また CPU を使う処理も大半は JavaFX ランタイムが処理を行っているため、CPU プロファイリングは余り効果的ではないとのことでした。
このセッションで紹介した PulseLogger などのツールを活用すべし、ということでしょうね。

いや、実に濃いセッションでした。こういう話を聞くと、JavaOne に来て良かったなあと思いますね。

Top 10 Web Application Defenses for Java Developers [CON5523]

OWASP のメンバーの Jim Manico さんによる、Web アプリケーションのセキュリティ対策についてのセッションです。
この方、とにかくノリのいい、楽しい方で、すさまじいマシンガントークで、ノリノリになってまくし立てていました。それを聞いているだけで楽しかったです。

SQL インジェクション、パスワード保存、XSS、HTML サニタイズCSRF、暗号化、クリックジャッキング、アクセスコントロールなどについて、防御方法を順に説明していました。
それらの説明に割と共通していたのが、「下手に自分で作り込むよりも、過去の知見が集積された OSS ライブラリを活用するように」という点でした。これはなるほどと感心しました。以下、概要を列挙します。

  • SQL インジェクションの防止はパラメータ化。Web アプリケーションがクラックされるトップの原因は「';」をテキストボックスに入力されること。
  • パスワードの保存についての注意点。
    • パスワードは絶対に文字種を限定しないこと!
    • 暗号化する場合、キーはクレデンシャルとは別のストアに置くこと。
    • ハッシュ生成は別サービスで行う。
    • ソルトを付けること。
  • XSS の対策はもちろんエスケープだけど、エスケープは色んな種類があって大変。そこで OWASP Java Encoder Project がお勧め。コンテキスト別に必要な関数を提供しており、パフォーマンスもとても良い。
  • HTML のサニタイズは、最近だとリッチテキストエディタを用意する必要もあったりして、一概に全てサニタイズできない。そこで OWASP HTML Sanitizer Project がお勧め。許可するタグだけを設定してサニタイズができるようになる。
  • CSRF 対策。
    • 最近だとホームルーターCSRF 脆弱性を突いてパスワードを変更するといった事例も出てきている。
    • トークンを入れるのが基本的かつ強力な対策。ただし、XSS 対策がきちんと行われていることが前提!
    • Amazon のように、再認証プロセスを入れるのも良い。
  • クリックジャッキング。
    • iframe を透明化して、別 Web サイトを被せて誤操作させる手口。
    • X-FRAME-OPTIONS に SAMEORIGIN を設定して対策する。
  • アクセスコントロール。
    • 自分で認証認可のロジックを作り込むと、バグがあったときに大変。
    • Apache Shiro のようなライブラリを活用しよう。
  • Certificate Pinning (証明書のピン留め) 。

このような感じで、結構いいまとめでした。なによりセッション全体が楽しかったですねえ。あんな風に楽しいプレゼンができるようになりたいなあ。

The State of Java Web Container Security [CON8016]

これはパネルディスカッションでした。今後 Java EE コンテナに対して、どのようなセキュリティ面での対策が加わるようになるといいのか、について議論するものです。
正直こういうフリーディスカッションの形式だと、自分の英語力では少し厳しかったです。(>_<)
次のような話が話題に挙がりました。

  • CSRF 対策。
    • Tomcat は対策が入っている。
    • タグを書き込む形式がいい?それともコンポーネントを用意?
    • トークンの生成頻度はどれが適当?
      • セッション単位?毎回?
      • Tomcat はレベルを設定出来るよね。
    • 一律に対策を入れちゃうとブックマークができなくちゃっちゃうよね。
  • HTTP パラメータ汚染。
    • 今のサーブレットでは URL のクエリパラメータが form パラメータに優先されるという問題がある。
      • どちらも HttpServletRequest#getParameter() で取得するので。
    • 明確に GET と POST で取り出す場所を分けるべきでは? PHP みたいに。
  • セキュリティ制約。
    • 今は URL パターンのみだよね。
    • ブラウザに指示する HTTP ヘッダ (Strict-Transport=security とか X-Frame-Options とか) を扱えるようにすべきでは?
  • 認証認可。
    • Spring Security ライクに XML で外部定義したいなあ、という意見が。
      • (それに対し、「いや設定変えたらテストするからどっちみちコンパイルするだろ」という突っ込みがw)

私が聞き取れたのは大体こんなところです。(^^;;
確かにもう少し JavaEE レベルでこのあたりの対策は追加していって欲しいかな、とは思っています。

*1:deterministic でないといけない、という言い方をしますね。

*2:なので、使用スレッド数などの設定も同様であるとのことでした。

*3:当日セッションを聞いていたときは、この False Sharing 問題についてよく知らなかったのですが、ここの解説が分かり易かったです。