Question

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;
}
Was it helpful?

Solution

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:

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top