私はすでにjvmにロードされている変更クラスをtringしています。私が見つけた解決策は:
transform
メソッドのクラス(Personなど)を変更します(コード:DemoTransformer)最初のステップから5番目のステップまで正常に動作しますが、retransformClasses
に問題があります。クラスを変更するコードを含むtransform
を再度呼び出しました。そして、それは私が変更したくない他のクラスを変更します。 addTransformer
またはretransformClasses
の間に問題が発生した可能性があります。しかし、私はまだ混乱しています。さて、クラスを再変換する方法は?何か案は? THX
public class AttachTest {
public static void main(String[] args) throws AttachNotSupportedException,
IOException, AgentLoadException, AgentInitializationException {
String agentPath = "D:\\work\\workspace\\myjar\\loaded.jar";
String vid = args[0];
VirtualMachine vm = VirtualMachine.attach(vid);
vm.loadAgent(agentPath);
}
}
//エージェント
public class AgentMain {
public static void agentmain (String agentArgs, Instrumentation inst)
throws ClassNotFoundException, UnmodifiableClassException,
InterruptedException {
Class<?> [] allLoadedClasses = inst.getAllLoadedClasses();
String tmpString = null;
for (int i = 0; i<allLoadedClasses.length; i++) {
tmpString = allLoadedClasses[i].getName();
if (0 != tmpString.length()) {
if (-1 != tmpString.lastIndexOf(".")) {
tmpString = tmpString.substring(tmpString.lastIndexOf(".")+1,tmpString.length());
}
if (tmpString.equals("Person")) {
inst.addTransformer(new DemoTransformer(), true);
inst.retransformClasses(allLoadedClasses[i]);
}
}
}
}
}
|
public class DemoTransformer implements ClassFileTransformer {
@Override
public byte[] transform (ClassLoader loader, String className,
Class<?> classBeingRedefined, ProtectionDomain protectionDomain,
byte[] classfileBuffer) throws IllegalClassFormatException {
ModifyMethodTest tm = new ModifyMethodTest(classfileBuffer);
byte[] byteArray = null;
try {
byteArray = tm.modiySleepMethod();
} catch (Exception e) {
e.printStackTrace();
}
return byteArray;
}
}
出力:アタッチプログラム
javax.management.RuntimeMBeanException: Java.lang.RuntimeException: Failed to transform [Person]
at com.Sun.jmx.interceptor.DefaultMBeanServerInterceptor.rethrow(DefaultMBeanServerInterceptor.Java:856)
at com.Sun.jmx.interceptor.DefaultMBeanServerInterceptor.rethrowMaybeMBeanException(DefaultMBeanServerInterceptor.Java:869)
at com.Sun.jmx.interceptor.DefaultMBeanServerInterceptor.invoke(DefaultMBeanServerInterceptor.Java:838)
at com.Sun.jmx.mbeanserver.JmxMBeanServer.invoke(JmxMBeanServer.Java:761)
at javax.management.remote.rmi.RMIConnectionImpl.doOperation(RMIConnectionImpl.Java:1427)
at javax.management.remote.rmi.RMIConnectionImpl.access$200(RMIConnectionImpl.Java:72)
at javax.management.remote.rmi.RMIConnectionImpl$PrivilegedOperation.run(RMIConnectionImpl.Java:1265)
at javax.management.remote.rmi.RMIConnectionImpl.doPrivilegedOperation(RMIConnectionImpl.Java:1360)
at javax.management.remote.rmi.RMIConnectionImpl.invoke(RMIConnectionImpl.Java:788)
at Sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at Sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.Java:39)
at Sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.Java:25)
at Java.lang.reflect.Method.invoke(Method.Java:597)
at Sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.Java:305)
at Sun.rmi.transport.Transport$1.run(Transport.Java:159)
at Java.security.AccessController.doPrivileged(Native Method)
at Sun.rmi.transport.Transport.serviceCall(Transport.Java:155)
at Sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.Java:535)
at Sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.Java:790)
at Sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.Java:649)
at Java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.Java:886)
at Java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.Java:908)
at Java.lang.Thread.run(Thread.Java:619)
at Sun.rmi.transport.StreamRemoteCall.exceptionReceivedFromServer(StreamRemoteCall.Java:255)
at Sun.rmi.transport.StreamRemoteCall.executeCall(StreamRemoteCall.Java:233)
at Sun.rmi.server.UnicastRef.invoke(UnicastRef.Java:142)
at com.Sun.jmx.remote.internal.PRef.invoke(Unknown Source)
at javax.management.remote.rmi.RMIConnectionImpl_Stub.invoke(Unknown Source)
at javax.management.remote.rmi.RMIConnector$RemoteMBeanServerConnection.invoke(RMIConnector.Java:993)
at AttachStackOverflow.main(AttachStackOverflow.Java:57)
Caused by: Java.lang.RuntimeException: Failed to transform [Person]
at loaded3.TransformerService.transform(TransformerService.Java:75)
at loaded3.TransformerService.transformClass(TransformerService.Java:38)
at Sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at Sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.Java:39)
at Sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.Java:25)
at Java.lang.reflect.Method.invoke(Method.Java:597)
at com.Sun.jmx.mbeanserver.StandardMBeanIntrospector.invokeM2(StandardMBeanIntrospector.Java:93)
at com.Sun.jmx.mbeanserver.StandardMBeanIntrospector.invokeM2(StandardMBeanIntrospector.Java:27)
at com.Sun.jmx.mbeanserver.MBeanIntrospector.invokeM(MBeanIntrospector.Java:208)
at com.Sun.jmx.mbeanserver.PerInterface.invoke(PerInterface.Java:120)
at com.Sun.jmx.mbeanserver.MBeanSupport.invoke(MBeanSupport.Java:262)
at com.Sun.jmx.interceptor.DefaultMBeanServerInterceptor.invoke(DefaultMBeanServerInterceptor.Java:836)
at com.Sun.jmx.mbeanserver.JmxMBeanServer.invoke(JmxMBeanServer.Java:761)
at javax.management.remote.rmi.RMIConnectionImpl.doOperation(RMIConnectionImpl.Java:1427)
at javax.management.remote.rmi.RMIConnectionImpl.access$200(RMIConnectionImpl.Java:72)
at javax.management.remote.rmi.RMIConnectionImpl$PrivilegedOperation.run(RMIConnectionImpl.Java:1265)
at javax.management.remote.rmi.RMIConnectionImpl.doPrivilegedOperation(RMIConnectionImpl.Java:1360)
at javax.management.remote.rmi.RMIConnectionImpl.invoke(RMIConnectionImpl.Java:788)
at Sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at Sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.Java:39)
at Sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.Java:25)
at Java.lang.reflect.Method.invoke(Method.Java:597)
at Sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.Java:305)
at Sun.rmi.transport.Transport$1.run(Transport.Java:159)
at Java.security.AccessController.doPrivileged(Native Method)
at Sun.rmi.transport.Transport.serviceCall(Transport.Java:155)
at Sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.Java:535)
at Sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.Java:790)
at Sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.Java:649)
at Java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.Java:886)
at Java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.Java:908)
at Java.lang.Thread.run(Thread.Java:619)
Caused by: Java.lang.UnsupportedOperationException: class redefinition failed: attempted to change the schema (add/remove fields)
at Sun.instrument.InstrumentationImpl.retransformClasses0(Native Method)
at Sun.instrument.InstrumentationImpl.retransformClasses(InstrumentationImpl.Java:124)
at loaded3.TransformerService.transform(TransformerService.Java:72)
... 31 more
出力:TARGET PROGRAM
print Call sayHello()
print Hello World!
Supported Redefine
Supported Retransform
Call transform() in TransformerService
Add transformer
support redefine. return TRUE
support retransforme. return TRUE
IsModifiable class "class Person". return TRUE
Retransform classes
Number of times to Call transform() in DemoTransformer:1
####ASM CODE####
consturct ModifyMethodTest
Call modifySleepMethod
new classreader
new classwriter
construct ModifyClassAdapter
sayHello
consturct Modifymethod
[arg1] = Java/io/PrintStream [arg2] = println #5
[arg1] = Java/io/PrintStream [arg2] = println #13
[arg1] = Java/util/concurrent/TimeUnit [arg2] = sleep #22
[arg1] = Java/io/PrintStream [arg2] = println #30
sayHello2
consturct Modifymethod
[arg1] = Java/io/PrintStream [arg2] = println #5
[arg1] = Java/io/PrintStream [arg2] = println #13
<init>
consturct Modifymethod
[arg1] = Java/lang/Object [arg2] = <init> #1
main
consturct Modifymethod
[arg1] = Person [arg2] = <init> #4
[arg1] = Person [arg2] = sayHello #9
[arg1] = Person [arg2] = sayHello2 #13
[arg1] = Java/lang/InterruptedException [arg2] = printStackTrace #21
getName
consturct Modifymethod
setName
consturct Modifymethod
Call visitend
Finished to call modifymethodtest
####End of ASM CODE
Remove transformer
Call transform() in TransformerService
Add transformer
support redefine. return TRUE
support retransforme. return TRUE
IsModifiable class "class Person". return TRUE
Retransform classes
Number of times to Call transform() in DemoTransformer:2
####ASM CODE####
consturct ModifyMethodTest
Call modifySleepMethod
new classreader
new classwriter
construct ModifyClassAdapter
sayHello
consturct Modifymethod
[arg1] = Java/io/PrintStream [arg2] = println #5
[arg1] = Java/io/PrintStream [arg2] = println #13
[arg1] = Java/util/concurrent/TimeUnit [arg2] = sleep #22
[arg1] = Java/io/PrintStream [arg2] = println #30
sayHello2
consturct Modifymethod
[arg1] = Java/io/PrintStream [arg2] = println #5
[arg1] = Java/io/PrintStream [arg2] = println #13
<init>
consturct Modifymethod
[arg1] = Java/lang/Object [arg2] = <init> #1
main
consturct Modifymethod
[arg1] = Person [arg2] = <init> #4
[arg1] = Person [arg2] = sayHello #9
[arg1] = Person [arg2] = sayHello2 #13
[arg1] = Java/lang/InterruptedException [arg2] = printStackTrace #21
getName
consturct Modifymethod
setName
consturct Modifymethod
Call visitend
Finished to call modifymethodtest
####End of ASM CODE
Remove transformer
print in sayHello()
print Call sayHello2()
print Hello World!2
短い答え
長い答え
ここにいくつかの推奨事項があります:
public void transformClass(String className)
のような操作を提供する必要があります。エージェントの取得したInstrumentationインスタンスへの参照で初期化する必要があります。 MBeanクラス、インターフェース、および必要なサードパーティのクラスは、エージェントのloaded.jarに含める必要があります。また、ModifyMethodTestクラスも含まれている必要があります(これはすでに含まれていると思います)。Instrumentation.addTransformer(theNewDemoTransformer, true)
を使用して、DemoTransformerインスタンスを登録します。Instrumentation.retransformClasses(ClassForName(className))
を呼び出します(MBean操作にバイナリクラス名を渡します)。この呼び出しが戻ると、クラスが変換されます。Intrumentation.removeTransformer(theNewDemoTransformer)
を使用してトランスフォーマーを削除します。以下は、私が何を意味するかのテストされていない近似です:
Transformer MBean
_public interface TransformerServiceMBean {
/**
* Transforms the target class name
* @param className The binary name of the target class
*/
public void transformClass(String className);
}
_
Transformer Service
_public class TransformerService implements TransformerServiceMBean {
/** The JVM's instrumentation instance */
protected final Instrumentation instrumentation;
/**
* Creates a new TransformerService
* @param instrumentation The JVM's instrumentation instance
*/
public TransformerService(Instrumentation instrumentation) {
this.instrumentation = instrumentation;
}
/**
* {@inheritDoc}
* @see com.heliosapm.shorthandexamples.TransformerServiceMBean#transformClass(Java.lang.String)
*/
@Override
public void transformClass(String className) {
Class<?> targetClazz = null;
ClassLoader targetClassLoader = null;
// first see if we can locate the class through normal means
try {
targetClazz = Class.forName(className);
targetClassLoader = targetClazz.getClassLoader();
transform(targetClazz, targetClassLoader);
return;
} catch (Exception ex) { /* Nope */ }
// now try the hard/slow way
for(Class<?> clazz: instrumentation.getAllLoadedClasses()) {
if(clazz.getName().equals(className)) {
targetClazz = clazz;
targetClassLoader = targetClazz.getClassLoader();
transform(targetClazz, targetClassLoader);
return;
}
}
throw new RuntimeException("Failed to locate class [" + className + "]");
}
/**
* Registers a transformer and executes the transform
* @param clazz The class to transform
* @param classLoader The classloader the class was loaded from
*/
protected void transform(Class<?> clazz, ClassLoader classLoader) {
DemoTransformer dt = new DemoTransformer(clazz.getName(), classLoader);
instrumentation.addTransformer(dt, true);
try {
instrumentation.retransformClasses(clazz);
} catch (Exception ex) {
throw new RuntimeException("Failed to transform [" + clazz.getName() + "]", ex);
} finally {
instrumentation.removeTransformer(dt);
}
}
}
_
クラストランスフォーマー
_public class DemoTransformer implements ClassFileTransformer {
/** The internal form class name of the class to transform */
protected String className;
/** The class loader of the class */
protected ClassLoader classLoader;
/**
* Creates a new DemoTransformer
* @param className The binary class name of the class to transform
* @param classLoader The class loader of the class
*/
public DemoTransformer(String className, ClassLoader classLoader) {
this.className = className.replace('.', '/');
this.classLoader = classLoader;
}
/**
* {@inheritDoc}
* @see Java.lang.instrument.ClassFileTransformer#transform(Java.lang.ClassLoader, Java.lang.String, Java.lang.Class, Java.security.ProtectionDomain, byte[])
*/
@Override
public byte[] transform(ClassLoader loader, String className,
Class<?> classBeingRedefined, ProtectionDomain protectionDomain,
byte[] classfileBuffer) throws IllegalClassFormatException {
if(className.equals(this.className) && loader.equals(classLoader)) {
return new ModifyMethodTest(classfileBuffer).modiySleepMethod();
}
return classfileBuffer;
}
}
_
エージェント
_public class AgentMain {
public static void agentmain (String agentArgs, Instrumentation inst) throws Exception {
TransformerService ts = new TransformerService(inst);
ObjectName on = new ObjectName("transformer:service=DemoTransformer");
// Could be a different MBeanServer. If so, pass a JMX Default Domain Name in agentArgs
MBeanServer server = ManagementFactory.getPlatformMBeanServer();
server.registerMBean(ts, on);
// Set this property so the installer knows we're already here
System.setProperty("demo.agent.installed", "true");
}
}
_
エージェントインストーラ
_public class AgentInstaller {
/**
* Installs the loader agent on the target JVM identified in <code>args[0]</code>
* and then transforms all the classes identified in <code>args[1..n]</code>.
* @param args The target JVM pid in [0] followed by the classnames to transform
*/
public static void main(String[] args) {
String agentPath = "D:\\work\\workspace\\myjar\\loaded.jar";
String vid = args[0];
VirtualMachine vm = VirtualMachine.attach(vid);
// Check to see if transformer agent is installed
if(!vm.getSystemProperties().contains("demo.agent.installed")) {
vm.loadAgent(agentPath);
// that property will be set now,
// and the transformer MBean will be installed
}
// Check to see if connector is installed
String connectorAddress = vm.getAgentProperties().getProperty("com.Sun.management.jmxremote.localConnectorAddress", null);
if(connectorAddress==null) {
// It's not, so install the management agent
String javaHome = vm.getSystemProperties().getProperty("Java.home");
File managementAgentJarFile = new File(javaHome + File.separator + "lib" + File.separator + "management-agent.jar");
vm.loadAgent(managementAgentJarFile.getAbsolutePath());
connectorAddress = vm.getAgentProperties().getProperty("com.Sun.management.jmxremote.localConnectorAddress", null);
// Now it's installed
}
// Now connect and transform the classnames provided in the remaining args.
JMXConnector connector = null;
try {
// This is the ObjectName of the MBean registered when loaded.jar was installed.
ObjectName on = new ObjectName("transformer:service=DemoTransformer");
// Here we're connecting to the target JVM through the management agent
connector = JMXConnectorFactory.connect(new JMXServiceURL(connectorAddress));
MBeanServerConnection server = connector.getMBeanServerConnection();
for(int i = 1; i < args.length; i++) {
String className = args[i];
// Call transformClass on the transformer MBean
server.invoke(on, "transformClass", new Object[]{className}, new String[]{String.class.getName()});
}
} catch (Exception ex) {
ex.printStackTrace(System.err);
} finally {
if(connector!=null) try { connector.close(); } catch (Exception e) {}
}
// Done. (Hopefully)
}
}
_
=================更新=================
ニックさん、うん、それは現在の(つまりJava 5-8)クラストランスフォーマーの制限の1つです。 Instrumentation javadoc から引用するには:
「再変換により、メソッド本体、定数プール、および属性が変更される場合があります。再変換では、フィールドまたはメソッドの追加、削除、または名前変更、メソッドのシグネチャの変更、または継承の変更を行ってはなりません。これらの制限は将来のバージョンで解除される可能性があります。クラスファイルのバイト数変換が適用されるまでチェック、検証、インストールされません。結果のバイトがエラーの場合、このメソッドは例外をスローします。」
余談ですが、この同じ制限は、クラスを再定義するためにも逐語的に文書化されています。
そのため、2つのオプションがあります。
新しいメソッドを追加しないでください。これは一般的に非常に制限があり、メソッドのラッピングのような非常に一般的なバイトコードAOPパターンの使用を不適格とします。使用しているバイトコード操作ライブラリによっては、必要なすべての機能を既存のメソッドに注入できる場合があります。一部のライブラリは、これを他のライブラリよりも簡単にします。あるいは、一部のライブラリでは、これが他のライブラリよりも簡単になります。
クラスがロードされる前にクラスを変換します。これは、retransformClassesを呼び出して変換をトリガーしないことを除いて、すでに説明したコードと同じ一般的なパターンを使用します。代わりに、ClassFileTransformerを登録して変換を実行しますbeforeクラスがロードされ、最初にクラスがロードされたときにターゲットクラスが変更されます。この場合、最終製品をまだ検証できるのであれば、クラスを自由に変更できます。アプリケーションを打ち負かす(つまり、アプリケーションがクラスをロードする前にClassFileTransformerを登録する)には、おそらくjavaagentのようなコマンドが必要ですが、アプリケーションのライフサイクルを厳密に制御できるため、これをより伝統的なアプリケーションレイヤーコードで行うことができます。先ほど述べたように、ターゲットクラスがロードされる前にトランスフォーマーを登録する必要があります。
#2のもう1つのバリエーションは、新しいクラスローダーを使用してsimulateまったく新しいクラスにすることです。既存の[ロード済み]クラスに委譲しないが、[アンロード済み]ターゲットクラスのバイトコードにアクセスできる新しい分離クラスローダーを作成する場合、JVMがこれを考慮しているため、上記の#2の要件を本質的に再現しています。真新しいクラスになるために。
================更新================
あなたの最後のコメントで、私はあなたがどこにいるのか少しトラックを失ったような気がします。とにかく、Oracle JDK 1.6は間違いなく再変換をサポートしています。私はASMについてあまり詳しくありませんが、投稿した最後のエラーは、ASM変換が何らかの理由で許可されていないクラススキーマを変更したため、再変換が失敗したことを示しています。
実用的な例がさらに明確になると考えました。上記と同じクラス(およびPersonと呼ばれる1つのテストクラス)は here です。いくつかの変更/追加があります:
-->Invoked method [com.heliosapm.shorthandexamples.Person.sayHello((I)V)]
変換前の出力は次のようになります。
_Temp File:c:\temp\com.heliosapm.shorthandexamples.AgentMain8724970986698386534.jar
Installing AgentMain...
AgentMain Installed
Agent Loaded
Instrumentation Deployed:true
Hello [0]
Hello [0]
Hello [1]
Hello [-1]
Hello [2]
Hello [-2]
_
Personには2つのsayHelloメソッドがあり、1つはintを受け取り、もう1つはStringを受け取ります。 (文字列1は、ループインデックスの負の値を出力するだけです)。
AgentInstallerを起動すると、エージェントがインストールされ、Personがループで呼び出されたら、JConsoleを使用してJVMに接続します。
TransformerService MBeanに移動し、transformClass操作を呼び出します。完全修飾クラス[バイナリ]名、計測するメソッド名、およびonlyに一致する正規表現(I)Vを指定しますsayHello intを引数として取るメソッド。 (または、。*、またはすべてのオーバーロードに一致するものを提供できませんでした)。操作を実行します。
次に、実行中のJVMに戻り、出力を調べます。
_Examining class [com/heliosapm/shorthandexamples/Person]
Instrumenting class [com/heliosapm/shorthandexamples/Person]
[ModifyMethodTest] Adding [System.out.println("\n\t-->Invoked method [com.heliosapm.shorthandexamples.Person.sayHello((I)V)]");]
[ModifyMethodTest] Intrumented [1] methods
-->Invoked method [com.heliosapm.shorthandexamples.Person.sayHello((I)V)]
Hello [108]
Hello [-108]
-->Invoked method [com.heliosapm.shorthandexamples.Person.sayHello((I)V)]
Hello [109]
Hello [-109]
_
できました。インストルメントされたメソッド。
再変換が許可される理由は、Javassistバイトコードの変更が、既存のメソッドにコードを挿入する以外に変更を加えなかったからです。
理にかなっていますか?