Designing highly flexible virtual machines: the JnJVM experience
Software, Practice & Experience
Dynamic flexibility is a major challenge in modern system design to react to context or applicative requirements evolutions. Adapting behaviors may impose substantial code modification across the whole system, in the field, without service interruption, and without state loss. This paper presents the JnJVM, a full Java virtual machine (JVM) that satisfies these needs by using dynamic aspect weaving techniques and a component architecture. It supports adding or replacing its own code, while it
... running, with no overhead on unmodified code execution. Our measurements reveal similar performance when compared to the monolithic JVM Kaffe. Three illustrative examples show different extension scenarios: (i) modifying the JVMs behavior; (ii) adding capabilities to the JVM; and (iii) modifying applications behavior. made during system design may not be optimal for all kinds of application  . For quality of service requirements, the system policies need adaptations at a fine granularity (such as a method). (3) Security: installing security patches typically requires a lengthy reboot, but in some applications, such as commercial application servers, a service interruption is not acceptable. Simple plug-in mechanisms, such as loadable device drivers in Linux, allow an administrator to update a piece of code, but this technique is insufficient for changes that cut across previously loaded code, as in the above examples: the Linux kernel itself can not be changed and an adaptation implies unloading the plug-in and therefore the loss of its state. Published hotswapping mechanisms as in K42 [28, 44] let the state transfer between components be handled by the developer. For changes that imply updates in many components, the state transfer can be an arduous task. Our proposal is the addition of an aspect weaver to the component updating mechanism. The core of our system is the Micro Virtual Machine (MVM † ) associated with a just in time compiler (JIT). The MVM is extended on the fly by adding new code or replacing its code dynamically. Components are used for code injection (see Sec. 2.1) but code replacement relies on our dynamic compiler coupled with a dynamic aspect weaver  (see Sec. 2.2). A predefined extension, called the JnJVM provides a full JVM. Applications can further extend and modify this base. The MVM is a common core for different virtual machines: a complete CLR is also developed on top of the MVM. The CLR is not presented in this paper ‡ because this paper focus on dynamic code evolution but not on VM interoperability. System code is organized as a component architecture with associated adaptation points. Adaptation consists of compiling new source code and injecting it into the running components (either by extending or replacing methods) without losing their state and inserting new fields in components. This mechanism is inspired by aspect oriented programming and aspect weaving  . Of course, if the software architecture is modified and components are split or merged, the state of every obsolete component must also be transferred to the new architecture. Our adaptations focus essentially on binding new components without major modifications: oldest components are extended, but the software architecture of oldest components remains globally the same. When state transfer is furthermore required, we let the developer handle this transfer. Compared to dynamic software updating , we focus on running code evolution instead of source evolution: we construct code evolution from a representation in memory of the running process independently from source code. MVM relies on runtime reflection whereas dynamic software updating relies on source code comparison. Especially, our adaptation system does not impose a unique language: applications developed in different languages are composed at runtime, thanks to the abstract representation. Original and adapted code are not necessarily developed in the same language and developing the new adapted code does not require access to the original code: a piece of Java code or Microsoft Intermediate Language (MSIL) code can replace native MVM code or a piece of Java code can replace MSIL code. Moreover, † Our MVM is unrelated to the Multi-tasking Virtual Machine. :1-7 Prepared using speauth.cls DESIGNING HIGHLY FLEXIBLE VIRTUAL MACHINES: THE JNJVM EXPERIENCE 3 our adaptation system is developed only once for all languages, whereas semantic patches generation of dynamic software updating must be developed for all languages. On the other hand, dynamic software updating provides a general solution to state transfer, whereas we avoid this problem with AOP but we do not provide an automatic solution when state transfer is furthermore required. The main contributions of this paper are the following: • Design and implementation of a minimal, dynamically adaptable execution environment, the Micro Virtual Machine. It defines our component architecture and our aspect weaver. The Micro Virtual Machine is the main novelty of our research: it defines a common layout for building dynamically adaptable systems. As far as we know, there is no other work that tries to build a runtime dedicated to the construction of dynamically adaptable systems. • Design and implementation of the JnJVM, a flexible Java Virtual Machine (JVM) which extends the Micro Virtual Machine. The JnJVM is our main realization of a flexible runtime based on the MVM. The goal of our work is not the construction of a flexible Java Virtual Machine, but the construction of an environment dedicated to the construction of flexible systems. The JnJVM is only a complete applicative example that demonstrates the flexibility of the MVM. • Demonstrate different useful specialization scenarios: (i) modifying the behavior of the JVM, (ii) adding capabilities to the MVM and JVM, and (iii) modifying the execution behavior to applications above the JVM. • Performance evaluation of the JnJVM. It shows that the JnJVM performs roughly the same as the existing open-source JVM Kaffe for an equivalent engineering effort. Most of all, JnJVM is compiled on the fly by the MVM when it is loaded. Our performance study shows that this level of virtualization has an acceptable cost in term of execution speed and memory consumption. Our evaluation also shows that our prototype is not optimized and performs 2 to 10 times slower than robust JVMs (IBM and Sun). Better performances of robust JVMs are explained by their Just In Time Compiler which optimizes the code. With a better JIT, we expect similar performances. Our system is based on our minimal core, the Micro Virtual Machine, which contains a JIT, a dynamic loader, and a fine-grain dynamic binding mechanism. The MVM can run either atop a standard Unix or atop Linux. The JnJVM is itself a substantial extension of the core, and runs standard Java applications and benchmarks. We report here on a number of further specializations, both to the core and to the JnJVM. They demonstrate how applications can extend the core to accommodate to new requirements. Our study focuses on a JVM for practical reasons but the lessons learned are applicable to execution environments in general. Indeed, a Java virtual machine acts as a small operating system for Java applications and provides a powerful experimental environment, which leaves aside hardware-related issues. The main strength of the MVM is its independence from the targeted execution environment or language. We have also built a CLR over the MVM , a new middleware , a framework to adapt Jonas and OpenCCM  on the fly, a flexible Web cache , a flexible viewer for documents  , and an execution environment for active network  . The MVM imposes only :1-7 Prepared using speauth.cls § Three input/output components are defined. The first one launches an interactive shell, the second one waits for incoming new specializations from a socket and the third one reads a specialization from a file. The bytecode compiler component compiles the bytecode of a Java method: this component, the Just In Time Compiler (JIT) of the JnJVM, reuses the JIT (the VPU) of the MVM (see Section 2.1). The bytecode compiler of the JnJVM is therefore portable because it uses only the VPU interface. We use directly the API of the VPU to compile Java bytecode on the fly. By using the same technique, we can compile other bytecoded languages. A first version of the CLR built over the MVM is functional and reuses also the VPU  .