31.4. MEETING APPLICATION-AWARENESS REQUIREMENTS WITH PROSETo show that dynamic AOP can be used for expressing application awareness, we developed PROSE (PROgrammable extenSions of sErvices), a system in which aspects are first-class Java entities and all related constructs are expressed using the base language, Java. The first version of PROSE was based on the debugger interface of the JVM [21]. The second version [19] is a JIT-based stub weaver based on IBM's Jikes Research Virtual Machine [1]. The PROSE experience has shown that dynamic AOP can be efficient and can be provided at a low implementation cost. In addition, by carefully selecting the features of the dynamic AOP platform, one can fully address the requirements of application awareness. The next section gives an overview of this system. 31.4.1. Addressing Application Awareness in PROSE31.4.1.1 Addressing SecurityFor application awareness, security is a very important feature. Just like Internet browsers that control the code they download from the network, the AOP support embedded in each node must guarantee that a foreign advice cannot damage the local system. In this respect, stub weavers offer a better choice than advice weavers. The reason is that a full-code weaver mixes advice and base code, and this makes the verification of the code origin at a later point in time a difficult task. By contrast, stub weavers separate advice code from the base code. This way, the advice code (potentially originating from a foreign host) can be kept in a sandbox. This setup allows well-understood security models (such as the Java Security Model [31]) to be enforced. It was for this reason that we have chosen the stub-weaver approach for PROSE. 31.4.1.2 Addressing PerformanceUnder normal operations (no woven aspects), the dynamic weaving platform should not lead to significant performance degradation (performance is a key requirement when dealing with online and mobile systems). Our decision to implement PROSE using a JIT-time approach was based on measurements using SpecJVM [27] and JavaGrande [8] benchmarks. The results are summarized in Table 31-2. They show that a JIT-time weaver slows down application execution less than 10% when considering all field operations and method calls as join points. The good performance of a JIT-time weaver was to be expected, as native code hooks can be programmed extremely efficiently. According to our measurements [20], JIT-time weavers compare favorably with load-based approaches, which incur larger overheads at each join point. With a JIT-time weaver, we also obtained encouraging results when expressing CPU-intensive adaptations like orthogonal persistence and transactional processing [18].
31.4.1.3 Addressing Portability and CostThe advantage of a small performance overhead (mainly due to the efficiency of native code) is balanced by the disadvantage of a reduced portability. By contrast, load-time stub weavers are usually portable from one JVM to another, as they are essentially replacements for a standard JVM classloader component. To address this restriction, we divided the AOP support provided in PROSE into two modules. 31.4.2. PROSE Architecture
Figure 31-4 gives an overview of this architecture. In the upper layer, the AOP engine accepts aspects (1) and transforms them into basic entities like join point stop requests (2). It activates the stop requests by invoking methods of the execution monitor (3). The execution monitor is integrated with the JVM. When the execution reaches one of the activated join points, the execution monitor notifies the AOP engine (4), which executes an advice (5). Figure 31-4. PROSE architecture.Following a conventional design approach, we kept in the execution monitor only the most frequently used functionality. Our goal was to show that this functionality can be provided at a low (implementation) cost. Indeed, in our first prototype, the execution monitor consisted of only 1600 lines of code added to the JVM core. If this support were available in every JVM platform, application providers could program their own AOP engines on top of the execution monitor.
When a new aspect is added to the system, the AOP engine activates join points using the join point activation API of the execution monitor. It specifies as parameter a class member (e.g., field or method) that uniquely identifies the join point to be activated. When an activated join point is reached, the execution monitor notifies through the callback API the AOP engine (each AOP engine implements an appropriate interface) by passing a join point object to the AOP engine. The passed join point object contains methods for the inspection of local variable values, of thread states, of return values, etc. This design implies making the stack layout visible to the join point object passed to the aspect engine. This is needed because the join point interface allows gathering information of the local environment (e.g., local variables). Stack visibility is just one of the problems that must be solved when providing an execution monitor. For consistency and efficiency reasons, a tight integration of the execution monitor with several components of the JVM is needed. One example is the division of a method's body into basic blocks. A basic block is a code sequence in which the stack layout remains unchanged. Basic blocks are used by the garbage collector to inspect the stack and to collect object references. When adding join point stubs, a method call may occur at a location where no basic block boundary was detected during bytecode analysis. As a consequence, JIT-level AOP implies the additional cost of adapting the bytecode analyzer of the JVM. In general, all information related to join points must be as close as possible to the internal representation in the JVM.
We explain the general architecture of the AOP engine by illustrating the actions for weaving an aspect using PROSE. Then we show the actions taken to execute the corresponding advice. Our example aspect definition encrypts all bytes-array parameters passed to sendBytes-methods. We use PROSE to describe this aspect: (1) // before methods 'void *.sendBytes(byte[] x)' (2) // do encrypt(x) (3) class ExampleCut extends BeforeCut { (4) void sendBytes(ANY thisObject, byte[] x) (5) { encrypt(x); } (6) } To weave this aspect, the AOP engine performs several sub-tasks, as illustrated in Figure 31-4. First, it inspects all the classes currently loaded by the JVM and gathers all methods m1 . . . mn that match the signature '*.sendBytes(byte[] x)'. For all generated stop requests, it activates method entry join points using the join point activation API of the underlying execution monitor. Upon entry in m1 . . . mn, the execution monitor calls the AOP engine. The AOP engine guarantees that all run-time conditions defining the reached join point are met (e.g., the aspect may be defined using control-flow crosscutting mechanisms [4]). If all the dynamic checks are passed successfully, the advice can be executed. Finally, the AOP engine executes the encrypt method. If classes are loaded by the JVM at a later point in time, the actions done for aspect weaving are repeated accordingly on each new class in the system. |