9

I have a program which requires dynamically (i.e. at run time) opening an available socket and start a JMX agent on it. This JMX parameters are being set inside the Java code and not through command line. This works fine. Thereafter it is needed to monitor( i.e issue JMX commands etc) through Java Visual VM remotely

The RMI server agent in the program is on the lines of out of box management described at: http://download.oracle.com/javase/6/docs/technotes/guides/management/agent.html

The question I have can be summarized as: How can such command line properties be set to the system level through the Java code, so that remote profiling can be used??

-Dcom.sun.management.jmxremote.port=1234

If the "jmxremote.port" and other parameters are set through command line, remote monitoring works fine. I am trying to find a way to do this through Java and not through the command line.

The program can not specify the port through the command line as the new available port has to be figured out at run time.

The process needs remote monitoring and it works fine locally. If the following parameters are not specified at command line, Java Visual VM does not connect to the process.

-Dcom.sun.management.jmxremote.port=1234
-Dcom.sun.management.jmxremote.authenticate=false
-Dcom.sun.management.jmxremote.ssl=false
-Djava.rmi.server.hostname=10.0.0.128

I have tried.

System.setProperty("com.sun.management.jmxremote.port",Integer.toString(port));

This is one of the first things done in the program before starting the JMXConnectorServer. Unfortunately it is not recognized. Only the run time properties (i.e. specified through command line are recognized for JMX connection by Java Visual VM).

Also came across the way properties can be extracted from java collection classes but could not reach how to trace the property "com.sun.management.jmxremote.port="

public static void setEnv(Map<String, String> newenv) throws Exception {
  Class[] classes = Collections.class.getDeclaredClasses();
  Map<String, String> env = System.getenv();

  for(Class cl : classes) {

    if("java.util.Collections$UnmodifiableMap".equals(cl.getName())) {

      Field field = cl.getDeclaredField("m");
      field.setAccessible(true);

      Object obj = field.get(env);
      Map<String, String> map = (Map<String, String>) obj;

      //map.clear();
      map.putAll(newenv);
    }
  }
}

Any help would be appreciated!

J.P. Armstrong
  • 836
  • 1
  • 9
  • 26
Devesh
  • 271
  • 1
  • 5
  • 10
  • You should edit your original question and include the code samples that you have been posting in comments. – Perception Sep 01 '11 at 23:27

6 Answers6

11

kbec's answer showed the way but did not work for me - however by looking at this post I was able to modify it and get a working solution.

public static String loadJMXAgent(int port) throws IOException,
        AttachNotSupportedException, AgentLoadException,
        AgentInitializationException {
    String name = ManagementFactory.getRuntimeMXBean().getName();
    VirtualMachine vm = VirtualMachine.attach(name.substring(0,
            name.indexOf('@')));

    String lca = vm.getAgentProperties().getProperty(
            "com.sun.management.jmxremote.localConnectorAddress");
    if (lca == null) {
        Path p = Paths.get(System.getProperty("java.home")).normalize();
        if (!"jre".equals(p.getName(p.getNameCount() - 1).toString()
                .toLowerCase())) {
            p = p.resolve("jre");
        }
        File f = p.resolve("lib").resolve("management-agent.jar").toFile();
        if (!f.exists()) {
            throw new IOException("Management agent not found");
        }
        String options = String.format("com.sun.management.jmxremote.port=%d, " +
                "com.sun.management.jmxremote.authenticate=false, " +
                "com.sun.management.jmxremote.ssl=false", port);
        vm.loadAgent(f.getCanonicalPath(), options);
        lca = vm.getAgentProperties().getProperty(
                "com.sun.management.jmxremote.localConnectorAddress");
    }
    vm.detach();
    return lca;
}

This works in Eclipse however getting it to work at the command line is a different matter - there is some discussion about this here Why does using the Java Attach API fail on linux? (even though maven build completes) but I found adding $JAVA_HOME/lib/tools.jar to my classpath solved the problem.

Community
  • 1
  • 1
Mark Butler
  • 4,361
  • 2
  • 39
  • 39
  • "com.sun.management.jmxremote.localConnectorAddress" property does not created. but this code worked for me – ruseel May 27 '20 at 10:50
4

You're going about this slightly the wrong way. By the time your code is called you've missed the chance for these properties to have any effect.

You need to create an RmiRegistry and then create a JMXConnectorServer linked to the platform MBeanServer like this:

private void createJmxConnectorServer() throws IOException {
    LocateRegistry.createRegistry(1234);
    MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
    JMXServiceURL url = new JMXServiceURL("service:jmx:rmi://localhost/jndi/rmi://localhost:1234/jmxrmi");
    JMXConnectorServer svr = JMXConnectorServerFactory.newJMXConnectorServer(url, null, mbs);
    svr.start();
}
AutomatedMike
  • 1,454
  • 13
  • 27
2

If you don't specify any jmxremote env as run param then JMX management agent was not loaded. You can try this for dynamic loading :

public static String loadJMXAgent(int port) throws IOException, AttachNotSupportedException, AgentLoadException, AgentInitializationException {
    System.setProperty("com.sun.management.jmxremote.port", Integer.toString(port));
    String name = ManagementFactory.getRuntimeMXBean().getName();
    VirtualMachine vm = VirtualMachine.attach(name.substring(0, name.indexOf('@')));

    String lca = vm.getAgentProperties().getProperty("com.sun.management.jmxremote.localConnectorAddress");
    if (lca == null) {
        Path p = Paths.get(System.getProperty("java.home")).normalize();
        if (!"jre".equals(p.getName(p.getNameCount()-1).toString().toLowerCase())) p = p.resolve("jre");
        File f = p.resolve("lib").resolve("management-agent.jar").toFile();
        if (!f.exists()) throw new IOException("Management agent not found");

        vm.loadAgent(f.getCanonicalPath(), "com.sun.management.jmxremote");
        lca = vm.getAgentProperties().getProperty("com.sun.management.jmxremote.localConnectorAddress");
    }
    vm.detach();
    return lca;
}

You must include jdk/lib/tools.jar

kbec
  • 3,415
  • 3
  • 27
  • 42
1

I tried some ways to specify the jmxremote port from java code to have a connection on a specific port and have figured out the following:

In case jmxremote arg is specified: The platform mbean server is started by the JVM, before my code modifies the necessary jmxremote System.properties. Every mbean server has an own registry of beans. The plaform and JVM mbeans could not register their own beans to it on an other way.

You can create an alternate mbean server after you set up the jmx port properties. That will listen on the correct jmx port, you specified.

This way you choose the platform server :

MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();

This way your own :

System.setProperty("com.sun.management.jmxremote.port","9991");
//...
MBeanServer mbsCustom=MBeanServerFactory.createMBeanServer();

also consider that linux has it's loopback interface so you should specify the correct hostname explicitly to listen on.

This is not recommended to use an other MBeanServer than the platform's, according to manuals, but I can imagine some situations where the command line options are not the way you can launch a server.

kisp
  • 6,402
  • 3
  • 21
  • 19
0

This is what works for me. Reference Oracle JMX Tutorial. I am assuming you already know how to right SimpleMXBean used in below example.

package sample;

import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.rmi.registry.LocateRegistry;
import java.util.HashMap;
import java.util.Map;

import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.management.remote.JMXConnectorServer;
import javax.management.remote.JMXConnectorServerFactory;
import javax.management.remote.JMXServiceURL;

public class MBServerTest {
    public static void loadJMXAgent(int port, MBeanServer mbs) throws IOException  {
        LocateRegistry.createRegistry(port);
        System.out.println("Initialize the environment map");
        Map<String,Object> env = new HashMap<String,Object>();
        env.put("com.sun.management.jmxremote.authenticate", "false");
        env.put("com.sun.management.jmxremote.ssl", "false");
        System.out.println("Create an RMI connector server");
        JMXServiceURL url =
            new JMXServiceURL("service:jmx:rmi:///jndi/rmi://:"+port+"/jmxrmi");
        JMXConnectorServer cs =
            JMXConnectorServerFactory.newJMXConnectorServer(url, env, mbs);

        // Start the RMI connector server.
        //
        System.out.println("Start the RMI connector server");
        cs.start();

    }

    public static void main(String[] args) throws Exception {
        MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
        loadJMXAgent(1199,mbs);

        SimpleStandard cache = new SimpleStandard();

        ObjectName name = new ObjectName(
                "org.javalobby.tnt.jmx:type=ApplicationCacheMBean");
        mbs.registerMBean(cache, name);
        imitateActivity(cache);
    }

    private static void imitateActivity(SimpleStandard cache) {
        while (true) {
            try {
                cache.cacheObject("hello");
                Thread.sleep(1000);
            } catch (InterruptedException e) {
            }
        }
    }
}
rahul maindargi
  • 5,359
  • 2
  • 16
  • 23
0

System.setProperty() is identical to the -D command line option. However obviously you must call it early enough that you are setting the property before it is being read.

user207421
  • 305,947
  • 44
  • 307
  • 483
  • The property which are being read by the profiler for remote profiling are the default properties set at the time JVM starts. In this case when the java code starts. I am starting the java visual vm profiler much later and setting the property before JMXConnectorServer is started. Trying to figure out how to over ride the property so that it is reflected as a jmx system level and can be detected by java visual vm ?? Would appreciate any suggestions on how to call it "early enough". – Devesh Sep 02 '11 at 00:49
  • Running app without any `-Dcom.sun.management.jmxremote` and setting this env in runtime later is bad idea because JMX agent was not loaded, so it cannot be used. – kbec Jun 20 '12 at 18:19