ブラックジャックや自動ガベージコレクションなしでJDKをアセンブルする方法

最近のJava Oneで、 Ruslan chereminは、 Disruptor開発者がガベージコレクタなしでJVMを使用する方法について話しました。 彼らにはそれに対する独自の理由があり、それはこのトピックとは何の関係もありません。



私は長い間、仮想マシンのソースコードを掘り下げたいと思っていましたが、それからGCを削除することは素晴らしいスタートです。 カットの下で、OpenJDKをアセンブルし、そこからガベージコレクターを切り取り、再度収集する方法を説明します。 最後に、あなたの頭に浮かんだ「なぜ」という質問に答えさえ与えられます。







ソース? さらに2つ与えて、バイナリを振りかけます!



メインコース



OpenJDKはフォレストを使用してMercurialに保存され、コードを取得する最も簡単な方法は



$ hg fclone http://hg.openjdk.java.net/jdk7/jdk7
      
      





フォレスト拡張機能がインストールされておらず、何らかの理由でインストールしたくない場合、これを行うことができます。



 $ hg clone http://hg.openjdk.java.net/jdk7/jdk7 && jdk7/get_source.sh
      
      







別のオプションは、完全なオフサイトバンドルをダウンロードすることです。 これはいくつかのコーナーをカットするのに役立ちますが、バージョン管理システムを使用する魅力を奪います。



興味深い機能:何らかの理由で、jaxpとjaxwsは別のリポジトリに格納されます。 したがって、適切なサイト( jaxp.java.netおよびjax-ws.java.net )からそれらを手動でダウンロードするか、 ALLOW_DOWNLOADS=true



ALLOW_DOWNLOADS=true



してALLOW_DOWNLOADS=true



が必要なものすべてmake



ダウンロードできるmake



する必要があります。 個人的には、このオプションは私にとってより便利なようです。 そうそう、完全なソースバンドルでは、すべてが既にダウンロードされています。



皿を調理できない道具



明確なビジネス、組み立てには多くのすべてが必要になります。 最も簡単なのは、少なくともバージョン1.6のブートストラップjdkです。 変数ALT_BOOTDIR



を使用して、パスを指定する必要があります。 さらに、明らかなant



やmakeからCUPSやALSAまで、すべての巨大な山が必要です。 正確にすべてを保持する最も簡単な方法は、パッケージマネージャーにすべてのビルド依存関係を満たすよう依頼することです。 たとえば、aptitudeを使用する場合:



 $ aptitude build-dep openjdk-6
      
      







何が起こっているのかを確認する

必要なものがすべて揃っていることを確認するには、 sanity



目標にmake



を実行make



必要があります。 環境変数の設定に注意してください。



 $ LANG=C ALT_BOOTDIR=/usr/lib/jvm/java-6-openjdk make sanity
      
      







すべてが順調であれば、碑文のSanity check passed







すべてが悪い場合、かなりわかりやすいエラーメッセージが表示されます。 それを修正して再試行してください。



これで、jdk自体をビルドできます。 環境変数は以前にALLOW_DOWNLOADS



追加しALLOW_DOWNLOADS





 $ ALLOW_DOWNLOADS=true LANG=C ALT_BOOTDIR=/usr/lib/jvm/java-6-openjdk make
      
      







20〜40分で成功した場合は、次の形式のメッセージを受け取ります。



 #-- Build times ---------- Target all_product_build Start 2012-04-20 01:56:53 End 2012-04-20 02:02:14 00:00:06 corba 00:00:09 hotspot 00:00:06 jaxp 00:00:08 jaxws 00:04:47 jdk 00:00:05 langtools 00:05:21 TOTAL
      
      







有用なものが実際に収集されたことを確認して、次のステップに進むことができます。



 $ ./build/linux-amd64/bin/java -version openjdk version "1.7.0-vasily_p00pkin" OpenJDK Runtime Environment (build 1.7.0-vasily_p00pkin-gs_2012_04_20_01_06-b00) OpenJDK 64-Bit Server VM (build 23.0-b21, mixed mode)
      
      







別のオペレーティングシステムを使用しています...



... BSDベース



ここではそれほど悪くはありません。 オラクルの優秀なスタッフの厳しい指導の下、私はサプサンの前庭にあるMacbookにホットスポットを組み立てることができました。 しかし、翌夜のJDK全体は実際にはうまくいきませんでした。 ただし、これを行うことができます。新しいXcodeと多くの忍耐が必要です。 私はどちらも持っていなかったので、Selectelクラウドでより強力なマシンを起動し、実験を行ったところです。 ボーナスとして、ラップトップをロードしていない間、クラウドでのアセンブリが高速であるため、この時点で(剣で戦う代わりに、椅子に乗って)何か役に立つことができます。 それでもポピーで収集したい場合は、プロセスの説明を以下に示します。



...まあ、あなたはそれを知っていますか?



実際、ここでもすべてがそれほど悪いわけではありません。 シグウィンとスモークマナで武装してください。



最も興味深いの始まり

-患者、あなたは倒錯に苦しんでいますか?

-あなたは何ですか、博士! 私はそれらを楽しみます!

ここで、ガベージコレクターを切り取るためにソース内のどこを呼び出す必要があるかを理解するタスクに直面しています。 これを行うには、3つの明白な方法があります。知っている人に尋ねる、すべてのソースコードを読む、またはunningで機知に富むことを示す。 最初の方法はうまくいきませんでした。知識のある人々は私を奇妙に見つめ、そのような疑わしい行動に参加することを拒否して去ったからです。 2番目の方法は、イデオロギーの観点からは最も正確でしたが、当時は残念でした。 したがって、3番目の方法は残りました。



論理的に推論してみましょう。外部からガベージコレクターにどのように影響を与えることができますか? 起動時にキーを使用する( -XX:+UseParallelGC



)とSystem.gc()



を使用するという2つのパスがすぐに思い浮かびます。 前者はより論理的に見えますが、私は後者から始めることにしました。なぜなら、javadocsはそこで何が起こっているのかについての関心を完全に満たすことができないからです。 Javaソースでは、この呼び出しはランタイムに委任され、メソッドは既にネイティブです。 JNIを使​​用したことがある人なら誰でも、関数名がネイティブコードJava_java_lang_Runtime_gc



コンパイルされる方法を知っています。 Quick grep



jdk/src/share/native/java/lang/Runtime.c



にそのようなコードを作成しjdk/src/share/native/java/lang/Runtime.c



。ここでは、次の行に興味があります。

 62 63 64 65 66
      
      





 JNIEXPORT void JNICALL Java_java_lang_Runtime_gc(JNIEnv *env, jobject this) { JVM_GC(); }
      
      





JVM_GC



JVM_GC



探していJVM_GC



。 彼の広告はsrc/share/vm/prims/jvm.cpp



ます:

 404 405 406 407 408 409
      
      





 JVM_ENTRY_NO_ENV(void, JVM_GC(void)) JVMWrapper("JVM_GC"); if (!DisableExplicitGC) { Universe::heap()->collect(GCCause::_java_lang_system_gc); } JVM_END
      
      



ここには、2つの非常に興味深いポイントがあります。1つ目は、コメントを必要としないDisableExplicitGC



と、 Universe::heap()



collect



メソッドです。 それがいかに簡単か: System.gc()



はコレクターが同期的に開始することだけを行うことが判明しました。 ドラマはありません。 えー さて、何もありませんが、 collect()



メソッドでアセンブリを無効にできる可能性が高いことがわかりました。 ファイルhotspot/src/share/vm/memory/universe.hpp



でUniverseクラスを簡単に発見し、静的heap



メソッドがCollectedHeap*



返し、 initialize_heap()



メソッドが存在することを確認しinitialize_heap()







宇宙をテーマにした少し叙情的な余談



OpenJDKのコードの品質は優れていると言わざるを得ません。優れた構造、何が起きているのか、多くのコメントを理解するのは簡単です。 たとえば、ここに素晴らしいスニペットがあります:

 121 122 123 124 125 126 127
      
      





 class Universe: AllStatic { // Ugh. Universe is much too friendly. friend class MarkSweep; friend class oopDesc; //   friend' //... }
      
      





さて、コレクターに戻りましょう。 initialize_heap()



メソッドはヒープを作成し、ユーザーが指定したコレクターに応じて、特定の実装が選択されます。 完全なリストは、 hotspot/src/share/vm/gc_interface/collectedHeap.hpp



ファイルにあります。



 192 193 194 195 196 197 198
      
      





 enum Name { Abstract, SharedHeap, GenCollectedHeap, ParallelScavengeHeap, G1CollectedHeap };
      
      





クラスの学習を続けると、最終的に必要なコードが見つかります。



 519 520 521 522 523 524 525 526 527 528
      
      





 // Perform a collection of the heap; intended for use in implementing // "System.gc". This probably implies as full a collection as the // "CollectedHeap" supports. virtual void collect(GCCause::Cause cause) = 0; // This interface assumes that it's being called by the // vm thread. It collects the heap assuming that the // heap lock is already held and that we are executing in // the context of the vm thread. virtual void collect_as_vm_thread(GCCause::Cause cause) = 0;
      
      





ここでは、コメントが最も役立ちます。 英語を十分に知らない人のために、私は説明します:最初のメソッド、単にcollect()



、「外部から」(たとえば、 System.gc



から、または同じgrep



示すように、Linuxのメモリ割り当てが失敗した場合)組み立てられることを意図しています 2つ目は、ガベージコレクションを担当する仮想マシンスレッドから起動されます(必要なすべてのロックが既に保持されていることを前提としています)。 簡単な解決策がすぐに思い浮かびます。これらのメソッドが呼び出されたときに、アセンブリが発生しないようにするためです。 私はこのアプローチを初めて試しましたが、それは不運です。すべてが多少複雑であり、各ヒープ実装にはアセンブリが行われる独自の追加の場所があることがわかりました。 そのため、特定の実装(最も単純なものとしてGenCollectedHeap



MarkSweepPolicy



したMarkSweepPolicy



)を選択する必要があり、フラグ(これをUseTheForce



と呼びUseTheForce



)に応じて、何もせずにアセンブリを生成するメソッドを終了する必要がありました。 その結果、最初のバージョンの変更は次のようになりました



やってみます!



ガベージコレクターの通常の操作中にOOMをスローしないクラスを簡単に説明しますが、クラスが存在しない場合、これは大きな喜びで実行されます。

 1 2 3 4 5 6 7 8 9 10 11
      
      





 public class TheForceTester { public static final int ARRAY_SIZE = 1000000; public static void main(String[] args) { while (true) { byte[] lotsOfUsefulData = new byte[ARRAY_SIZE]; } } }
      
      



そして、新しい仮想マシンを使用してこのビジネスを開始します。



 $ ./build/linux-amd64/bin/java -XX:+UseTheForce -verbose:gc TheForceTester Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at ru.yandex.holocron.core.TheForceTester.main(TheForceTester.java:10)
      
      







やった! 激しいワイン! さらに、テスターアプリケーションでは、現在の空き領域の出力を追加し、他のすべても正常に動作しているように確認できます: Xmx != Xms



ヒープが拡張し、等しい場合、空き領域は理論上必要なだけ正確に減少します。 クラス! スプーン1杯分のタールを追加するだけです。



免責事項と同じ質問への回答



その同じ質問によって、私は、もちろん、「そしてなぜ?!」を意味します。 トピックの冒頭で、パフォーマンスが非常に重要なDisruptorについて説明しました。 ご存知のように、ガベージコレクターでは、アプリケーションに予測不可能な遅延が発生します。 したがって、ほとんどのオブジェクトを再利用し、時々再起動することが可能であれば、GCを飲みました-これは非常に高速な方法です。



また、できるかどうかを見たいからです。 また好奇心が強い。



免責事項は次のとおりです。指定されたソリューションはかなり汚れており、概念実証としての役割を果たします。 まず、実際にガベージコレクションを瞬時に作成したため、仮想マシンでコレクターを使用することで他のさまざまなオーバーヘッドが発生しました。 良い意味で、これらのオーバーヘッドを完全に排除するCollectedHeap



実装を書く価値がありました。 ただし、この後でも、たぶんいくつかの場所を探し回る必要があります。



このすべてから何が続きますか? さらにトピックをお待ちください! :)



PS他に何をしますか?



linux-amd64アーカイブの下でコンパイルされたPPS: clck.ru/1-L-9 (Yandex.Disk)



PPPSリポジトリ全体を複製しないでください。 600メガバイト以上の重量があり、ホストされているマシンのトラフィックは有料です。 ただし、これは、java.netに傾いてから、単一のコミット( 3358:3f014511ecce )をプルすることを妨げるものではありません。



All Articles