javaでjavaagentとagentlibを調査してみる
http://docs.oracle.com/javase/6/docs/technotes/tools/solaris/java.html
の-agentlib, -javaagentの項目について調べてみた
あれ、なんか俺agentlibとjavaagentを勘違いしている、、、、?
-agentlib
-agentlib:hprofや-agentlib:jdwpなどプロファイラやデバッガなどを利用するネイティブライブラリを指定する
詳しくはJVMTI(Java Virtual Machine Tools Interface)を参照することつまりCやC++を使って、VMにアクセスしてプロファイル情報などを引き抜いたり、デバッグ用に挙動を操作したりすることができるらしい
JVMTIの日本語DOC
http://java.sun.com/javase/ja/6/docs/ja/platform/jvmti/jvmti.html#starting
JVMTIを利用する
http://www.02.246.ne.jp/~torutk/javahow2/jvmti.html
-javaagent
java.lang.instrumentパッケージを使ってバイトコードをVM起動時に動的に書き換えたりできる。
下記さくらばさんの書き込みがそのままです
http://www.atmarkit.co.jp/bbs/phpBB/viewtopic.php?topic=26578&forum=12
以下バイトコードを動的に操作 Instrumentationを参考にjavaagentについて色々動作を試してみる
eclipseのworkspaceにできたクラスファイルをjarにまとめる
$ cd ~/Documents/workspace/lick_up_jvm/target/classes $ jar cvf sample.jar jp/co/watanabe_yusaku/instrument/InstrmentTest.class マニフェストが追加されました InstrmentTest.classを追加中です(入=982)(出=528)(46%収縮されました)
いったんjarを展開してマニフェストファイルにPremain-Classを追記する
$ jar xvf sample.jar META-INF/が作成されました \META-INF/MANIFEST.MFが展開されました \jp/co/watanabe_yusaku/instrument/InstrmentTest.classが展開されました $ vim META-INF/MANIFEST.MF ↓追記 Premain-Class: jp.co.watanabe_yusaku.instrument.InstrmentTest $ jar cvfm sample.jar META-INF/MANIFEST.MF jp/co/watanabe_yusaku/instrument/InstrmentTest.class マニフェストが追加されました jp/co/watanabe_yusaku/instrument/InstrmentTest.classを追加中です(入=982)(出=528)(46%収縮されました)
実行してみる
$ java -javaagent:sample.jar jp/co//watanabe_yusaku/main/HelloWorld class java.util.HashMap$EntryIterator class java.lang.Boolean 略 Hello, World!
ちゃんとHelloWorldクラスのmainが実行される前にInstrmentTestクラスのpremainが実行されてるようだ。
この仕組みを使えば稼働中のアプリケーションの構造(設定ファイルなどを含む)を一切変更せずに挙動を変更することができる。
以下のようなコードをjarで固めてjavaagent指定すると全てのメソッドの実行時間をミリ秒で出力することができた。
import java.io.ByteArrayInputStream; import java.io.IOException; import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.IllegalClassFormatException; import java.lang.instrument.Instrumentation; import java.security.ProtectionDomain; import javassist.CannotCompileException; import javassist.ClassPool; import javassist.CtClass; import javassist.CtMethod; public class MethodTimer { private static ClassPool classPool; @SuppressWarnings("rawtypes") public static void premain(String agentArgs, Instrumentation inst) { classPool = ClassPool.getDefault(); inst.addTransformer(new ClassFileTransformer() { public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { try { ByteArrayInputStream stream = new ByteArrayInputStream(classfileBuffer); CtClass ctClass = classPool.makeClass(stream); CtMethod[] ctMethods = ctClass.getDeclaredMethods(); for(CtMethod ctMethod : ctMethods) { ctMethod.addLocalVariable("start", CtClass.longType); ctMethod.insertBefore("start = System.currentTimeMillis();"); ctMethod.insertAfter("System.out.println(\"" + ctClass.getName() + "." + ctMethod.getName() + "()" + ":exec time = \" + (System.currentTimeMillis() - start));"); } return ctClass.toBytecode(); }catch(CannotCompileException | IOException e) { IllegalClassFormatException ecfe = new IllegalClassFormatException(); ecfe.initCause(e); throw ecfe; } } }); } }
マニフェストファイルは
Premain-Class: MethodTimer Boot-Class-Path: javassist.jar Can-Redefine-Classes: true
mainクラスは
public class HelloWorld { public static void main(String[] args) throws Exception{ HelloWorld h = new HelloWorld(); h.execute(args); } public void execute(String[] args) throws Exception{ this.say("Hello, World0"); this.say("Hello, World1"); Thread.sleep(3000); this.say("Hello, World2"); this.say("Hello, World3"); } public void say(String str) { System.out.println(str); } }
実行ディレクトリにjavasisst.jarとsample.jarを配置してから実行すると
java -javaagent:sample.jar HelloWorld sun.launcher.LauncherHelper.makePlatformString():exec time = 0 sun.launcher.LauncherHelper.getMainMethod():exec time = 0 sun.launcher.LauncherHelper.checkAndLoadMain():exec time = 6 Hello, World0 HelloWorld.say():exec time = 1 Hello, World1 HelloWorld.say():exec time = 1 Hello, World2 HelloWorld.say():exec time = 0 Hello, World3 HelloWorld.say():exec time = 0 HelloWorld.execute():exec time = 3004 HelloWorld.main():exec time = 3004
AOPを導入するまでもないけど、ちょっと本番環境で設定ファイルなどを含む環境などを一切変更しないでプロファイリングしたい時に便利かもしれないですね