2

Is there a (pref portable) way to check if The JVM has been stated with a particular -javaagent?

In particular I'm interested to know if the aspectj load time weaver has loaded or not. (I'm trying to provide a helpful error msg in the case of incorrect startup).

bacar
  • 9,761
  • 11
  • 55
  • 75

2 Answers2

10

The following code shows

  • a way to determine any -javaagent:... JVM arguments,
  • a way to check if the AspectJ weaving agent entry point class (the one mentioned in the manifest entry Premain-Class: of aspectjweaver.jar) is loaded.

The former just proves that the argument was given on the command line, not that the agent was actually found and started.

The latter just proves that the weaver is available on the classpath, not that it was actually started as an agent. The combination of both should give you pretty much confidence that the agent is actually active.

package de.scrum_master.app;

import java.lang.management.ManagementFactory;
import java.lang.management.RuntimeMXBean;
import java.util.List;

public class Application {
    public static void main(String[] args) {
        RuntimeMXBean runtimeMxBean = ManagementFactory.getRuntimeMXBean();
        List<String> arguments = runtimeMxBean.getInputArguments();
        for (String argument : arguments) {
            if (argument.startsWith("-javaagent:"))
                System.out.println(argument);
        }
        try {
            Class.forName("org.aspectj.weaver.loadtime.Agent");
        } catch (ClassNotFoundException e) {
            System.err.println("WARNING: AspectJ weaving agent not loaded");
        }
    }
}

You also might find the question Starting a Java agent after program start and some of its answers helpful.


Update:

Okay, here is a combination of my own solution and yours, but one which actually works even if the weaver is unavailable, which is important because this is what you want to check in the first place:

public static boolean isAspectJAgentLoaded() {
    try {
        Class<?> agentClass = Class.forName("org.aspectj.weaver.loadtime.Agent");
        Method method = agentClass.getMethod("getInstrumentation");
        method.invoke(null);
    } catch (Exception e) {
        //System.out.println(e);
        return false;
    }
    return true;
}

Update 2:

After some discussion with the OP bacar I have decided to offer a solution which does not use reflection but catches NoClassDefError instead:

public static boolean isAspectJAgentLoaded() {
    try {
        org.aspectj.weaver.loadtime.Agent.getInstrumentation();
    } catch (NoClassDefFoundError | UnsupportedOperationException e) {
        System.out.println(e);
        return false;
    }
    return true;
}

Now both main error types

  • weaving agent is available on the classpath, but instrumentation has not been initiated because aspectjweaver.jar was not started as a Java agent,
  • agent aspectjweaver.jar is not on the classpath at all and class org.aspectj.weaver.loadtime.Agent is thus unavailable

are handled gracefully by returning false after warning messages (in this simple examples just the exceptions which say clearly what is wrong) have been printed on the console.

Possible console outputs for the two cases are:

  • java.lang.UnsupportedOperationException: Java 5 was not started with preMain -javaagent for AspectJ
  • java.lang.NoClassDefFoundError: org/aspectj/weaver/loadtime/Agent
Community
  • 1
  • 1
kriegaex
  • 63,017
  • 15
  • 111
  • 202
  • Thanks. The question you reference was my initial starting point (I prefer to load automatically instead of erroring), but dynamic loading requires tools.jar, and I haven't found a good way of referencing tools.jar in a sufficiently portable way such that it works well in various IDEs and maven cmd line, on dev workstations and on our build servers (even though all servers are just various flavours of windows+oracle jdk) – bacar Jan 22 '15 at 08:44
  • So will you accept and upvote my answer, please, if it seems adequate? Or is there anything you still want to know? – kriegaex Jan 24 '15 at 16:26
  • I've no principled objection to the reflection route btw. I prefer my static way, but still worth capturing the other as an answer - different situations call for different solutions. – bacar Jan 25 '15 at 14:07
  • Just in case you are interested, I played around some more and patched AspectJ in a way which helped me attach the weaving agent dynamically if the `-javaagent:...` part was forgotten/omitted on the command line. I have created an [AspectJ bug ticket](https://bugs.eclipse.org/bugs/show_bug.cgi?id=458871) in order to get the feature in upstream. – kriegaex Jan 31 '15 at 12:27
  • Cool. I did find a [third-party library](https://aspectjutil.googlecode.com/svn/site/apidocs/com/newmainsoftech/aspectjutil/adviseutil/AspectJWeaverAgentLoader.html) that does this without appearing to need changes to aspectj. I had concerns re portability though, given the tools.jar requirement. – bacar Jan 31 '15 at 12:48
  • 1
    Initially I also thought about creating a thin add-on layer around *aspectjweaver.jar*, much like the one you pointed me to. But then I thought it would make more sense to get the feature in upstream eventually. – kriegaex Jan 31 '15 at 15:54
  • I'd assumed they don't include it for a reason (maybe they consider it too risky to attach at runtime given many people want to apply aspects to the JDK etc). Good luck! – bacar Jan 31 '15 at 20:10
  • It would not work for JDK classes, only for classes loaded *after* the weaver was attached, i.e. even slightly later than with `javaagent:...` because the bootstrapping class itself which attaches the weaver is also exempt from weaving as it was already loaded before the weaver is attached. Retransforming/redefining already loaded classes is not possible because AspectJ adds methods, which is not allowed for reloading/retransformation. If you need to weave into JDK classes, you have to use binary compile-time weaving and replace the classes in the JDK or prepend them to the *bootclasspath*. – kriegaex Jan 31 '15 at 22:12
0

I've found the following works (tested against 1.8.4), although it relies on undocumented aspectjweaver features so may not work across versions.

public static boolean isAspectJAgentLoaded() {
    try {
        org.aspectj.weaver.loadtime.Agent.getInstrumentation();
        return true;
    } catch (UnsupportedOperationException e) { 
        return false;
    }
}

Explanation: when aspectj is loaded as an agent, the org.aspectj.weaver.loadtime.Agent.premain(...) static method is invoked by the JVM. This has a side effect we can test for. Calling getInstrumentation either throws UnsupportedOperationException (if it was not initialised as an agent) or returns successfully if it was.

bacar
  • 9,761
  • 11
  • 55
  • 75
  • I don't think this is a particularly practical approach because if the weaving agent is not loaded probably *aspectjweaver.jar* will not be on the classpath either and your code will not run, but issue an exception: `Exception in thread "main" java.lang.NoClassDefFoundError: org/aspectj/weaver/loadtime/Agent`. I would rather recommend using `Class.forName(..)` like I did in my answer and then (only if successful) call `getInstrumentation()` via reflection. See update on my answer. – kriegaex Jan 24 '15 at 18:47
  • Simple solution: put it on the classpath. That's the (often inconsequential) cost of doing this check. The error in the case that it's not on the CP is clear and transparent and easily fixed. Your suggestion will treat typos (or changes across versions) in the qualified class or method name as "agent not loaded". Mine will fail fast, which I prefer. Secondly, it's not clear to me whether the jar loaded by `-javaagent` is available to the default classloader anyway - have you tested your proposal? (I think all bets are off for both solutions if you have custom ClassLoaders) – bacar Jan 25 '15 at 09:07
  • Check out my ***updated*** solution and, l if you prefer, have multiple `catch` clauses for multiple error types. I think it is not good to let the whole application exit with an unhandled error in a utility method, even though this error occurs in the very case you want to handle gracefully. And if you put the weaver on the classpath, it does not mean that LTW is active because then it still is not loaded as an agent. To specify it twice on the command line is counter-intuitive. My solution is superior, IMHO. – kriegaex Jan 25 '15 at 10:51
  • Okay, my 2nd update does the same without reflection, but handles the `NoClassDefFoundError`. – kriegaex Jan 25 '15 at 13:04
  • That was my initial thought, though I got the impression that catching Errors is Evil? – bacar Jan 25 '15 at 13:21
  • Only if you cannot handle them well and merely cover up a problem instead. But in this case you have a method which is designed to return a boolean value depending on some condition. The user would not expect to handle an undeclared error by calling a simple boolean method, especially in a case which should clearly return a value of `false`. The user can still decide to throw an exception or exit the application if the aspect weaver is unavailable. – kriegaex Jan 25 '15 at 13:52
  • Makes sense. It's still not obvious to me though that Class.forName will have access to it just because it's added as a javaagent. – bacar Jan 25 '15 at 14:01
  • `Class.forName(..)` will find it if it is visible to the current classloader, just as the other variant would. If it is just available on the classpath, but inactive, the call to `getInstrumentation()` will still fail just like in your example. While the first condition is necessary (agent class availability), only the second one is sufficient for a return value of `true`. Which classloader instances the agent is attached to, which aspects it registers and which joinpoints it weaves which advice into, you can see on the console when using the `-verbose` switch in `aop.xml`. – kriegaex Jan 25 '15 at 14:20