序文
バックグラウンド
Javaショップに行ってきました。主なアプリケーションは Java である分散システムでのパフォーマンス テストの実行に専念するために丸 1 か月を費やしました。その中には、Sun 自身 (当時は Oracle) によって開発および販売された製品を暗示するものもあります。
私が学んだ教訓、JVM についての歴史、内部構造についての話、いくつかのパラメーターの説明、そして最後にいくつかのチューニングについて説明します。実際に適用できるように、それを要点に留めようとしています。
Java の世界では物事が急速に変化しているため、昨年私がすべてを行って以来、その一部はすでに時代遅れになっている可能性があります。(Java 10 はもう出ていますか?)
良い習慣
あなたがすべきこと:ベンチマーク、ベンチマーク、ベンチマーク!
パフォーマンスについて本当に知る必要がある場合は、ワークロードに固有の実際のベンチマークを実行する必要があります。代替手段はありません。
また、JVM を監視する必要があります。監視を有効にします。優れたアプリケーションは、通常、監視 Web ページや API を提供します。それ以外の場合は、一般的な Java ツール (JVisualVM、JMX、hprof、およびいくつかの JVM フラグ) があります。
通常、JVM を調整してもパフォーマンスは向上しないことに注意してください。それはむしろ「クラッシュするかクラッシュしないか、遷移点を見つける」ことです。その量のリソースをアプリケーションに与えると、その見返りとしてその量のパフォーマンスが一貫して期待できるということを知ることです。知識は力である。
パフォーマンスは主にアプリケーションによって決まります。より速くしたい場合は、より良いコードを書かなければなりません。
ほとんどの場合、信頼できる機密性の高いデフォルトで生活する
すべてのアプリケーションを最適化して調整する時間はありません。ほとんどの場合、適切なデフォルトをそのまま使用します。
新しいアプリケーションを構成するときに最初に行うことは、ドキュメントを読むことです。重要なアプリケーションのほとんどには、JVM 設定に関するアドバイスを含む、パフォーマンス チューニングのガイドが付属しています。
次に、アプリケーションを構成できます。JAVA_OPTS: -server -Xms???g -Xmx???g
-server
: 完全な最適化を有効にします (このフラグは最近のほとんどの JVM で自動的に設定されます)
-Xms
-Xmx
: 最小ヒープと最大ヒープを設定します (両方の値は常に同じです。最適化はこれだけです)。
よくできました。JVM について知っておくべきすべての最適化パラメータについて理解できました。おめでとうございます! それは簡単でした:D
してはいけないこと:
インターネットで見つけたランダムな文字列をコピーしないでください。特に、次のような複数の行が含まれている場合は注意してください。
-server -Xms1g -Xmx1g -XX:PermSize=1g -XX:MaxPermSize=256m -Xmn256m -Xss64k -XX:SurvivorRatio=30 -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+UseCMSInitiatingOccupancyOnly -XX:CMSInitiatingOccupancyFraction=10 -XX:+ScavengeBeforeFullGC -XX:+CMSScavengeBeforeRemark -XX:+PrintGCDateStamps -verbose:gc -XX:+PrintGCDetails -Dsun.net.inetaddr.ttl=5 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=`date`.hprof -Dcom.sun.management.jmxremote.port=5616 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -server -Xms2g -Xmx2g -XX:MaxPermSize=256m -XX:NewRatio=1 -XX:+UseConcMarkSweepGC
たとえば、Google の最初のページにあるこのことは、まったくひどいものです。競合する値を持つ引数が複数回指定されています。JVM のデフォルトを強制するだけのものもあります (最終的には、2 つ前の JVM バージョンのデフォルトになります)。いくつかは時代遅れであり、単に無視されています。そして最後に、少なくとも 1 つのパラメーターが無効であるため、単に存在するだけで起動時に JVM が一貫してクラッシュします。
実際のチューニング
メモリ サイズの選択方法:
アプリケーションからガイドを読んでください。何らかの兆候が見られるはずです。生産を監視し、後で調整します。精度が必要な場合は、いくつかのベンチマークを実行してください。
重要な注意: Java プロセスは、最大ヒープに 10%を加えた量を使用します。X% のオーバーヘッドはヒープ管理であり、ヒープ自体には含まれません。
通常、すべてのメモリは、起動時にプロセスによって事前に割り当てられます。常に最大ヒープを使用しているプロセスが表示される場合があります。それは単に真実ではありません。実際に何が使用されているかを確認するには、Java 監視ツールを使用する必要があります。
適切なサイズを見つける:
- OutOfMemoryException でクラッシュする場合は、メモリが不足しています
- OutOfMemoryException でクラッシュしない場合は、メモリが多すぎます
- メモリが多すぎるが、ハードウェアがそれを取得したか、すでに支払い済みである場合、それは完璧な数であり、仕事は完了です!
JVM6 はブロンズ、JVM7 はゴールド、JVM8 はプラチナ...
JVM は常に改善されています。ガベージ コレクションは非常に複雑なものであり、多くの非常に賢い人々が取り組んでいます。過去 10 年間で大幅な改善が見られましたが、今後もその傾向が続くでしょう。
情報提供を目的としています。これらは、Oracle Java 7-8 (HotSpot) および OpenJDK 7-8 で少なくとも 4 つの利用可能なガベージ コレクターです。(他の JVM は、Android、IBM、組み込みなど、まったく異なる場合があります):
- シリアルGC
- パラレルGC
- ConcurrentMarkSweepGC
- G1GC
- (およびバリアントと設定)
[Java 7 以降。Oracle と OpenJDK のコードは部分的に共有されています。GC は両方のプラットフォームで (ほとんど) 同じである必要があります。]
JVM >= 7 には多くの最適化があり、まともなデフォルトを選択します。プラットフォームによって少し異なります。それは複数のことをバランスさせます。たとえば、マルチコアの最適化を有効にするか、CPU に複数のコアがあるかどうかを決定します。あなたはそれをやらせるべきです。GC 設定を変更または強制しないでください。
コンピューターに決定を任せても問題ありません (それがコンピューターの目的です)。すべてのボックスで「一時停止時間を短縮するために常に 8 コアのアグレッシブ コレクション」を強制するよりも、JVM 設定を常に 95% 最適にする方がよいでしょう。
例外: アプリケーションにパフォーマンス ガイドと特定のチューニングが用意されている場合。提供された設定をそのままにしておくことはまったく問題ありません。
ヒント: 最新の改善点を利用するために新しい JVM に移行すると、多くの労力をかけずに十分なブーストが得られる場合があります。
特殊なケース: -XX:+UseCompressedOops
JVM には、内部で 32 ビットのインデックスを強制的に使用する特別な設定があります (読み取り: ポインターのようなもの)。これにより、4 294 967 295 オブジェクト * 8 バイト アドレス => 32 GB のメモリをアドレス指定できます。(REAL ポインターの 4GB アドレス空間と混同しないでください)。
これにより、全体的なメモリ消費量が削減され、すべてのキャッシュ レベルにプラスの影響を与える可能性があります。
実際の例: ElasticSearch のドキュメントでは、実行中の 32 GB 32 ビット ノードは、メモリに保持される実際のデータに関して 40 GB 64 ビット ノードと同等である可能性があると記載されています。
歴史に関する注意: フラグは、Java-7 より前の時代 (おそらく Java-6 より前) には不安定であることが知られていました。しばらくの間、新しいJVMで完全に機能しています。
Java HotSpot™ 仮想マシンのパフォーマンス強化
[...] Java SE 7 では、-Xmx が指定されていない場合、および -Xmx の値が 32 ギガバイト未満の場合、圧縮 oops の使用が 64 ビット JVM プロセスのデフォルトです。6u23 リリースより前の JDK 6 の場合、java コマンドで -XX:+UseCompressedOops フラグを使用して機能を有効にします。
参照: ここでも、JVM は手動チューニングよりも何年も先を行っています。それでも、それについて知ることは興味深いです =)
特殊なケース: -XX:+UseNUMA
Non-Uniform Memory Access (NUMA) は、マルチプロセッシングで使用されるコンピューターのメモリ設計です。メモリ アクセス時間は、プロセッサに対するメモリ位置によって異なります。出典:ウィキペディア
最新のシステムは、複数のレイヤーのメモリとキャッシュ (プライベートまたは共有) をコアと CPU にまたがって使用する、非常に複雑なメモリ アーキテクチャを備えています。
明らかに、現在のプロセッサの L2 キャッシュ内のデータにアクセスする方が、別のソケットからメモリ スティックにアクセスするよりもはるかに高速です。
現在販売されているすべてのマルチソケットシステムは設計上 NUMA ですが、コンシューマ システムはすべてそうではありません。numactl --show
Linuxのコマンドを使用して、サーバーが NUMA をサポートしているかどうかを確認します。
NUMA 対応フラグは、基盤となるハードウェア トポロジのメモリ割り当てを最適化するよう JVM に指示します。
パフォーマンスが大幅に向上する可能性があります (つまり、2 桁: +XX%)。実際、「NOT-NUMA 10CPU 100GB」から「NUMA 40CPU 400GB」に切り替えると、フラグを知らなければパフォーマンスが[劇的に]低下する可能性があります。
注: NUMA を検出し、JVM http://openjdk.java.net/jeps/163でフラグを自動的に設定するための議論があります。
おまけ: 大きなファット ハードウェア (つまり NUMA) で実行するすべてのアプリケーションは、最適化する必要があります。これは、Java アプリケーションに固有のものではありません。
未来に向かって: -XX:+UseG1GC
ガベージ コレクションの最新の改善点は、G1 コレクター (読み取り: ガベージ ファースト)です。
これは、ハイ コア、ハイ メモリ システムを対象としています。絶対最小で 4 コア + 6 GB メモリ。これは、その 10 倍以上を使用するデータベースおよびメモリ集約型アプリケーションを対象としています。
短いバージョンですが、これらのサイズでは、従来の GC は一度に処理するには多すぎるデータに直面しており、一時停止が手に負えなくなっています。G1 はヒープを、アプリケーションの実行中に独立して並行して管理できる多くの小さなセクションに分割します。
最初のバージョンは 2013 年に利用可能になりました。現在は製品として十分に成熟していますが、すぐにデフォルトになることはありません。これは、大規模なアプリケーションで試してみる価値があります。
触れないでください: 世代サイズ (NewGen、PermGen...)
GC はメモリを複数のセクションに分割しました。(詳細には触れませんが、「Java GC Generations」をグーグルで検索できます。)
前回、10000ヒット/秒のアプリで世代フラグの20の異なる組み合わせを試すために1週間を費やしました。-1% から +1% の素晴らしいブーストを得ていました。
Java GC 世代は、論文を読んだり書いたりするのに興味深いトピックです。本当に最適化が必要な 1% の人々の中で、ごくわずかな利益のためにかなりの時間を費やすことができる 1% の一員でない限り、それらは調整するものではありません。
結論
これがあなたを助けることを願っています。JVM をお楽しみください。
Java は世界で最高の言語であり、最高のプラットフォームです! 愛を広めに行きましょう:D