JRockit JVM Support For AOP, Part 1

Pages: 1, 2, 3, 4

HotSwap: Changing bytecode at runtime adds more complexity

Java 5 brought the HotSwap API as part of the JVMTI specification. Before Java 5, this API was only available when running in debug mode, and only for native C/C++ JVM extensions. This API allows the changing of bytecodes—that is, redefining a class—at runtime. It is used by some AOP frameworks and AOP-applied products to emulate runtime weaving capabilities.

Despite being very powerful, this API limits usability and scalability in these ways:

  • It is inefficient. Since bytecodes are being changed at runtime, instrumentation costs (CPU overhead and memory overhead) are also happening at runtime. Also, if there is a need to do a change in many places, this means redefining many classes as well. The JVM will then have to redo all the optimization and inlinings that it may have done before.
  • It is very limited. The API does not specify where the current running bytecode is that is safe to change. A weaver therefore needs to make the assumption that this bytecode is on disk, or it needs to keep track of it. This is a major issue when multiple weavers are used, as explained in the next section.

Furthermore, none of the current implementations of the HotSwap API supports schema change, which the specification states as being optional. This means that it is not possible to change the schema of a class at runtime, for example, adding methods/fields/interfaces that might be needed for the underlying instrumentation model. This makes it impossible to implement certain types of runtime weaving and therefore requires the user to "prepare" the classes in advance.

Multiple agents is a problem

When multiple products are using bytecode instrumentation, unexpected problems may happen. Problems related to precedence, notification of changes, undoing of changes, and so on. While this may not be a big problem today, it will be a significant problem in the future. A weaver can be seen as an agent (as referred to in the JVMTI specification) that performs instrumentation at loadtime or runtime. With multiple agents, there is a high risk that the agents will get in each other's way, changing the bytecode in a way that was not expected by the next agent, which makes the assumption that it is the sole configured agent.

Here is an example of a problem that can occur when two agents are unaware of each other. If one is using two agents, an AOP weaver and some application performance product, that are both doing bytecode instrumentation at loadtime, it is likely that, depending on the configuration, woven code may or may not be part of the performance measurement, as illustrated below:

// say this is the original user code
void businessMethod() {
  userCode.do();
}

//---- Case 1
// say the AOP weaver was applied BEFORE the
// performance management weaver
// the woven code will behave like:
void businessMethod() {
  try {
    performanceEnter();
    aopBeforeExecuting();//hypothetical advice
    userCode.do()
  } finally {
    performanceExit();
  }
}
// ie the AOP code affect the measure


//---- Case 2
// say the AOP weaver was applied AFTER the
// performance management weaver
// the woven code will behave like:
void businessMethod() {
  aopBeforeExecuting();//hypothetical advice
  try {
    performanceEnter();
    userCode.do()
  } finally {
    performanceExit();
  }
}
// ie the AOP code will NOT affect the measure

We have a problem with precedence between the agents; there is no fine-grained configuration to control the ordering at a join point (or pointcut) level.

Some other situations may lead to more unpredictable results. For example, when a field access is intercepted, it usually means that the field get bytecode instructions are moved to a newly added method and replaced by a call to this new method. The next weaver will therefore see a field access from another place in the code (from this newly added method) that then may not be matched by its own matching mechanism and configuration.

To summarize, the main problems are:

  • Which bytecode does the agent see? The problem is that normally the bytecode to be woven is obtained from the class-loading pipeline, but the dependent bytecode to build up the class database from is read from disk. When multiple agents are involved, bytecode on disk is no longer the one being executed; since some agent may have already changed the bytecode, this means that the second agent has a wrong view of the bytecode. This also happens when the HotSwap API is used.
  • There may be a problem with agent A undoing or changing its weaving. If another agent B has made changes after agent A, then agent B may have restructured the bytecode so that it looks completely different (even though it behaves the same), and then agent A will not know what to do.

Intercepting reflective calls is impossible

Current weaving approaches can only instrument execution flows that can be (at least partially) statically determined. Consider the following code sample that invokes the method void doA() on the given instance foo.

public void invokeA(Object foo) throws Throwable {
  Method mA = foo.getClass().getDeclaredMethod("doA", new Class[0]);
  mA.invoke(foo, new Object[0]);
}

This kind of reflective access is often used in modern libraries to create instances, to invoke methods, or to access fields.

From a bytecode perspective, the call to the method void doA() is not seen. The weaver will see only calls to the java.lang.reflect API. There is no simple and performant way of weaving calls that are made reflectively. This is an important limitation in how weaving can be performed and how AOP is implemented today. Best practices recommend that the developer use execution side pointcuts instead. Obviously, from a JVM perspective, there will be a method dispatch to the doA() method, even it does not appear in the source code or bytecode. JVM weaving has proven to be the only weaving mechanism that addresses this issue in an efficient way.

Other problems

Bytecode instrumentation, especially when done on the fly (at loadtime or runtime), is looked upon with skepticism by some people. There is an emotional angle to changing code on the fly that should not be underestimated, especially when it is paired with a mind-bending revolutionary new technology such as AOP or transparent injection of services. Clashes that may happen when multiple agents are involved will increase this skepticism.

Another potential problem is the 64Kb boundary for class files as stated in the Java specification. Method bodies are limited to a 64Kb total bytecode instruction size. This may pose a problem when weaving already large class files, like, for example, the resulting class file when compiling a JSP file to a servlet. When instrumenting this class, it may break the 64Kb limit, and then cause a runtime error.

Pages: 1, 2, 3, 4

Next Page »