0

I'm working on a monitoring application, which uses Sigar for monitoring to monitor different kind of applications. One problem with Sigar is that when monitoring the heap usage of a Java application (JVM) I only get the maximum heap size but not the actually used heap size of the JVM. So I extended my monitoring application to use JMX to connect to a JVM and retrieve the CPU as well as the heap usage. This works fine so far, but I want to automise everything as much as possible and I don't want to start all my applications, being monitored, with JMX activated, but activate it dynamically when needed with the following piece of code:

private void connectToJVM(final String pid) throws IOException, AgentLoadException, AgentInitializationException {
    List<VirtualMachineDescriptor> vms = VirtualMachine.list();
    for (VirtualMachineDescriptor desc : vms) {
        if (!desc.id().equals(pid)) {
            continue;
        }
        VirtualMachine vm;
        try {
            vm = VirtualMachine.attach(desc);
        } catch (AttachNotSupportedException e) {
            continue;
        }
        Properties props = vm.getAgentProperties();
        String connectorAddress = props.getProperty(CONNECTOR_ADDRESS);
        if (connectorAddress == null) {
            String agent = vm.getSystemProperties().getProperty("java.home") + File.separator + "lib"
                    + File.separator + "management-agent.jar";
            vm.loadAgent(agent);

            // agent is started, get the connector address
            connectorAddress = vm.getAgentProperties().getProperty(CONNECTOR_ADDRESS);
        }
        vm.detach();
        JMXServiceURL url = new JMXServiceURL(connectorAddress);
        this.jmxConnector = JMXConnectorFactory.connect(url);
    }
}

This works fine so far but the problem is that I have now a dependency to the tools.jar from the JDK. My question is now can I somehow check during runtime if the tools.jar is available in the JAVA_HOME path and load it when it is? Because if it isn't available I just want to do the normal monitoring with Sigar, but if it is available I want to use JMX for monitoring Java applications. My project is a maven project and I'm using the maven-shade-plugin to create a executable jar with all dependencies in it.

Currently I'm using a dirty hack I found in the internet which uses reflection to add the tools.jar dynamically to the system classpath if it exists. But I'm wondering if it is possible to do it differently as well? Thanks in advance for your support.

tzwickl
  • 1,341
  • 2
  • 15
  • 31
  • This is a bad idea, not least because this JAR is going away in Java 9. You should use the Compiler API to access the `javac`. – Peter Lawrey Mar 30 '16 at 08:04

3 Answers3

3

I do a similar thing in my project, look here.

The idea is to load your utility class by differrent ClassLoader which has tools.jar in path.

    File javaHome = new File(System.getProperty("java.home"));
    String toolsPath = javaHome.getName().equalsIgnoreCase("jre") ? "../lib/tools.jar" : "lib/tools.jar";

    URL[] urls = new URL[] {
            getClass().getProtectionDomain().getCodeSource().getLocation(),
            new File(javaHome, toolsPath).getCanonicalFile().toURI().toURL(),
    };

    URLClassLoader loader = new URLClassLoader(urls, null);
    Class<?> utilityClass = loader.loadClass("some.package.MyUtilityClass");

    utilityClass.getMethod("connect").invoke(null);
apangin
  • 92,924
  • 10
  • 193
  • 247
  • Thanks for the prompt reply. Your approach sounds interesting I will try to adapt it to my project :) With your method I can't invoke a method as I would normally but always need to use the notation `class.getMethod("methodName").invoke(parameters)`? – tzwickl Mar 29 '16 at 20:59
  • @tom1991te Not necessarily. You may create an instance of that class and cast it to some known interface, e.g. `Runnable utility = (Runnable) cls.newInstance();` Then call it normally: `utility.run();` – apangin Mar 29 '16 at 21:17
0

Finding tools.jar on the filesystem is a little more tricky than @apangin's solution. Different JDK's stick the tools.jar in different places as shown by this method, which claims to support the IBM JDK and HotSpot on Mac.

But even the code I've referenced looks out of date. It suggests all mac JDK's use classes.jar, but my Mac 1.7 and 1.8 JDK's instead use tools.jar.

This other answer of mine shows locations of tools.jar and classes.jar files for mac some 1.6, 1.7 and 1.8 JDKs.

Community
  • 1
  • 1
0

The code I ended up using is from: org.gridkit.lab::jvm Attach Api

From that source code, you simply need one file: AttachAPI.java


    /**
     * Copyright 2013 Alexey Ragozin
     *
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     *
     *     http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */
    package org.gridkit.lab.jvm.attach;

    import java.lang.reflect.Method;
    import java.net.URL;
    import java.net.URLClassLoader;

    /**
     * @author Alexey Ragozin (alexey.ragozin@gmail.com)
     */
    class AttachAPI {

        private static final LogStream LOG_ERROR = LogStream.error();
        private static boolean started;

        static {
            try {
                String javaHome = System.getProperty("java.home");
                String toolsJarURL = "file:" + javaHome + "/../lib/tools.jar";

                // Make addURL public
                Method method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
                method.setAccessible(true);

                URLClassLoader sysloader = (URLClassLoader)ClassLoader.getSystemClassLoader();
                if (sysloader.getResourceAsStream("/com/sun/tools/attach/VirtualMachine.class") == null) {
                    method.invoke(sysloader, (Object) new URL(toolsJarURL));
                    Thread.currentThread().getContextClassLoader().loadClass("com.sun.tools.attach.VirtualMachine");
                    Thread.currentThread().getContextClassLoader().loadClass("com.sun.tools.attach.AttachNotSupportedException");
                }

            } catch (Exception e) {
                LOG_ERROR.log("Java home points to " + System.getProperty("java.home") + " make sure it is not a JRE path");
                LOG_ERROR.log("Failed to add tools.jar to classpath", e);
            }
            started = true;
        };

        public static void ensureToolsJar() {
            if (!started) {
                LOG_ERROR.log("Attach API not initialized");
            }
        }   
    }

To use this class, put it somewhere in your project and ensure you change its package accordingly. In the example below, I have placed the file in the same folder as my MyApp.java file but I've not amended the AttachAPI.java file's package statement to reflect that since I wanted to leave it pristine.

Lastly, in your main class, ensure you have a block such as the follows:


    public class MyApp
    {
        static {
            AttachAPI.ensureToolsJar();
        }

        public static void ensureToolsJar() {
            // do nothing, just ensure call to static initializer
        }



    }

    ...

Now you will no longer need to specify a path to the tools.jar on the command line and can launch you app with simply a java -jar MyApp.jar

Jinesh Choksi
  • 988
  • 6
  • 6