Articles
Enterprise Architecture
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:
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.
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:
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.
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.