Javaの性能

Javaの性能（ジャバのせいのう）では、Javaプラットフォームの性能について説明する。プログラミング言語としてのJavaに対する批判や、Javaプラットフォームの性能に対する批判は「Javaに対する批判」の記事を参照のこと。この記事ではJavaプラットフォームの性能について批判以外の説明をする。
プログラミング言語Javaは、その「ネットワークから送り込まれるプログラムの安全な実行」や「write once, run anywhere」というスローガンを、業界にありがちなスローガンだけのスローガンではなく可能な限り達成するべく、Javaバイトコードにコンパイルするコンパイラと、Javaバイトコードを解釈実行するインタプリタであるJava仮想マシン (Java VM, JVM)、という構成の実装を、公式の実装として伴って発表された。
コンピュータ科学的には特に目新しいものではない。しかし、従来のC言語あるいはC++といったネイティブコードにコンパイルする言語で書かれたアプリケーションソフトウェアとの性能比較や、当初はJVMのチューニングや高速化手法が進んでいなかったことによる性能の制限、また、当時の一般ユーザーが使っていたMicrosoft Internet Explorerにおいて、Javaアプレットが埋込まれたウェブページを表示しようとすると、JVMを起動するために数十秒から最悪の場合は数分も待たされたことから（起動してしまえば実は高性能なVMだったのだが）、「Javaは遅い」などと言われるようになったため、「Javaの性能」が議論されるようになった。
Javaプログラムの実行速度はJITコンパイルの導入（1997年 / 1998年の以降）や
コードの解析の機能が言語に追加されたこと、Java仮想マシン自体の最適化（2000年からサン・マイクロシステムズのVMで標準的に動作するようになったHotSpot技術など）によって大きく向上した。
仮想マシンの最適化手法.
オラクル（旧サン・マイクロシステムズ）のJVMの性能は徐々に向上してきたが、このJVMはいくつかの最適化を実装した最初の仮想機械であることも多く、そうした技術は類似のプラットフォームでも使用されている。
ジャストインタイムコンパイル.
初期のJava仮想マシンはバイトコードのインタプリタであり、このことが性能に対する大きな足かせ（平均的なアプリケーションで、Java対Cで10〜20倍程度）になっていた。
Java 1.1で JIT コンパイラが導入された。
Java 1.2でHotSpotと呼ばれる技術が導入された。これは、Java仮想マシンがプログラムの頻繁に実行される箇所、「ホットスポット」の性能解析を実行中に実行し続けるもので、解析した情報は最適化に利用して、他のパフォーマンスに影響のないコードには余分な負荷をかけることなく、性能を向上させることができる。
Java 1.3でHotSpotが標準で用いられるようになった。
HotSpot技術により、コードはまずインタプリタ実行され、「ホットスポット」が動的にコンパイルされる。Javaの性能測定においてベンチマークをとる前にプログラムを数回実行させる必要があるのはこのためである。
HotSpotによるコンパイルではインライン展開、ループ展開、、アーキテクチャ固有のレジスタ割り付けなどの様々な最適化手法が用いられる。
ベンチマークによってはこうした手法により10倍の性能向上が見られる。
適応的最適化.
適応的最適化 () とは、計算機科学においてプログラムの一部を現在の実行結果のプロファイルに基づき動的再コンパイル () する技術である。単純な実装では、適応的最適化はジャストインコンパイルとインタプリタ実行を選択するだけだが、より高水準のものでは、データの局所的な状態を用いて分岐を取り除き、インライン展開してコンテキスト切り替えを削減することもできる。
HotSpotのようなJava仮想マシンは、一度JITコンパイルされたコードを解消 (: 脱最適化) することも可能である。これによって、積極的な（場合によっては危険な）最適化を実行し、後で最適化を解消し安全な実行方法に戻ることもできる。
ガベージコレクション.
Java 1.0と1.1のJava仮想マシンでは、ガベージコレクション実行後のヒープが断片化する可能性のあるマーク・アンド・スイープ方式のガベージコレクションを採用していた。
Java 1.2より、Java仮想マシンは世代別ガベージコレクションを用いるようになり、断片化が起こりづらくなった。
現代的なJava仮想マシンは、ガベージコレクションの性能を改善する様々な手法を用いている。
その他の最適化技術.
バイトコード検証の分割.
クラスの実行に先立ち、オラクルのJVMはバイトコードの検証を行う。バイトコードのロードと検証は特定のクラスがロードされ実行する準備ができた場合のみ行われ、プログラムの開始時点で行われるわけではない。しかし、Javaのクラスライブラリも正規のJavaクラスであり、使用時にロードされるため、Javaプログラムの起動時間は例えばC++のプログラムより長くなることが多い。
"分割検証"と呼ばれる技術がJavaプラットフォームのJ2MEで導入され、からJava仮想マシンで利用されるようになった。これはバイトコードの検証を二つのフェーズに分割する。
こうした手法は、Javaコンパイラがクラスのコードの流れを解析し、コンパイルされたメソッドのバイトコードにクラスのフロー情報の概要を注釈(アノテート)することで動作する。実行時の検証を劇的に単純化するわけではないが、若干の処理を省略することができる。
エスケープ解析と粗粒度ロック.
Javaは言語レベルでマルチスレッドに対応している。マルチスレッドとは、下記のようなことを可能にする技術である。
しかし、マルチスレッドを使用するプログラムは、スレッド間で共有されるオブジェクトやメソッド、コードブロックに開発者が特別な注意を払う必要がある。またオブジェクトやコードブロックをロックすることは、それに伴うOSの性質によって時間のかかる操作である（並行性制御やロックの粒度を参照）。
Javaライブラリにはどのメソッドが複数のスレッドから使用されるか分からないため、マルチスレッド環境で使用される標準的なライブラリは常にコードブロックのロックを行っている。
Java 6以前では、複数の異なるスレッドが同時にオブジェクトを変更するリスクがない場合でも、仮想マシンはオブジェクトやコードブロックのロックをプログラムの要求にしたがって行っていた(ロックの実装を参照)。例えば、ローカル変数のがあり、それに対する "add" 操作を行う際、それが確実にローカルでしか使用されずロックが不要である状況でも、同時に他のスレッドから変更されないようロックを行っていた。
public String getNames() {
 Vector v = new Vector();
 v.add("Me");
 v.add("You");
 v.add("Her");
 return v.toString();
Java 6では、コードブロックやロックは必要なときだけロックされるようになり 、上記の例では仮想マシンはcodice_1オブジェクトのロックを行わない。
バージョン 6 Update 14で、Javaは実験的ながらエスケープ解析をサポートするようになった。
レジスタ割付の改善.
以前では、「クライアント」仮想マシンにおけるレジスタ割り付けはかなり初歩的なもので（ブロックを超えてレジスタが生存できない）、これは例えばx86のようなレジスタが少ないアーキテクチャで問題となる。ある操作に必要なレジスタが足りなくなると、コンパイラはレジスタからメモリ（ないしはメモリからレジスタ）に値をコピーするが、メモリは通常レジスタより低速なので、通常より時間がかかる。なお「サーバ」仮想マシンではグラフ彩色によるレジスタ割り付けを行うため、こうした問題は生じない。
レジスタ割り付けの最適化はサンのJDK 6で導入された。これは同じレジスタを、可能な場合コードブロックをまたがって使用することでメモリアクセスを減らすもので、報告によればいくつかのベンチマークで約60%の性能向上が得られた。
クラスデータの共有.
オラクルJVMにおけるクラスデータの共有 (class data sharing: CDS) とは、Javaアプリケーションの起動時間を短くし、同時にを削減する仕組みである。JREがインストールされると、インストーラーはシステムJARファイル（Java のクラスライブラリを全て含むJARファイルで、rt.jarと呼ばれる）からいくつかのクラスを特別な内部表現形式でロードし、この内部表現を「共有書庫」ファイルとして書き出す。それ以降のJVMの呼び出し時には、共有書庫ファイルはメモリマッピングされ、これによってクラスをロードする時間を短縮し、複数 JVM プロセスがこれらのクラスのメタデータを共有できるようになる。
起動時間の短縮は、特に小さなプログラムで効果が著しい。
Sun の Java バージョンによる性能の向上.
上記以外にも、オラクルのJavaは各バージョンでJava APIの性能向上を多数盛り込んでいる。
JDK 1.1.6.
仮想マシンレベルの向上:
J2SE 1.2.
仮想マシンレベルの向上:
J2SE 1.3.
仮想マシンレベルの向上:
J2SE 1.4.
サン・マイクロシステムズが1.3から1.4での性能向上をまとめたリンクを参照
Java SE 5.0.
仮想マシンレベルの向上:
Java SE 6.
仮想マシンレベルの向上:
その他の向上:
今後の性能改善点.
今後の性能改善は、Java 6のupdateかJava 7で計画されている。
他の言語との比較.
Javaプログラムは通常仮想マシンによって実行時にJITコンパイルされるが、
C/C++のように事前コンパイルすることも可能である。JITコンパイルされた場合、その性能は一般的に
プログラムの速度.
Javaプログラムの平均的な性能は徐々に向上しており、Javaの性能はCやC++に匹敵するほどになっており、Javaが低速な場合もあるが、高速な場合もある。2009年3月の時点で、Java は Computer Language Benchmarks GameにおいてC/C++より5-15%遅い。ベンチマークは小規模で数値演算中心の性能を測定するとも言われる。これは、おそらくCに有利に働く。実際のプログラムでは、JavaがCを上回ったり性能の差はなかったりする。例として、（クローンで、GPLのCコードをJavaに変換して作成）のベンチマークがあり、Java 5.0のバージョンは、同じハードウェア構成でCの性能を上回る。データがどのように計測されたか明確ではないが（例えば、オリジナルのQuack 2の実行ファイルが1997年にコンパイルされたものであれば、現在のコンパイラではよりよい最適化を行うことができる）、Javaの同じソースコードがVMを更新するだけで大きく性能向上しており、これは100%静的にコンパイルする方法では達成できない。
また、Javaや類似の言語では可能な最適化で、C/C++では実施できないものもある。
しかし、JavaとC/C++でのベンチマークによる比較は実施する作業に大きく依存する。例えば、Java 5.0と比較すると、
起動時間.
Javaアプリケーションの起動時は、膨大な数のクラス（プラットフォームのクラスライブラリののクラスを含む）を使用前にロードしなければならないため、CやC++よりもかなり時間がかかることが多い。
起動時間の大半はJVMの初期化やクラスのロードそのものではなく、I/Oを伴う操作によるものと ("rt.jar"のクラスデータファイルは40MBあり、JVMは多数のデータをこの巨大なファイルをシークして取り出さなければならない)。いくつかの実験により、バイトコード検証分割の方法を用いるとクラスのロードは約40%向上するが、大規模なプログラムの起動時間は5%しか向上しないことがわかった。
改善幅は小さいものの、単純な操作を実行しすぐ終了するような小さなプログラムでは、Javaプラットフォームのデータローディングはプログラムの操作の数倍の負荷であるため、向上が目に見えやすい。
Java SE 6 Update 10より、オラクルのJREにはQuick Starterが同梱されるようになり、OSの起動時にクラスのデータをロードしておくことで、ディスクではなくディスクキャッシュからデータを読み出すことができるようになる。
では、この問題に対して別の方向からアプローチしている。JETのStartup Optimizerはアプリケーションの起動時にディスクから読み出すデータを削減し、さらにシーケンシャルに読み出せるよう配置する。
メモリ使用量.
Javaのメモリ使用量はC/C++より大きい。
三角関数.
Javaの三角関数の性能は、Cと比べて悪い。Javaが数値演算の結果に（使用するハードウェアとも合致しない場合もある）厳密な仕様を定義しているためである。
x87での絶対値formula_1/4以上の値に対するサイン、コサインの演算結果は、formula_1の値に近似値を用いるため正確ではない。JVMの実装ではソフトウェアで正確な演算を行わなければならず、その領域では大きな性能低下を引き起こす。
Java Native Interface.
Java Native Interfaceはオーバーヘッドが大きく、JVM上で動作するコードとネイティブコードの境界を行き来するコストが高くなっている。
ユーザインタフェース.
Swingはウィジェットの描画をPure Javaで記述されたJava 2D APIに任せているため、ネイティブのウィジェット・ツールキットと比較して低速であるとされてきた。しかし、SwingとOSのネイティブGUIライブラリに描画処理を任せるStandard Widget Toolkitをベンチマークで比較しても、片方が明確に速いわけではなく、結果はコンテキストや環境に大きく依存した。
高性能計算分野での Java の使用.
いくつかの独立した研究によれば、高性能計算 (HPC) における Javaの性能は、演算中心のベンチマークでFORTRANと同等であるが、JVMはグリッドネットワーク上の通信が多くなると、スケーラビリティに問題があるようである。
しかし、Javaで記述された高性能計算のアプリケーションがベンチマークで最高の成績を出したことがある。2008年、Javaで記述されたHPCのプロジェクト Apache Hadoop が、テラバイト級の整数のソートで最高速の結果を出した。