1

I am using jdk1.6. I have loaded a jar at runtime successfully. Codes are as follow:

vm = VirtualMachine.attach(vid);
vm.loadAgent(agentPath);

Now I wannt to unload this agent at runtime. There is no API DOC to do that. Can anyone give me some advices? Thanks.

EDIT

More Codes

GlobalVariables.vm = VirtualMachine.attach(vid);

// Check to see if transformer agent is installed
if(!GlobalVariables.vm.getSystemProperties().contains("demo.agent.installed")) {
    System.out.println("Load agent");

    GlobalVariables.vm.loadAgent(agentPath); 

}

GlobalVariables.connectorAddress = GlobalVariables.vm.getAgentProperties().getProperty("com.sun.management.jmxremote.localConnectorAddress", null);
if(null == GlobalVariables.connectorAddress) {
    // It's not, so install the management agent
    String javaHome = GlobalVariables.vm.getSystemProperties().getProperty("java.home");
    File managementAgentJarFile = new File(javaHome + File.separator + "lib" + File.separator + "management-agent.jar");
    GlobalVariables.vm.loadAgent(managementAgentJarFile.getAbsolutePath());
    System.out.println("Load Management agent");
    GlobalVariables.connectorAddress = GlobalVariables.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");
    GlobalVariables.on = new ObjectName("transformer:service=DemoTransformer");
    // Here we're connecting to the target JVM through the management agent
    GlobalVariables.connector = JMXConnectorFactory.connect(new JMXServiceURL(GlobalVariables.connectorAddress));
    GlobalVariables.server = GlobalVariables.connector.getMBeanServerConnection();
    System.out.println("MBean Server connection...");

    // Call transformClass on the transformer MBean
    GlobalVariables.server.invoke(GlobalVariables.on, "transformClass", new Object[]{className, args}, new String[]{String.class.getName(), String.class.getName()});

} catch (Exception ex) {
    ex.printStackTrace(System.err);
} finally {
    if(GlobalVariables.connector!=null) try { GlobalVariables.connector.close(); } catch (Exception e) {}
    GlobalVariables.vm.detach();
    System.out.println("Has disconnected");
}

What did I do then

I run the above code again to load those two agents again. But I got errors.

Errors

error in current vm

com.sun.tools.attach.AgentInitializationException: Agent JAR loaded but agent failed to initialize
    at sun.tools.attach.HotSpotVirtualMachine.loadAgent(HotSpotVirtualMachine.java:103)
    at com.sun.tools.attach.VirtualMachine.loadAgent(VirtualMachine.java:508)
    at faultinjectionaction.AttachClass.attachAgent(AttachClass.java:111)
    at org.apache.jsp.fjstep3_jsp._jspService(fjstep3_jsp.java:350)
    at org.apache.jasper.runtime.HttpJspBase.service(HttpJspBase.java:70)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:728)
    at org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:432)
    at org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:390)
    at org.apache.jasper.servlet.JspServlet.service(JspServlet.java:334)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:728)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:305)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:222)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:123)
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:502)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:171)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:99)
    at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:953)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:118)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:408)
    at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1023)
    at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:589)
    at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:310)
    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)

error in target vm

Exception in thread "Attach Listener" java.lang.reflect.InvocationTargetException
        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.instrument.InstrumentationImpl.loadClassAndStartAgent(InstrumentationImpl.java:323)
        at sun.instrument.InstrumentationImpl.loadClassAndCallAgentmain(InstrumentationImpl.java:348)
Caused by: javax.management.InstanceAlreadyExistsException: transformer:service= DemoTransformer
        at com.sun.jmx.mbeanserver.Repository.addMBean(Repository.java:453)
        at com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.internal_addObject(DefaultMBeanServerInterceptor.java:1484)
        at com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.registerDynamicMBean(DefaultMBeanServerInterceptor.java:963)
        at com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.registerObject(DefaultMBeanServerInterceptor.java:917)
        at com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.registerMBean(DefaultMBeanServerInterceptor.java:312)
        at com.sun.jmx.mbeanserver.JmxMBeanServer.registerMBean(JmxMBeanServer.java:482)
        at faultinjectionagent.AgentMain.agentmain(AgentMain.java:28)
        ... 6 more

Aim

To do the same job with no errors. I think I need to unload the agents when I finished my job every time. But I failed to unload those agents.

EDIT GlobleVariables

import javax.management.MBeanServerConnection;
import javax.management.ObjectName;
import javax.management.remote.JMXConnector;
import com.sun.tools.attach.VirtualMachine;

public class GlobalVariables{
    public static MBeanServerConnection server;
    public static ObjectName on;
    public static JMXConnector connector = null;
    public static String connectorAddress = null;
    public static VirtualMachine vm;
}
Drew MacInnis
  • 8,267
  • 1
  • 22
  • 18
Nick Dong
  • 3,638
  • 8
  • 47
  • 84

1 Answers1

2

What do you mean by unload the agent? You cannot unload the classes as it implicitly says in the documentation:

the specified JAR file is added to the system class path (of the target virtual machine)

The only way arround this might be some custom class loader magic, but I would not recommend that.

Update: After looking at your extended question, I think your problem is actually something else. At some point you are invoking

JMXConnectorFactory.connect(new JMXServiceURL(GlobalVariables.connectorAddress))

in order to create a JMXConnector. I guess that within your GlobalVariables.server.invoke call, you are registering a MBean by new ObjectName("transformer:service=DemoTransformer"). This name must be unique and when you are running the code a second time, this name is already taken as suggested by javax.management.InstanceAlreadyExistsException: transformer:service= DemoTransformer. What you needed to do is:

  • Either choose another name when registering the MBean a second time.
  • Call MBeanServerConnection.close(new ObjectName("transformer:service=DemoTransformer")) before detaching from the remote JVM in order to make the name available again.

You might have assumed that by detaching, all state on the remote machine was reset. This is however not true. You added an MBean with a name and than you tried to do this again. This error can be understood as if you added a two values with the same key to a map. Other than with the map, you will however not override values but cause the exception you observe above.

By the way: You should call JMXConnector.close explicitly when the connection to the remote server is not longer required.

PS: You might find this article interesting.

Update 2: After discussion in the chat and after getting the MBean naming conflict out of the way, I think this is what caused the problem:

When a Java Agent is loaded a second time, the classes that come with the agent (represented in managementAgentJarFile) are already loaded in the target JVM. This means, that no class initializers will be run again and state changes that are represented by static variables will still be represented. Additionally, it is not possible to load classes with the same name but changed implementation. This will cause LinkageErrors and the agent loading will fail. The solution is to avoid static state such that an agent cannot inflict with itself and to create separate name spaces for different agents. Otherwise, agent classes can be unloaded by using custom class loaders. More information on this matter can be found on many places and here:

Community
  • 1
  • 1
Rafael Winterhalter
  • 42,759
  • 13
  • 108
  • 192
  • Does `remove an Instrumentation` means `unload agent`? – Nick Dong Nov 28 '13 at 03:44
  • I have edited my question @raphw . Could you do me favor to give me some advices? Thanks – Nick Dong Nov 28 '13 at 07:55
  • What is the `GlobalVariables` super type of `VirtualMachine`? This should not even compile. – Rafael Winterhalter Nov 28 '13 at 08:44
  • A class including some variables with `static`. I have added the code to my question description. @raphw – Nick Dong Nov 28 '13 at 08:52
  • I think I see what your problem is. You registered two MBeans with the same name. See my edited answer above. – Rafael Winterhalter Nov 28 '13 at 09:00
  • Yes, I just call JMXConnector.close()` explicitly`. Errors about `javax.management.InstanceAlreadyExistsException` has been removed for now. But the agent still be loaded at the second time, so `AgentInitializationException` still exists. If the jars which was loaded to the target jvm can be unloaded or be removed from system classpath or whatever, just making the target jvm be the original one, everything would be better. Is that possible? – Nick Dong Nov 28 '13 at 09:35
  • Well, hypothetically, it is possible to load classes with a custom class loader. In this case, all classes loaded with this custom class loader can be garbage collected once no instance of any class that was loaded by this class loader is reachable. However, I would not recommend this. Your exception is also quite explicit about what is wrong: `com.sun.tools.attach.AgentInitializationException: Agent JAR loaded but agent failed to initialize`. Maybe you are doing some `static` variable magic? This will turn out different when your code runs a second time. The best is to avoid such variables. – Rafael Winterhalter Nov 28 '13 at 10:07
  • The exception says: Initialization goes wrong, but the agent could be loaded before that. I once described how to *unload* classes in this answer: http://stackoverflow.com/questions/19692976/can-i-pre-empt-the-class-path/19694603#19694603 – Rafael Winterhalter Nov 28 '13 at 10:10
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/42107/discussion-between-raphw-and-nick-dong) – Rafael Winterhalter Nov 28 '13 at 10:19
  • Sorry for late to reply. Yes I am on it. @raphw – Nick Dong Nov 28 '13 at 11:02
  • Serveral messages from discussion. 1st `cannot load the SAME agent several times, you can however execute it's hook several times`. 2nd `once a class by a specific name is loaded, the JVM makes sure to never load a class by this name again`. 3rd `if you are calling some mypackage.A, the JVM will check if there is a class of that name. if there is, this class will be returned. if not, the JVM will attempt to load this class from some jar`. – Nick Dong Nov 29 '13 at 10:12
  • Solved. Not unload the jar. But not load many times either. *GlobalVariables.vm.getSystemProperties().contains("demo.agent.installed")* always false. So agent is loaded every time. It should be *GlobalVariables.vm.getSystemProperties().getProperty("demo.agent.installed") == null*. – Nick Dong Nov 29 '13 at 10:15
  • It is no need to unload. Just make sure agents will not be loaded again. – Nick Dong Dec 02 '13 at 08:48