JVMのメモリを掘り下げます。 フラグ操作



HotSpot JVMには、仮想マシンで何が起こっているかを追跡するための多くのオプションがあります: PrintGCPrintCompilationTraceClassLoadingなど。 通常、これらはコマンドラインオプションに含まれています(例-XX:+PrintGCDetails 。 ただし、他のパラメーターでJVMを再起動できない場合、アプリケーションの操作中にこのフラグを直接有効または無効にすることが必要になる場合があります。 これは、通常の方法とハッカーの方法の両方で実現できますが、後者はより強力で興味深い方法です。 ただし、どちらも注目に値します。

この記事から次のことを学びます。




HotSpot JVMのフラグは何ですか


説明付きのすべてのフラグのリストは、OpenJDKソースで利用できます: globals.hppの主要部分と、 アーキテクチャコンパイラ 、およびG1コレクターの追加オプション。

ご覧のとおり、フラグはさまざまなマクロによって定義されています。


JVMのバージョンで使用可能なすべてのフラグとその現在の値を表示するには、 PrintFlagsFinalパラメーターを指定してJVMを実行する必要があります。
 java -XX:+PrintFlagsFinal 


JMXを介してフラグを操作する


HotSpotを使用すると、 Management APIを介していくつかのフラグの値をプログラムで読み取ったり設定したりできます。 さらに、 リモート管理を有効にすると、リモートサーバー上でもこれを実行できます。

まず、 com.sun.management:type=HotSpotDiagnostic type=HotSpotDiagnosticという名前のMXBeanインスタンスを取得する必要がありcom.sun.management:type=HotSpotDiagnostic
 import com.sun.management.HotSpotDiagnosticMXBean; import java.lang.management.ManagementFactory; import javax.management.MBeanServer; ... MBeanServer server = ManagementFactory.getPlatformMBeanServer(); HotSpotDiagnosticMXBean bean = ManagementFactory.newPlatformMXBeanProxy( server, "com.sun.management:type=HotSpotDiagnostic", HotSpotDiagnosticMXBean.class); 


bean.getVMOption(String option)メソッドは、JVMオプションの現在の値をbean.getVMOption(String option)ます。
and bean.setVMOption(String option, String newValue) -新しいものを設定します。
フラグを読み取ることができる場合は、 manageable可能なもののみを変更できます。
bean.getDiagnosticOptions()メソッドは、 manageableすべてのオプションのリストを返します。

例:
 //   JVM,    ReentrantLock  ..  thread dump bean.setVMOption("PrintConcurrentLocks", "true"); 


JVMダイレクトメモリアクセス


残念ながら、JMXを介して変更されるオプションのセットはわずかです。 ただし、JVMフラグは、プロセスのアドレス空間にある単なる普通の変数です。 変数のアドレスがわかっている場合は、 Unsafe APIを使用して変数から新しい値を書き込むことができます 。 JVMフラグのアドレスを見つけることは残っています。 起動から起動まで、アドレスはオペレーティングシステムの意志で変わるため、タスクは簡単ではありません。 幸いなことに、Linuxは非常に適応性の高いOSであり、正しく尋ねられれば、必要な情報をすべて提供してくれます。

  1. 最初に、 libjvm.so仮想マシンライブラリのlibjvm.so 、ロードされているアドレスを確認する必要があります。 proc仮想ファイルシステムは、特に、現在のプロセスの仮想アドレス空間のすべての領域をリストするfile /proc/self/mapsに役立ちます。 その中に/libjvm.so終わる行が/libjvm.soます。
     2b6707956000-2b67084b8000 r-xp 00000000 68:02 1823284 /usr/java/jdk1.7.0_40/jre/lib/amd64/server/libjvm.so 

    最初の番号(0x2b6707956000)は、ライブラリがロードされるベースアドレスになります。

    これらはすべて純粋なJavaで実行できることに注意してください!
     private String findJvmMaps() throws IOException { BufferedReader reader = new BufferedReader(new FileReader("/proc/self/maps")); try { for (String s; (s = reader.readLine()) != null; ) { if (s.endsWith("/libjvm.so")) { return s; } } throw new IOException("libjvm.so not found"); } finally { reader.close(); } } 

  2. 重要な瞬間がlibjvm.solibjvm.soファイルを読み取り用に開きます。 オープンソースのELFパーサーを使用して、ライブラリ内にシンボルセクションがあり、すべてのJVMフラグがシンボル間に存在します。 繰り返しますが、ハッキングはなく、純粋なJavaのみです。
     ElfReader elfReader = new ElfReader(jvmLibrary); ElfSymbolTable symtab = (ElfSymbolTable) elfReader.section(".symtab"); 

  3. 手順1で取得したライブラリのベースアドレスをシンボルアドレスに追加し、フラグ値が格納されているメモリ内の目的の場所を取得します。 Unsafe.putIntを使用して簡単に変更できます。
     private ElfSymbol findSymbol(String name) { for (ElfSymbol symbol : symtab) { if (name.equals(symbol.name()) && symbol.type() == ElfSymbol.STT_OBJECT) { return symbol; } } throw new NoSuchElementException("Symbol not found: " + name); } public void setIntFlag(String name, int value) { ElfSymbol symbol = findSymbol(name); unsafe.putInt(baseAddress + symbol.value(), value); } public void setBooleanFlag(String name, boolean value) { setIntFlag(name, value ? 1 : 0); } 



おわりに


ご覧のとおり、Javaでは、1行のネイティブコードなしで、仮想マシン自体を含むランタイム環境を制御できます。 ただし、文書化されていないメソッドを使用するのは危険であり、本番環境での使用を推奨するものではありません。

実験の完全なソースコードはGitHubにあります

詳細については、10月15日にサンクトペテルブルグで開催されるJoker Javaテクノロジー会議に参加してください。 Odnoklassnikiから、JVMを含む3つのレポートが提示されます。

Source: https://habr.com/ru/post/J195004/


All Articles