
HotSpot JVMには、仮想マシンで何が起こっているかを追跡するための多くのオプションがあります:
PrintGC
、
PrintCompilation
、
TraceClassLoading
など。 通常、これらはコマンドラインオプションに含まれています(例
-XX:+PrintGCDetails
。 ただし、他のパラメーターでJVMを再起動できない場合、アプリケーションの操作中にこのフラグを直接有効または無効にすることが必要になる場合があります。 これは、通常の方法とハッカーの方法の両方で実現できますが、後者はより強力で興味深い方法です。 ただし、どちらも注目に値します。
この記事から次のことを学びます。
- すべてのJVMフラグを見つける場所、およびそれらがどのタイプに分割されているか。
- JMXを使用してプログラムでフラグを読み取りまたは設定する方法。
- 仮想マシンのメモリ内で適切な領域を見つけて、それを
台無しにして修正する方法。
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ダイレクトメモリアクセス
残念ながら、JMXを介して変更されるオプションのセットはわずかです。 ただし、JVMフラグは、プロセスのアドレス空間にある単なる普通の変数です。 変数のアドレスがわかっている場合は、
Unsafe APIを使用して変数から新しい値を書き込むことができ
ます 。 JVMフラグのアドレスを見つけることは残っています。 起動から起動まで、アドレスはオペレーティングシステムの意志で変わるため、タスクは簡単ではありません。 幸いなことに、Linuxは非常に適応性の高いOSであり、正しく尋ねられれば、必要な情報をすべて提供してくれます。
- 最初に、
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(); } }
- 重要な瞬間が
libjvm.so
: libjvm.so
ファイルを読み取り用に開きます。 オープンソースのELFパーサーを使用して、ライブラリ内にシンボルセクションがあり、すべてのJVMフラグがシンボル間に存在します。 繰り返しますが、ハッキングはなく、純粋なJavaのみです。
ElfReader elfReader = new ElfReader(jvmLibrary); ElfSymbolTable symtab = (ElfSymbolTable) elfReader.section(".symtab");
- 手順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つのレポートが提示されます。