[AOP]
AOP weavers - are we doing it wrong?
[ avasseur ] 12:18, Wednesday, 10 August 2005
These last days I have been prototyping around an interesting idea.

As you know we have been working on an API to add JVM support for AOP in JRockit. The prototype will be available any time soon.

The nice thing about it is that you don't manipulate the bytecode anymore and that you are using only well known java.lang.reflect.* API to tell the JVM if your pointcut is matching or not. This is somehow similar to what ones can do with Spring AOP - as this one is proxy based (see f.e. MethodMatcher API in Spring).

The immediate benefit of it is that first there is no need to have another in-memory representation of classes beeing weaved (before they get loaded) that is backed by some expensive (both memory and CPU) bytecode analysis. This advantage is detailled in our JVM support for AOP part1 article.
As a consequence it is really easy to query the method annotation, generics properties, and such - something that is extremely complex to achieve with acceptable overhead in regular bytecode based weaver such as AspectJ or AspectWerkz (actually more complex than changing some bytecode instruction).

So what is that idea I had ?
Having AOP support in JRockit is nice, but it will take some time before that gets mainstream (with eventually a JSR etc). In this transition period, there must be a way to implement a better bytecode based weaver that will perform way better than current weavers (both AspectJ and AspectWerkz), that will be easier to implement, and whose only requirement is Java 5 (well off course, it won't be as good as JRockit JVM support for AOP - so it is still a transition technology).

I have thus been sketching on an hybrid system that makes extensive use of the hotswap API, and whose actual weaver relies only on pointcut matching backed by the java.lang.reflect.* API ie does not build any kind of equivalent structure backed by bytecode analysis.
As such the memory overhead is zero, and the CPU overhead is way less than current AspectJ and AspectWerkz.

The overall idea is quite simple and consists in 2 phases:
Phase 1
A first weaver is changing the bytecode in some stable way - such that all classes are transformed (lets say prepared) the same way - while not introducing any dependancies on any kind of AOP, and while not adding any kind of performance overhead (ie no changes in the execution flow such as introduced by wrappers method and such usually used when implementing instrumentation needed for around advice).
All classes thus get loaded as expected with a very limited time overhead and no memory overhead at all (thanks to the excellent ASM performance).

Phase 2
Then when an actual class gets loaded (as per regular application behavior) I get a small callback invoked when this class has just been loaded and just before anything else happens (ie the class static initializer invoked by the JVM). Current load time weavers do things "just before the class gets loaded" and as such cannot access the java.lang.reflect representation of what they weave.
This callback can then perform the actual weaving by relying entirely on the java.lang.reflect.* API to match the pointcuts. It then constructs the instrumented version of the class and hotswap it thanks to the Java 5 API. The JVM eats this one and goes on.
The first prepare phase is needed as the hotswap API does forbids change in the schema (ie cannot add or remove methods or fields).

A nice extra side effect is that at any point in time any class can be exposed to the AOP layer thru a simple API. This can be handy for some cases where there are some circular references in the dependancies (f.e. the instrumentation needs the java.lang.reflect.Method class to match against the pointcuts so if I want to add aspects to the Method class, I cannot do it until I have a representation of this class ie there 's no way to have it working by simply invoking the callback from the prepared Method class itself).

This might seems like gory details, especially when compared to what we do in the JRockit JVM support for AOP, but I am sure there are some concepts worth digging there - as memory usage and weaving overhead as been reported more than once as a major problem (see f.e. use case with AspectJ reported by Ron Bodkin where the system takes 4 minutes to start instead of less than a minute without any weaver here)
This makes it also very easy to add aspects even to java.* classes (though it's not generally a good idea unless you know what you are doing).

This sample is an actual code snip of the actual AOP transformation (part of phase 2). As you can see it relies on the java.lang.reflect API.

    public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
         if (!filter(access, name)) {//fast filter for f.e. clinit method as we look for method execution join points
              // see if we get a match for this join point
              // only java.lang.reflect.* API is used here
              Member method = ReflectQuery.getMethod(m_klass, name, desc);
              Class thisClass = m_klass;
              Class targetClass = null;
              Member withinCode = null;
              //do matching
              if (match(method, thisClass, targetClass, withinCode)) {
                   //change the bytecode but don't change the schema for hotswap purpose
               } else {
                   return super.visitMethod(access, name, desc, signature, exceptions);
               }
          }
     }

Finally I must say this idea is not 100% new as I wrote a paper on it for AOSD 2004 (read my paper here). At that time though I was not doing the pointcut matching based on the java.lang.reflect API - as the purpose was to enable dynamic AOP in AspectWerkz 1.0) ie I was not solving the problem of the memory and CPU overhead of the actual weaver.

What do you think? Are those ideas worth digging more?

Comments

Cool stuff, Alex. I wonder what performance impact would be from second class-loading + hotswap. Probably there are some other ways to implement pointcut matching without keeping entire structure in memory, that is somehow similar to what is Lucene and other search engines are doing, so actual index is much smaller then the real data, but it still allows to do matching there. Just a thought...

--eu, August 10, 2005 04:46 PM

That's right. I still need to see how expensive phase 2 can be (in terms of CPU only) to really see if all that makes sense.

I have been thinking for a while now of using an embedded Java DB to push the reflective information (say BerkeleyDB or a real jdbc enabled one) but what I don't like in such a model is that you still need to basically duplicate what is in java.lang.reflect (and this can be hell with complex hierarchy, generics, and so on to rebuild all that from the bytecode [not to say what happens with remote code where bytecode is nowhere locally etc]).

Is that the kind of approach you have in mind ?
That would indeed be interesting to have some Lucene behind the pointcut matching.

--Alex, August 10, 2005 04:55 PM

If I understand correctly the challenge is to say if given class is actually a subclass or implementing an interface given in a pointcut. If that is correct, then BerkleyDB or JDBC DB probably not the best option, because they ones are not the best choice for hierarchical searches.

It seems that the whole issue came from the fact that VM is loading dependencies for any given class after that class had been loaded. So, possible optimisation could be to read class name, super and interfaces without running a visitor trough ASM's ClassReader (it still would require to load constant pull for each class).

I also wonder if you can kick class loading for super class and interfaces and then actually do a pointcut check (even reflection-based). If so, then hotswap won't be needed. This is somehow similar to the approach used by CGLIB.

--eu, August 10, 2005 10:19 PM

We're currently using Sesame (www.openrdf.org) as a backend for both reflection & design information, so that the weaver can query it dynamically. Sesame offers several alternatives for data persistance. Anyway, as I once posted some days ago on the AOSD discuss list, I reckon the question of AOP overhead should be reconsidered...

cheers,
Alan

--Alan Cyment, November 3, 2005 08:51 PM

I think this is a great idea. I was thinking about how it might work and wonder how it could support inter-type declarations? It seems to me that with current hotswap implementation these have to be added "up-front".

Perhaps this could use a hybrid approach to define interfaces up front (including ITD's, declare parents, etc.) but defer detail implementations. Of course just resolving the interfaces can require some resolution...

--Ron Bodkin, November 28, 2005 10:31 PM
Post a comment









Remember personal info?