检查tools.jar是否可用,并在运行时动态加载它

时间:2016-03-29 20:26:07

标签: java maven jvm jmx tools.jar

我正在开发一个监控应用程序,它使用Sigar进行监控以监控不同类型的应用程序。 Sigar的一个问题是,在监视Java应用程序(JVM)的堆使用情况时,我只获得最大堆大小,而不是JVM实际使用的堆大小。 所以我扩展了我的监控应用程序,使用JMX连接到JVM并检索CPU以及堆使用情况。这到目前为止工作得很好,但是 我希望尽可能自动化所有内容,并且我不希望启动所有应用程序,受到监控,JMX已激活,但在需要时使用以下代码动态激活它:

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);
    }
}

到目前为止这个工作正常,但问题是我现在已经从JDK的tools.jar依赖了。 我现在的问题是,我可以在运行时检查tools.jar路径中JAVA_HOME是否可用,然后加载它?因为如果它不可用我只想用Sigar进行正常监视,但如果它可用,我想使用JMX来监视Java应用程序。 我的项目是一个maven项目,我使用maven-shade-plugin创建一个包含所有依赖项的可执行jar。

目前,我正在使用我在互联网上发现的脏黑客,它使用反射将tools.jar动态添加到系统类路径(如果存在)。但是我想知道是否有可能以不同的方式做到这一点? 在此先感谢您的支持。

3 个答案:

答案 0 :(得分:3)

我在项目中做了类似的事情look here

我们的想法是通过路径中tools.jar的不同ClassLoader加载您的实用程序类。

    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);

答案 1 :(得分:0)

在文件系统上查找tools.jar比@ apangin的解决方案更棘手。 不同的JDK将tools.jar粘贴在不同的地方,如this method所示,它声称支持Mac上的IBM JDK和HotSpot。

但即使我引用的代码看起来已经过时了。它建议所有mac JDK使用classes.jar,但我的Mac 1.7和1.8 JDK使用tools.jar。

这个other answer of mine显示了几乎1.6,1.7和1.8 JDK的tools.jar和classes.jar文件的位置。

答案 2 :(得分:0)

我最终使用的代码来自: org.gridkit.lab :: jvm Attach Api

从该源代码中,您只需要一个文件: 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");
            }
        }   
    }

要使用此类,请将其放在项目中的某个位置,并确保相应地更改其包。在下面的示例中,我将文件放在与MyApp.java文件相同的文件夹中,但我没有修改AttachAPI.java文件的软件包语句以反映这一点,因为我想让它保持原始状态。

最后,在您的主类中,确保您有一个块,如下所示:


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

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



    }

    ...

现在,您不再需要在命令行中指定tools.jar的路径,只需java -jar MyApp.jar

即可启动应用程序