質問

Intent:

I'm using the java.lang.instrument package to create some instrumentation for Java programs. The idea is that I use bytecode manipulation via this system in order to add method calls at the beginning and end of each method. Generally speaking, a modified Java method would look like:

public void whateverMethod(){
    MyFancyProfiler.methodEntered("whateverMethod");
    //the rest of the method as usual...
    MyFancyProfiler.methodExited("whateverMethod");
}

MyFancyProfiler is an entry point to a relatively complex system that gets initialized during the premain method (which is part of java.lang.instrument).

edit - MyFancyProfiler contains a static API that will get a reference to the rest of the system through a mechanism like the one described in the solution to this question. The reference is obtained as an Object, and the appropriate calls are made via reflection, so that even if the current ClassLoader doesn't know about the underlying classes, it will still work.

Difficulties

For a simple Java program, the approach works fine. For "real" apps (like windowed applications, and especially RCP/OSGi applications), I ran into issues with ClassLoaders. Some ClassLoaders won't know how to find the MyFancyProfiler class, so it will throw Exceptions when it tries to call the static methods in MyFancyProfiler.

My solution to this (and where my real problem is happening) is currently to "inject" MyFancyProfiler into each encountered ClassLoader by reflectively calling defineClass. The gist of it is:

public byte[] transform(ClassLoader loader, String className, /* etc... */) {
  if(/* this is the first time I've seen loader */){
    //try to look up `MyFancyProfiler` in `loader`.
    if(/* loader can't find my class */){
      // get the bytes for the MyFancyProfiler class
      // reflective call to 
      // loader.defineClass(
      //   "com.foo.bar.MyFancyProfiler", classBytes, 0, classBytes.length);
    }
  }
  // actually do class transformation via ASM bytecode manipulation
}

edit for more info - The reason for this injection is to ensure that every class, no matter which ClassLoader loaded it, will be able to call MyFancyProfiler.methodEntered directly. Once it makes that call, MyFancyProfiler will need to use reflection to interact with the rest of the system, or else I will get an InvocationTargetException or NoClassDef exception when it tries to refer directly. I currently have it working so that the only "direct" dependencies of MyFancyProfiler are JRE system classes, so it seems to be fine.

Problem

This even works! Most of the time! But for at least two separate ClassLoaders that I encounter while trying to trace Eclipse (launching the IDE from the command line), I get a NullPointerException coming from inside the ClassLoader.defineClass method:

java.lang.NullPointerException
    at java.lang.ClassLoader.checkPackageAccess(ClassLoader.java:500)
    at java.lang.ClassLoader.defineClass1(Native Method)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:791)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:634)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:601)
    // at my code that calls the reflection

Line 500 of ClassLoader.java is a call to domains.add(pd), where domains seems to be a Set that gets initialized at constructor time, and pd is a ProtectionDomain that (as far as I can tell) should be the "default" ProtectionDomain. So I don't see an obvious way for that line to cause a NullPointerException. Currently I'm stumped, and I'm hoping that somebody can offer some insight into this.

What could cause the defineClass to fail in this way? And in case there's no obvious solution, can you offer a potential alternative approach to my overall problem?

役に立ちましたか?

解決

Instead of injecting code to the ClassLoaders, try loading the jar that contains MyFancyProfiler in the bootstrap class-loader. Easiest way to do that is by adding the following line to the manifest of your javaagent jar:

Boot-Class-Path: fancy-profiler-bootstrap-stuff.jar

This will make everything in that jar accessible to all class loaders, including, I believe OSGi and friends.

他のヒント

You're getting NullPointerException because this is null in this case. If you want to load a class using bootstrap classloader (which is null) you need to bypass the security checks by using sun.misc.Unsafe.defineClass(String name, byte[] b, int off, int len, ClassLoader loader, ProtectionDomain protectionDomain); method with null as a ClassLoader and null as ProtectionDomain.

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top