Dynamic update of Java applications-balancing change flexibility vs ...

4 downloads 36793 Views 598KB Size Report
contemporary rich client software development. Not only can programmers benefit from dynamic updates during the development of concurrent applications ...
JOURNAL OF SOFTWARE MAINTENANCE AND EVOLUTION: RESEARCH AND PRACTICE J. Softw. Maint. Evol.: Res. Pract. 2009; 21:81–112 Published online in Wiley InterScience (www.interscience.wiley.com). DOI: 10.1002/smr.406

Research

Dynamic update of Java applications—balancing change flexibility vs programming transparency Allan Raundahl Gregersen∗,† and Bo Nørregaard Jørgensen The Maersk Mc-Kinney Moller Institute, University of Southern Denmark, Campusvej 55, DK-5230 Odense M, Denmark

SUMMARY The ability to dynamically change the behavior of an application is becoming an important issue in contemporary rich client software development. Not only can programmers benefit from dynamic updates during the development of concurrent applications where recreation of complex application states can be avoided during test and debugging but also at post-deployment time where applications can be updated transparently without going through the well-known halt, redeploy and restart scheme. In this paper, we explain how our dynamic update framework achieves transparent dynamic updates of running Java applications while guaranteeing both type and thread safety. A novel feature of our approach is that it supports full redefinition of classes by allowing changes to the type hierarchy. Our approach is based on a lightweight runtime system, which is injected into an application via bytecode transformations at class loading. We show how our approach can add dynamic update capabilities to rich client development by integrating it with the NetBeans rich client platform. Performance experiments on our NetBeans implementation show that the overhead of our approach is low when applied to component application programming interface classes. To the best of our knowledge no other existing approach achieves the same level of low overhead and programming transparency. Copyright © 2009 John Wiley & Sons, Ltd. Received 27 June 2008; Revised 2 February 2009; Accepted 4 February 2009

KEY WORDS:

dynamic update; software maintenance; software evolution

∗ Correspondence to: Allan Raundahl Gregersen, The Maersk Mc-Kinney Moller Institute, University of Southern Denmark, Campusvej 55, DK-5230 Odense M, Denmark. † E-mail: [email protected]

Copyright q

2009 John Wiley & Sons, Ltd.

82

A. R. GREGERSEN AND B. N. JØRGENSEN

1. INTRODUCTION While most rich client platforms, e.g., NetBeans platform [1] and Eclipse Rich Client Platform [2], support dynamic extension of applications through dynamic loading of new components, they lack general support for dynamic updates of already running components. The reason is that Java does not support full dynamic reloads of already loaded classes. The current version of Java (Java 6 at the time of writing) does allow redefinition of method bodies through an implementation of Dmitriev’s proposed class reloading facilities [3]. However, it prohibits changes to the class interface and type hierarchy. The absence of runtime support for dynamic reload of classes leads to the fact that runtime updates of classes have to be performed at the application level. This is achievable by using either of two possibilities: (1) loading a modified version of a class by a different class loader and (2) renaming the class and loading it with the original class loader. The first choice leads to a problem—reported to as the version barrier [4]. The version barrier is a result of Java’s type system, as the type of any class loaded consecutively by other class loaders is considered to be distinct from the original. Thus, the version barrier prohibits type correspondence between objects of the same class loaded by different class loaders. Likewise, the second choice leads to a loss of type correspondence, as objects belong to distinct types even if they are logically identical, due to the renaming of the class. Furthermore, this choice requires the application developer to explicitly deal with type wrappers and class casts. This paper presents an approach in which objects can transparently go back and forth through the version barrier while preserving the type safety of Java. It does so without introducing modifications to the Virtual Machine (VM) or extensions to the Java language. Updates take effect at the granularity level of components. Changes that do not break component application programming interface (API) compatibility with previous running versions have no impact beyond the module itself. The ones that violate the binary compatibility rules [5] expand the update set to also include new API compatible component versions for all running dependents. Classes can add any number of fields, methods and constructors and declare new extends and implements relationships. Changes to the inner workings of a component are immediately supported. In general, the approach supports unanticipated dynamic software evolution of both code and state at the level of classes. Based on commonly accepted guidelines for the API evolution, our approach allows the same set of state-full updates derived from a scheme that serializes state before halting the system and de-serializes after restarting the redeployed system. In addition to the original presentation of our approach in [6], this paper elaborates on design and implementation. Moreover, it reports on a number of improvements and new findings. These include the move from a compile-time transformation of the original system to a load-time approach using bytecode modifications. Furthermore, we present a novel contribution in the form of a lazy state-migration mechanism that preserves the Java concurrency semantics. In addition, Section 6 outlines best practices including how to overcome the general problem of updating running threads by following a simple design pattern. Moreover, Section 5.3 includes our performance experiments and results based on a case study that was published at the 2008 IEEE European Conference on Software Maintenance and Reengineering [7]. The new findings and improvements have advanced our approach further since our first reporting. The rest of the paper is organized as follows: Section 2 states the overall goals of dynamic update. Section 3 describes the design of our approach while Section 4 presents implementation details. Section 5 presents the case study of how the approach advances the current NetBeans reload feature. In addition, Section 5 reports on the

Copyright q

2009 John Wiley & Sons, Ltd.

J. Softw. Maint. Evol.: Res. Pract. 2009; 21:81–112 DOI: 10.1002/smr

DYNAMIC UPDATE OF JAVA APPLICATIONS

83

performance characteristics of the approach obtained from the case study. Section 6 describes best practices. Section 7 discusses related work. Finally, we conclude in Section 8. 2. GOALS AND APPROACH In order to evaluate our approach, we have extracted major design goals from the existing papers on dynamic update. Some of these goals are explicitly declared, whereas others are only implicitly discussed. Programmer transparency. Programmers should be able to continue their standard development praxis; thus, the dynamic update approach should require as little programmer insight as possible [8–14]. Flexibility. The set of changes allowed to a running application should be as large as possible. In general, all changes achievable through the halt, redeploy and restart update scheme should be supported [9,12–15]. Performance. The overhead imposed by the approach should be minimized to an acceptable level [8,9,12]. Correctness. The behavior of an updated application should be equivalent to that of an application initially deployed with the updated code. Furthermore, the dynamic update approach must ensure correct application behavior during updates [10,12,14]. Concurrency. A dynamic update approach should support multithreaded applications. Thus, it should not introduce any deadlocks or lost state update situations [8,16]. Availability. A dynamic update approach should only affect the availability of the impacted parts of the running application during updates [14,17]. This is particularly important in multithreaded applications. Configurability. Approaches that support one policy only may require certain coding conventions for particular aspects of a dynamic update in order to accomplish dynamic updateability. To avoid such unwanted situations, the approach should have the ability to incorporate different update policies. Examples are the possibility to allow already running code to finish after an update or setting up different state migration policies to choose from [12,17]. The latter is required when the data structure of an application’s state is changed by an update. Roll-back. If a dynamic update results in unwanted application behavior, it should be possible to roll-back the update [8]. However, this is non-trivial if the execution results in non-reversible actions on the application’s context. The above criteria are ordered in terms of importance according to our interpretation. No generalpurpose dynamic update approach has been widely accepted in statically typed object-oriented programming languages, mainly because they all at some level require programmers to take special actions in order to gain the capabilities of the underlying technique. This is heavily supported by the experience obtained by Mark E. Segal, who concludes that, ‘The more transparent the online upgrading system is to the developers and their development processes, the more likely it will be used’ [18]. On the other hand, flexibility is equally important as systems constraining the update process will limit the set of possible updates. Performance and correctness are also important for obvious reasons. Concurrency is another aspect of increasing interest as more applications are processors. Full concurrency support requires that any sub-part of the system that is not impacted by the update must be equally available, as if the update was not taking place. The configurability

Copyright q

2009 John Wiley & Sons, Ltd.

J. Softw. Maint. Evol.: Res. Pract. 2009; 21:81–112 DOI: 10.1002/smr

84

A. R. GREGERSEN AND B. N. JØRGENSEN

requirement has not been an area of focus in the literature. This is probably because researchers focus mainly on finding one general applicable dynamic update scheme applicable to all environments. We believe that finding such a solution is simply not likely. Researchers will need to incorporate different policies for achieving different levels of dynamic updates. Of course, the default policy should fulfill the transparency requirement and provide out-of-the-box support for the largest possible set of dynamic updates. Lastly, roll-back is considered necessary, at least for environments with strong consistency requirements. Other characteristics of a good dynamic update approach have been reported such as ‘recovery of tainted state’ [8] and support for powerful refactorings such as merging or splitting classes [15]. These characteristics represent special cases of the configurability requirement as they are typically orthogonal to the approach itself and they can be incorporated if new policies are introduced to handle them. To the best of our knowledge, no existing approach achieves a combination of high transparency and support for flexible changes to the running system. We believe that these two factors play a key role in finding a dynamic update approach that can be easily adopted by the programming community. A key aspect of our framework, Dynamic Correspondence Proxification, is that it imitates standard development practices for off-line software evolution. Consider a traditional component-based system. Whenever the system is modified due to new requirements, its programmers change and recompile any affected component. Before the rewritten components can be redeployed, the system must be shut down and its state made persistent. After redeployment of the rewritten components, the system is restarted and its persisted state reestablished. Thus, the key to a successful update is the fact that programmers are able to compile the rewritten components, thereby ensuring binary compatibility with client components and reestablishing persisted system state. This well-practiced off-line scheme ensures that rewritten components are binary compatible with client components before a restart is attempted. In case a change results in binary incompatibilities between components, the programmer must resolve those before redeploying the rewritten components. A similar scenario exists for components developed by a third party. If a third party makes a binary incompatible change to the API of one of its components, any client component of the previous version of the component has to be updated as well. This is typically the responsibility of application programmers. Having fixed the incompatibilities in all the client components, we are back to the standard off-line update scenario, capable of applying the update without the risk of a runtime linkage error. Our approach makes the assumption that in order for a dynamic update to succeed, the set of components that make up the updated application must be binary compatible, thereby not compromising the correctness of the application. Therefore, programmers need not change their development practices nor do they need any knowledge about the underlying dynamic update mechanism. Hence, the approach maintains the usual level of programmer transparency. In addition, it supports highly flexible changes to classes; most noticeable is the ability to change the inheritance hierarchy of running classes. Concurrent applications are supported by a lazy update scheme where application threads adapt to class updates migrating state on an on-demand basis. It preserves standard Java semantics for synchronization and, more importantly, neither concurrency issues nor any risks for deadlocks are introduced by the approach. Further, the lazy update mechanism ensures high availability at all

Copyright q

2009 John Wiley & Sons, Ltd.

J. Softw. Maint. Evol.: Res. Pract. 2009; 21:81–112 DOI: 10.1002/smr

DYNAMIC UPDATE OF JAVA APPLICATIONS

85

times regardless of when updates are rolled out. In fact, our dynamic update approach only affects the availability of individual members (i.e., classes and methods) of an updated component on the first access after a successful update, as the classes and objects of the component have to be transferred to the new version. With regard to the configurability of our update approach, we do not directly support different update policies. However, our framework is orthogonal to such extensions. The main goal at present is to provide a framework for dynamic update that fulfils the transparency requirement while still supporting flexible component evolution at runtime.

3. DYNAMIC CORRESPONDENCE PROXIFICATION Our dynamic update approach is based on the knowledge of the typical class-loader setup in modern component systems. Throughout the paper we use the term component as an entity that contains classes, presumably organized in packages, in addition to its local resources. Furthermore, a component will have a well-defined API from which the outside world, namely dependent components, can reach its public members. A component explicitly declares a dependency on other components. Typically, every component is assigned to a distinct class loader solely responsible for loading classes and resources explicitly declared by that component. A class load of a class declared in a component dependency consists of finding the associated class loader and delegates the class load to that class loader. This broadly applied scheme results in a unique representation of each class, which makes type sharing between different components straightforward. However, in context of dynamic replacement of components, a question arises: How do components reach classes declared in a newer version of their component dependencies? In lack of built-in support for class reloading, systems relying on hot-swapping implementation classes, such as Dynamic Updating through Swapping of Classes (DUSC) [14] and Dynamic C++ classes [19], must assign a new class loader with each replaced component. From a VM viewpoint, a new set of distinct types are thereby created. This situation is shown in Figure 1. Associating new class loaders with replaced components is problematic as client components are statically bound to the former version of the component. To clarify, if a component F class in Figure 1 uses the class A declared by component D, then updating component D will create a new type A, which is not assignment compatible with the original type A. Therefore, an attempt to alter the class-loading delegation scheme dynamically to let the class loader associated with component F delegate to component D(1) will not succeed. As an example: A case where client classes have already been linked with classes in component D(0) using the new type will result in class-cast exceptions and linkage errors. Nonetheless, in the following sections we present a dynamic update framework that resolves the issues from the setup shown in Figure 1. Moreover, it successfully handles all type-related issues originated from the version barrier, entailing that component communication across the version barrier can happen seamlessly within the current semantics of Java. To ease the understanding of the technical descriptions, the following notations apply throughout the remainder of this paper. A target component denotes the most recent version of a running component. In consecutive updates of a component, we use the term former component to refer to any of the components but the target. At update time the terms current target and new target are

Copyright q

2009 John Wiley & Sons, Ltd.

J. Softw. Maint. Evol.: Res. Pract. 2009; 21:81–112 DOI: 10.1002/smr

86

A. R. GREGERSEN AND B. N. JØRGENSEN

Figure 1. Typical system setup to the left. At some point component D updates to D(1). Unfortunately, classes in client components (F) cannot re-link to D(1), which makes the code in D(1) unreachable by default.

used when referring to the target component before and after the update, respectively. The term intermediate component is used to refer a particular component version between the target and some former component. As an example, consider four versions of Component A: A1, A2, A3 and A4. In this case A4 is the target and all the others are former components. Components A2 and A3 are intermediates relative to A1, and A3 is an intermediate relative to A2. Furthermore, a component is associated (if appropriate to the given explanation) with a time of the update that is noted on the form A(n) also used already by Figure 1. Timings are always represented as a linear sequence of equidistant real numbers even though the time difference between updates varies. Moreover, any two components that represent two versions of the same initially deployed component are mentioned as correspondent components. In addition to these time-related terms, any user-specific component built on top of the component platform itself is said to be an updateable component. At the class level of an updateable system, we use the term updateable class to describe a class that has been declared in an updateable component. 3.1. In-place proxification A basic concept of our dynamic update approach is the In-Place Proxification technique. It is the glue between a former component and the target. A number of other dynamic evolution techniques adopt a variant of the generic wrapper terminology [20] to introduce the indirection mechanism to support dynamic update [14,16,21,22]. They do so by introducing new language constructs [16,21,22] or by using the wrapper only as a hot-swapping mechanism as proposed in [14]. A wrapper intercepts calls to a client before it reaches the client, thus supporting some unanticipated changes to be implemented. However, this usage may invalidate the use of the implicit self-parameter in forwarded messages if not handled properly, as seen in [16]. The In-Place Proxification mechanism provides a novel solution to this problem. It uses in-place-generated code to provide the level of indirection to a newer version within the code of the present one. The main idea is to generate a component-level switch to enable proxy behavior inside the code of components. Figure 2 illustrates how incoming requests to component D(0) are intercepted and forwarded to D(1). This forwarding cannot happen through direct communication as it will cross the version barrier that rises due to the creation of a new and distinct namespace in component D(1). For that

Copyright q

2009 John Wiley & Sons, Ltd.

J. Softw. Maint. Evol.: Res. Pract. 2009; 21:81–112 DOI: 10.1002/smr

DYNAMIC UPDATE OF JAVA APPLICATIONS

87

Figure 2. Conceptual overview of the In-Place Proxification technique. A cross-component invocation is always intercepted in the API of the receiving component. Once intercepted, a component-level check decides if the component should execute local code or forward the request to the target component.

reason we use the Java Reflection API [23] to communicate from former components to the target. However, reflection does not provide a complete solution to the problem at hand, as the formal parameter types and the return type in a forwarded method invocation may be incompatible with the types known by the former component. In general, the prerequisites described in the following subsections apply to a method declared in an updateable class for successful forwarding. The target class. Every updateable class needs to know about the target class. Therefore, the Dynamic Correspondence Updating framework generates a static field of type java.lang.Class in every updateable class just before it is loaded. The field is set on first usage after every update of the declaring component. The target method. Finding the correct target method is troublesome because updateable formal parameter types are not necessarily type-compatible with the ones in the calling method. Therefore, the correct types must be looked up so that they match the formal types of the target method. The target method will always be declared in the target component. However, this is not necessarily the case with the formal parameter types of the target method. Consider the scenario in Figure 3 where two components depend on a third component that is afterwards been updated. Now, if a method, say m, declared in component D uses a component E type class as a formal parameter type, this type would not comply with the namespace of the target component E, namely E(2). Therefore, a type in the namespace of E(0) should be used to look up this method. The only reasonable option to obtain the correct type version, regardless of origin, is to ask the associated class loader of the target class, which delegates to known class loaders only. Asking class loaders for classes, each time a method invocation happens, introduces an unacceptable overhead. It would force the execution of the class-loading algorithm for each updatable formal parameter type in each method invocation. This is why the dynamic correspondence framework caches already looked up methods directly in the declaring class. In addition, it maintains an already loaded-class cache for each updateable component. The target object. In case we deal with a static method, the target class and method are sufficient to support method forwarding. However, in an instance-method invocation, the method must determine the object of which the invocation should be forwarded to. A reference to this target object is kept

Copyright q

2009 John Wiley & Sons, Ltd.

J. Softw. Maint. Evol.: Res. Pract. 2009; 21:81–112 DOI: 10.1002/smr

88

A. R. GREGERSEN AND B. N. JØRGENSEN

Figure 3. Component bindings after successful updates of first component D and afterwards component E. Looking up formal parameter types in order to obtain a reference to the target method does in general not always correspond to lookup class types in target components. The correct version can be determined by the target component by asking its associated class loader for known class types.

in every instance of an updateable class by a code-generated instance field of a special marker type. The marker type is a Java interface that is automatically implemented by all updateable classes. We denote with the term target information for all of the above-mentioned targets, namely, target component, class, method and object. However, the term is context dependent in the sense that in a static method context the target information consists of the target component, class and method only. Actual parameters and return value. Finding the correct parameters to forward to the target method is identical to determining the target object in the first place. Parameters must represent the same entity in the system regardless of version. Therefore, the actual parameters delivered to the target method can be a combination of In-Place Proxies and target objects according to what types are understood by the target class. This also applies to return values, and we refer to Section 3.2 for details about object correspondence handling. Besides evolution of method bodies, the Dynamic Correspondence Proxification framework also allows dynamic evolution of the class interface and the type hierarchies. Changes that affect the type of an API class become directly visible to a client component at the time of an update of the client. This is achieved by letting the class loader associated with the new version of the client component directly delegate its class loading to target components of its component dependencies. The situation is illustrated in Figure 4. The setup shown in Figure 4 also reveals another very important property of the Dynamic Correspondence Proxification framework with respect to the overall performance. Clearly, the forwarding of API methods in accordance with the mentioned prerequisites poses a relatively large performance penalty. This is mainly caused by the introduced reflective behavior. However, once the clients have been compiled against the updated component API and then dynamically bound to the new target component, these invocations will again happen directly to the target method. Hence, method invocation speed returns to the speed before the update. Of course it takes some update-time to set up the new state-space, which we will get back to in Section 3.3.

Copyright q

2009 John Wiley & Sons, Ltd.

J. Softw. Maint. Evol.: Res. Pract. 2009; 21:81–112 DOI: 10.1002/smr

DYNAMIC UPDATE OF JAVA APPLICATIONS

89

Figure 4. Any new component (i.e., F(2)) introduced dynamically will establish bindings with the current target module (i.e., D(1)) of its dependencies directly and thus will be able to use those types as if they were deployed together in the first place.

3.2. Correspondence mapping While the In-Place Proxification technique of the previous section enables communication across class-loader boundaries, it does not maintain program consistency by itself. As explained above there are type-related problems involved in the forwarding that need to be handled to support correct execution flow. The framework must ensure that any live object in a particular target component is represented by either zero or one object in every corresponding former component. Zero because corresponding objects are created on demand only. For example, running a normal constructor in a target component does not trigger the creation of any other objects in former versions at that point. Only if a target object is returned through an In-Place Proxy method, a corresponding object is constructed and returned in its place. This corresponding object is preconfigured to forward all future method invocations to the target object from which it was created. In order to guarantee that correct objects are always returned, the framework must provide a registry in which objects are paired in accordance with its live representation. This is where Correspondence Maps become useful as they are in essence a one-to-one pairing between corresponding objects of two versions of a particular component. To control these correspondence maps a Dynamic Correspondence Controller (DCC) is introduced. At update time the DCC changes the behavior of all classes and objects of the current target component to become In-Place Proxies by altering an isInPlaceProxy flag of the current target component. On the first usage of the In-Place Proxificated object, it tells the DCC to construct a corresponding uninitialized object from the class definition declared by the new target component and add these pairs into the appropriate correspondence map. Furthermore, this triggers the initialization of the target object field that is set to the new object created. Actually, the DCC adds the corresponding two objects to two different maps: one that uses the former object as key and one that uses the target. This is for performance reasons only. At this point, an uninitialized object indicates that no state of the former object has been migrated to the new target object, which also happens lazily on the first usage of individual fields. This lazy initialization of target objects is new to our framework, as the update scheme described in our previous work, [6,7] was based on a specialized copy algorithm for moving the entire object graph in one atomic operation. Section 3.3 reasons about the move to lazy state migration and describes the design. The above-mentioned scenario describes how corresponding objects are created and handled for already present objects in the running system. Clients can continue to create objects of former components after the update took place. Constructor invocations then forward to the corresponding

Copyright q

2009 John Wiley & Sons, Ltd.

J. Softw. Maint. Evol.: Res. Pract. 2009; 21:81–112 DOI: 10.1002/smr

90

A. R. GREGERSEN AND B. N. JØRGENSEN

constructor in the target component just as method invocations do. Although this will execute two constructors, only one user-written constructor body runs. In addition, the DCC pairs the two objects just before returning from the original constructor. Another important issue is to guarantee that any method invocation across component boundaries only goes through one level of indirection via an In-Place Proxy. Suppose that a component has been updated twice and its objects go through the In-Place Proxification process two times. Then, when a client of the first version invokes a method, it will go through an intermediate component to get to the object in the target component. To guarantee one level of indirection, an In-Place Proxy class maintains a static forward reference to its last known target component. Eventually, this target is updated and its isInPlaceProxy flag is changed to enable forwarding in its declared classes. This makes the forward reference held by the initial component point toward an intermediate component, which is not desirable. Therefore, a simple check of the target component reveals that a new target component has been installed and the forward reference is updated by the application thread itself just before the forwarding. Furthermore, the update is reflected in the correspondence maps as well. To ensure that the old target stays consistent, it is also assigned to the newly created target. In general, the framework must guarantee that every representation of an object forwards to the same target object. This is handled by the DCC that corrects the references from any former version of a specific target object at the point where one of them discovers that the target has changed. Having explained the In-Place Proxification technique and how the framework handles correspondence, we now have all ingredients that were set up in order to achieve successful method forwarding. Listing 1 explains the basics of the method forwarding. IF this is an In-Place Proxy THEN IF target information is uninitialized THEN Initialize target information and add new correspondence ELSE IF target is not current THEN update target information. DCC corrects correspondence END IF END IF IF target method is not in cache THEN lookup the target method and save it END IF FOR each parameter having updateable type ask DCC for the object understood by the target component END FOR invoke target method using parameter values found IF method has updateable return type THEN ask DCC for object understood by caller END IF ELSE execute normal method body END IF Listing 1. Pseudo code algorithm for method forwarding.

Copyright q

2009 John Wiley & Sons, Ltd.

J. Softw. Maint. Evol.: Res. Pract. 2009; 21:81–112 DOI: 10.1002/smr

DYNAMIC UPDATE OF JAVA APPLICATIONS

91

Figure 5. Overall control flow of the approach.

Figure 5 shows a cross-component method invocation with a non-void return type and a single parameter. It illustrates how constructors can be forwarded in case of updates. In addition, it provides an overview of how the DCC pairs corresponding objects. The figure shows the most relevant code snippets only. 3.3. State migration State migration is central to dynamic update. How and when to move state are vital research questions addressed by numerous authors (i.e., [9,14,24]). Generally, implementations fall into two categories: (1) transparent state migration and (2) programmer-influenced state migration. In our opinion, transparent state migration is preferable over programmer-influenced state migration since it does not require explicitly written version adaptors or state transfer functions. Writing version adaptors and state transfer functions is a labor intensive task, which is unnecessary for many types of updates. We have chosen a simple, yet powerful mechanism that transfers field values of the former component to the target component automatically and transparently. In our previous work [6,7] the state migration policy was based on a temporary stop of the current target component. During this stop, the state migration was performed atomically by a special update-thread and afterwards the component re-enabled. This approach required the component to be in a special quiet state called quiescence [25] or another less disruptive requirement called tranquility [26]. Basically, it states that no active process must execute within the current target component during the update. Furthermore, any incoming requests should be queued. While the tranquility requirement ensures consistency during updates an ‘important drawback is that it is not guaranteed that a tranquil status will ever be reached for a given component’ [26]. In general, this implies that any system waiting for tranquility must also implement a fall-back mechanism of quiescence. Unfortunately, the bookkeeping involved in monitoring activity in components imposes a non-negligible performance overhead. It is hard to correctly determine such safe criteria in a

Copyright q

2009 John Wiley & Sons, Ltd.

J. Softw. Maint. Evol.: Res. Pract. 2009; 21:81–112 DOI: 10.1002/smr

92

A. R. GREGERSEN AND B. N. JØRGENSEN

IF the state is present THEN return it immediately ELSE IF this is an In-Place Proxy THEN forward request to target getter method use DCC to convert to correct namespace ELSE retrieve and convert state from a former version update state field and save state END IF END IF Listing 2. Pseudo code for the generated getter method.

multithreaded application, without using very expensive synchronization mechanisms in every API element. Furthermore, forcing incoming threads to wait at the gate of a component may cause deadlocks in the sense that these threads might hold the key to notify other threads waiting. In the present version of our work, we take a completely different approach to state migration. Instead of forcing threads to wait while an update takes place we now perform state migration lazily on demand. State is migrated from a former component to the target component by application threads on first accesses of fields. This approach preserves the current synchronization semantics of Java as no special update-threads run. Furthermore, deadlock is no more an issue as no threads are stopped temporarily. Section 3.2 explained how the application threads constructed new target objects as uninitialized without migrating state to their fields. After an update every incoming request ends up in the new target component and, eventually, it will cause the state to be transferred from the former versions. To achieve this, the approach creates special getters to handle field reading. The algorithm in Listing 2 represents the generated getter method. The basic idea is to maintain a Boolean field for every field that reflects whether the state is present. Upon class loading and standard constructor invocations, these fields are set to false (default initialization). In this case false reflects that the state is present, because any state is in the initial version at first. In new target classes and objects constructed from former objects, these fields are explicitly set to true. Thereby, it indicates to the associated getter methods that the state is not present. To obtain the state, the DCC is used to lookup former versions until it finds the one holding a false value in its associated Boolean field. Then the field is transferred from the former version to the target and the Boolean values are set to true and false, respectively. The process also depends on the type of the value found as only non-updateable types can be copied by reference. If it is an updateable type, the DCC is used to find or create the correct correspondent object. The algorithm in Listing 2 ensures that only one active copy of a particular state exists. Thus, no synchronization of state in different versions is done. We name this the consistency criterion because it enforces consistent retrieval of any field value regardless of the origin of the caller. The process of field writes is similar. However, if a field write is made to a field after an update, then we have a situation where the state in the former version is overwritten in the target. The target

Copyright q

2009 John Wiley & Sons, Ltd.

J. Softw. Maint. Evol.: Res. Pract. 2009; 21:81–112 DOI: 10.1002/smr

DYNAMIC UPDATE OF JAVA APPLICATIONS

93

version must update its associated Boolean field, which yields an inconsistent situation. Therefore, the state field of the previous owner of the state must be cleared. An important property of the framework is that it supports introduction of new fields in classes. It has just been described how the associated state fields of migrated objects have been explicitly set to true. This does not reflect the fact that new field values are actually stored in target classes, initially. For that reason, once the special getter is invoked for the first time, it ends up trying to locate the field in a former version. It will then discover that this particular field has been added and so correct the Boolean field as if the value was already present. However, new fields cannot automatically be assigned to meaningful values. To circumvent possible null-pointer exceptions or unexpected default primitive values, the framework supports initialization of field assignments explicitly written at the class level by programmers themselves. For every class-level assignment written by the programmer, the framework guarantees that a new object created from any former object will hold the values assigned by the programmers. This is accomplished through bytecode analysis and the details are given in Section 4.1. 3.4. Binary incompatibility Having explained how the approach handles binary compatible changes, this section describes how it handles binary incompatible changes. The approach imitates the off-line update scheme requiring update sets that jointly constitute binary compatible components. Thus, the approach also supports updates of component APIs breaking binary compatibility with their previous version. We use the well-known and simple example of adding a parameter to an existing method as an example. Listing 3 visualizes this change. Now, trying to update the component containing the method in Listing 3 may result in broken clients. Therefore, a static dependency verifier locates such incompatible component changes before performing the update. Concerning Listing 3, the verifier will find a removed API-method change. It then tries to locate updates to client components and checks the compatibility with the updated set of client components. If it does not find any incompatibilities, it carries out the update of the new set of components; otherwise, it discards the update. Introducing binary incompatible changes thus affects the update ability of the running system by placing requirements on client components. This is a well-known restriction in independently developed components. The price to pay for breaking a contract, set up by the original version of the component, is the risk that the approach cannot apply the update in all running systems before the implicated programmers invest in updating clients as well. API designers are fully aware of this price. Typically, they take actions to maintain binary compatibility. An example is that they will overload the method in Listing 3, thus maintaining the old API for a number of source-code iterations. We stress that this limitation is exactly what you get from an off-line update scheme, and that it has nothing to do with how the updates are carried out. Any attempt to introduce smarter mechanisms for allowing updates of binary incompatible changes will break programmer transparency. However, such extensions are orthogonal to our approach as they can be incorporated into different update policies. public void createInstance(String name, int ID) {...} public void createInstance(String name, int ID, String description) {...}

Listing 3. Example of a binary incompatible change between two component versions.

Copyright q

2009 John Wiley & Sons, Ltd.

J. Softw. Maint. Evol.: Res. Pract. 2009; 21:81–112 DOI: 10.1002/smr

94

A. R. GREGERSEN AND B. N. JØRGENSEN

3.5. Other issues In this section we briefly discuss a number of issues caused by the ideas of In-Place Proxification and correspondence maps. Obejct identity. Although the Dynamic Correspondence Proxification framework ensures that only objects understood by clients are returned from API methods, object identity issues may still arise in which the operator ‘==’ gives a false negative. Consider for example the usage of an IdentityHashMap from the Java collection framework. If such a map is used as a registry in, say Component A, it is possible for different object versions of a particular client to be registered in the map. Now, checking if a given object is in the registry would return false if the map contained another version of the object. The framework addresses this by intercepting == occurrences and checking to see if the objects involved are updateable. If not, normal == behavior is used; otherwise, the == operation is performed on the target version of the objects. This ensures that the test is carried out on objects of one namespace only. Concurrency. Consider a multithreaded setup where objects need to wait for the execution of some critical code. The built-in wait() and notify() mechanisms of Java pose no problem for an un-updated system. However, in an updated system, where different versions co-exist, simply invoking wait() on one of these objects is not sufficient to achieve correct semantics. The InPlace Proxification mechanism ensures correct behavior of wait calls by means of forwarding calls to target objects. However, naive forwarding of notify() could lead to situations where a former object will not release the threads waiting (i.e., if a wait() was invoked before an update). To handle these situations, our framework broadcasted notify calls to all versions of an object. Hence wait() and notify() calls are handled correctly across the version barrier. Class type co-existence. The co-existence of different versions of a class in a running system can have negative side-effects on type-based tests. For example, the isInstance() method of the java.lang.Class class may return a false negative if performed on different namespace classes. Therefore, the framework intercepts all type-checking methods ensuring that tests are performed in comparable namespaces only. Internal API update problem. A class, the type that is not publicly available to other components, is normally not regarded as a member of the component API. However, its public and protected members are reachable through the API, if it implements/extends an API interface/class. Consider a removal of such a class in a given update; a perfectly binary compatible change [5]. Likewise, if such an API class is added by an update, by default a corresponding class in the namespace of the former version does not exist; thus, an In-Place Proxy cannot be constructed. These issues are addressed in [27] where virtual updating of dependent clients components are proposed. This is done by performing a regular update of the existing code base in order to move the type knowledge to the new target namespace. Although avoiding no-such-method-exceptions, there are risks of state loss because the prior type of a particular field does not have a corresponding representation in the new target. However, this will always be the case when using a completely transparent approach to state migration. In the situations where new internal API classes are added through an update, the approach simply constructs the bytecode of an In-Place Proxy representation, which is then used when former client versions retrieve instances of these types. Please refer to [27] for details. System class sub-classing. Using the extends clause to inherit behavior from a system class poses some technical troubles once instances of these sub-classes have become in-place proxies.

Copyright q

2009 John Wiley & Sons, Ltd.

J. Softw. Maint. Evol.: Res. Pract. 2009; 21:81–112 DOI: 10.1002/smr

DYNAMIC UPDATE OF JAVA APPLICATIONS

95

Consider the invocation of a method not overridden in the sub-class declared in the former component. By default the invocation will be carried out in the state of the former object that is not in agreement with the consistency criterion that only allows active state in the target component. The framework handles such situations by intercepting public and protected members of system classes and forwards them to the target object, if necessary. This can be done directly without the use of reflection due to the fact that neither parameter types nor return types can be changed in system classes. Thus, it does not introduce much of an overhead. Inheritance across components. When a subclass declares to extend from a class in a different component, runtime issues can occur after an update of the parent class. Consider the invocation of a non-overridden method in the subclass. It will end up executing in the superclass. In this case, the super method will find itself in a situation where it has been updated while the object executing has not been. Executing the method in the old superclass is not an option due to the fact that the code may have changed. Trying to invoke the target method instead is not possible, because no corresponding subclass is present in which a target object can be created. To deal with such situations, our previous work suggested performing a virtual update of the client. This brings the subclass up-to-date with its superclass. As seen, these situations only occur when inheriting from a non-pure abstract class. Therefore virtual updates of clients are not necessary for interfaces, which are the most used cross-component inheritance scenario. Circular dependencies, deadlocks and other bugs. There is a potential risk that an updated component introduces a circular dependency between running components or, even worse, a deadlock. In example, if a thread waits for an object to be notified, this notification may never happen if the code has been modified. This is the case if the modification has removed the notification completely or changed the assumptions for making the notify call. We consider such modifications of the concurrency semantics to be incompatible dynamic updates. The approach does not provide any special mechanisms to deal with circular dependencies or deadlock situations caused by faults introduced by the programmers. In fact, any bug introduced by programmers cannot be automatically captured and handled in general, regardless of the origin of the error.

4. IMPLEMENTING DYNAMIC CORRESPONDENCE PROXIFICATION To integrate the Dynamic Correspondence Proxification framework into an existing component, system requires additional code to be injected into the system and application classes. This code is injected during class loading using bytecode transformations. Furthermore, the framework needs to hook into the class-loader hierarchy to obtain the required information for the DCC to initialize the correspondence maps and allows classes to check whether they have been updated. 4.1. Bytecode transformations When you build an application on top of a component system (i.e., NetBeans Platform or Eclipse Rich Client Platform) you have to specifically declare for each component, which packages should be made visible to dependent components. This means that only public and protected methods contained in the selected packages can be reached from dependent components. This eases the job of locating injection points; that is, where to inject code in classes and methods.

Copyright q

2009 John Wiley & Sons, Ltd.

J. Softw. Maint. Evol.: Res. Pract. 2009; 21:81–112 DOI: 10.1002/smr

96

A. R. GREGERSEN AND B. N. JØRGENSEN

The required transformations can be done at the source-code level using a preprocessor approach as suggested by our previous work [6]. Such an approach is possible by inputting original source code to a preprocessor capable of producing ready-made updateable classes before applying the normal compilation step. However, a preprocessor approach requires the presence of the source code, which has a number of implications. First, there is no support for transforming third-party proprietary libraries, because only the bytecode is present. Second, as mentioned, our approach needs to modify Java system classes in order to enable forwarding from updated subclasses to the corresponding target. Although you could argue that the source code for Sun JDK is now open source, it would not be acceptable for most users to rely on a preprocessed version of the JDK itself. Furthermore, it would limit the applicability to open-source implementations of the Java library. In order to support updates of third-party modules and to keep up the portability of the approach, the transformations for those reasons need to work at the bytecode level. The Java instrumentation environment enables class transformations through Java agents. It does so by providing event hooks on every class load including classes dynamically generated at runtime. However, it does not come with any tools to support the actual bytecode transformations. Fortunately, a number of open-source tools are available. Binder et al. [28] provide a brief overview of these. For the transformations done by the Dynamic Correspondence Proxification framework, ASM is chosen because of its well-supported API, its small program size and its impressive performance compared with the other libraries [29]. In general, three kinds of transformations need to be done to make a component system dynamically updateable: 1. Classes declared in updateable components need to be prepared to comply with the In-Place Proxification technique. 2. Non-final system classes need to be able to forward requests to target objects. The transformations done to these classes are rather trivial and will not be further elaborated. 3. All classes must be manipulated to work with the enhanced class-inheritance-checking rules, the new == semantics and the likewise enhanced wait-notify semantics. In-Place Proxy classes. The forwarding functionality is equivalent to that of a before-advice in the Aspect Oriented Programming (AOP) world [30]. Fortunately, the ASM framework comes with handy templates for such commonly used transformations. For that reason it only takes some bytecode engineering effort to generate the correct code to inject. Furthermore, the fields for holding target references are generated. The most difficult step in converting a class into an inplace proxy class is how to support automatic construction of instances of these classes without specific knowledge of its constructors. This functionality is desired by the DCC and by the state migration process as mentioned earlier. Fortunately, it is possible to create a virtual no-argument constructor for any class at runtime using the java.reflect.ReflectionFactory class. The semantics of this constructor is especially designed to match the needs of serialization. Therefore, no initialization is done to objects created from it. Besides providing good performance, it does not execute a potential state altering code. This complies with the lazy state-migration approach used. Moreover class-level initializations must be obtained from the bytecode of a class to capture class-level initializations. It must be remembered that the framework assumes programmer-written

Copyright q

2009 John Wiley & Sons, Ltd.

J. Softw. Maint. Evol.: Res. Pract. 2009; 21:81–112 DOI: 10.1002/smr

DYNAMIC UPDATE OF JAVA APPLICATIONS

97

default values at the class level. However, in the class file format [31] such class-level initializations are automatically put into every constructor by the compiler. For that reason, the framework needs to parse at least one constructor in order to locate these assignments. Naively assigning every initialization of fields found in a constructor would introduce possible side-effects; hence, the main difficulty is to capture bytecode blocks assigning only class-level initializations. That is why the algorithm in Listing 4 has been designed. The definition of valid instructions is the set of all possible instructions in the Java instruction set [31] at the point of assigning a field at the class level. In addition to locating all class-level initializations, this algorithm might find more. This is because it is not possible to distinguish class-level assignments from local constructor assignments in those cases where only class-level information is used. However, if some fields are ‘wrongly’ written during creation, they will get overwritten once the state is moved; thus, such assignments will have no effect. Only new fields will keep the assigned value, as the state-migration process cannot find these fields in any former version. Listing 5 shows the changes made for input classes to enable assignment of newly introduced fields. A special init method is generated from the collected bytecode instructions found during the constructor parse phase. FOR each instruction in a constructor IF instruction is before the super call THEN IF instruction is a method instruction THEN set before super variable to false initialize the current instruction block END IF Parse next instruction END IF IF the instruction is a put field instruction THEN IF the field name is not parsed THEN add instruction to current instruction block add the current block to the list of all blocks clear the current block ELSE stop parsing the constructor END IF END IF IF instruction is a valid instruction THEN Add instruction to current block ELSE stop parsing the constructor END IF END FOR Listing 4. Pseudo code for parsing a constructor for class-level initializations. These are placed just after the mandatory super-call. As instructions are parsed, they are added to a representation of an instruction block. If no side-effect is found, the block is added to a list of blocks containing all valid class-level assignments.

Copyright q

2009 John Wiley & Sons, Ltd.

J. Softw. Maint. Evol.: Res. Pract. 2009; 21:81–112 DOI: 10.1002/smr

98

A. R. GREGERSEN AND B. N. JØRGENSEN

// Input class class A { private String name = ‘‘a’’; A(String name) { this .name = name; } }

// Transformed class class A { private String name = ‘‘a’’; A(String name) { this .name = name; } public void $$init() { super .$$init(); this .name = ‘‘a’’; } }

Listing 5. Result of the transformation done in order to assign values to new fields. The generated method $$init is invoked once on every object created through the aforementioned ReflectionFactory method.

Figure 6. The framework uses a single java agent jar to hook into the existing system. It intercepts class loading and delivers services that can be reached by the code injections in the enhanced classes.

4.2. Hooking into the component system The main part of integrating the framework into an existing component system is a generic Javaagent library. It transforms classes in accordance with the descriptions of the previous sections. Figure 6 shows the overall system setup. One point of concern is how an update is initiated and carried out in an existing component system. Typically, a component system has specific installation procedures where component dependencies are resolved. On that basis the setup of class loaders can be accomplished. The main point is the fact that these procedures are specific to each platform. In particular, there are different class-loading algorithms for delegating class loads. In order to preserve the semantics of a given platform, the framework integrates by manually implementing the basic update procedure that must be specific to each platform. This will typically consist of assembling the parts that install a component into the system and reusing this functionality. Furthermore, the framework assumes that class loads will always delegate to a version known to the initiating class loader. However, this might not be the case

Copyright q

2009 John Wiley & Sons, Ltd.

J. Softw. Maint. Evol.: Res. Pract. 2009; 21:81–112 DOI: 10.1002/smr

DYNAMIC UPDATE OF JAVA APPLICATIONS

99

when multiple representations become available. A simple pattern can be used when enhancing the class-loading algorithm to ensure this property. The main idea is to take a snapshot of the current target class loaders while installing or updating a component in the system. That would provide a reliable and quick access for initiating class loaders to search for classes in its individual snapshot. In the next section we will present an implementation of the framework that enhanced the current NetBeans reload feature. 5. CASE STUDY, ENHANCING THE NETBEANS RELOAD FEATURE This section summarizes and elaborates on the case study of integrating the framework into the NetBeans Platform as first described in [7]. The purpose of the case study is to answer two central questions regarding the approach. First, can the approach make the updates of components in existing component systems transparent to the application developer? Second, is the performance overhead imposed by the approach acceptable for application developers? We answer the first question by providing a detailed analysis of the implications of the existing reload feature of the NetBeans Platform that prevents smooth reload of application modules. We argue how our approach circumvents these drawbacks, thus enabling seamless runtime evolution of code and state. We answer the second question by providing quantitative performance measurements extracted directly from the case study. The case study originates from a feature of the current version of the NetBeans IDE, namely the NetBeans reload feature. This feature makes it possible for developers of NetBeans modules to test new module versions without stopping the target-application instance. The reload feature targets development of extensions to the NetBeans IDE itself by allowing on-the-fly reloads of modules in the current working instance of NetBeans. This functionality is especially useful for developers of the NetBeans community and third-party vendors writing plug-ins to NetBeans. In addition to that, the reload feature provides support for dynamic module reloads in a test instance of an arbitrary application that are written on top of the NetBeans Platform [1]. However, neither way the reload feature currently supports the transfer of state during a dynamic update. Owing to the fact that components in NetBeans are denominated modules, we use this term throughout this section to honor the NetBeans naming conventions. 5.1. The multiple dependencies problem The current NetBeans reload functionality takes place in two steps. These are: (1) unloading the running version of the implicated module and (2) deploying the new module with the running system. A naive approach would only reload the module that initiated the reload, say Module A. Doing so will directly result in the version-barrier problem if the reloaded module has any running dependents. Recall the situation as was illustrated in Figure 1, which corresponds to this scenario. The reload feature of NetBeans takes a logical step to overcome the version barrier problem. It reloads the initiating module and all its dependents in one atomic operation, thereby ensuring that shared types are always compatible and assignable during communication between modules. While this solves the version-barrier problem, it results in a loss of state for all reloaded modules. We return to the state-migration problem in Section 5.2. In fact, not only direct dependents must undergo the reload operation, reload also applies recursively to their dependents, which makes the approach

Copyright q

2009 John Wiley & Sons, Ltd.

J. Softw. Maint. Evol.: Res. Pract. 2009; 21:81–112 DOI: 10.1002/smr

100

A. R. GREGERSEN AND B. N. JØRGENSEN

slow. Moreover, programmers are required to write special install/uninstall code for every module in the chain of reloads in order to clean up and release system resources. This accumulates the risk of programmer errors from each reload step. Our In-Place Proxification technique improves this scenario as it only virtually updates dependent modules if it discovers the circumstances given in Section 3.5. The updated module is reached through the proxificated old one; thus, the update is transparent from the perspective of the clients. The main benefit is the fact that client modules do not need to execute uninstall code. Indeed, even though clients are class-loader migrated, no additional uninstall code needs to be executed, because all existing bindings are preserved. In fact, these bindings are important in the sense that they, after the update, provide the level of indirection needed to reach the migrated module. 5.2. The problem of program state and object registrations The most notable drawback of the current reload feature in NetBeans is that it does not automatically transfer state to the target module when a reload is applied. Of particular concern is the time for a programmer to perform a sequence of actions to reach a certain state so that testing of new code changes can proceed. Although NetBeans’ reload feature does not support state migration, objects of former modules can, in fact, still be present and reachable despite the atomic unloading of all dependents. Consider again Module A, which declares a dependency on Module B. Module B offers some kind of registration service, in which Module A registers objects programmatically. Now, reloading Module A will have no affect on Module B, which leaves B executing with potentially ‘invalid’ objects, if they are later retrieved from the registry by modules aware of the specific type version of the registered objects. Owing to the lost assignability, trying to cast the ‘old’ objects to the new type will throw class-cast exceptions; thus, a full restart of the application is likely to be needed. The problem can be partially fixed, if Module A explicitly tells Module B to remove all registrations in the uninstall hook of the former module. Another solution would suggest that Module B listens to changes in the set of enabled modules and removes any old instances. Although, solving this particular problem by leaving such issues to programmers is in general a bad idea, not only because the task of remembering to keep cleanup procedures up-to-date or writing listeners burdens the programmers, but also because this task is likely to be forgotten in the first place. Furthermore, cleanup and listener code have a negative impact on the overall comprehension of source code and the application’s performance. In addition, both these ‘solutions’ have the drawback of loosing state when removing objects from the system. If best practices for developing NetBeans modules were followed, the above scenarios would occur less frequently. The programmer of Module A would then use a declarative registration approach to register objects in a global registry called the Lookup [32]. Unfortunately, the approach can only effectively solve the registration problem in a static way. While the mechanism supports automatic startup registrations and their cleanup, there is no support for automatic cleanup of registrations done at runtime. In such scenarios we are back to asking programmers to do cleanup in uninstall hooks or to ask them to write listener code. The Dynamic Correspondence Proxification framework resolves the issue of having ‘old’ objects potentially executing after a reload. As explained, the In-Place Proxification technique makes these ‘old’ objects proxies; thus, the execution can continue in the new target component. An important observation is that the semantics of the reload feature will change from the dual operation of turning

Copyright q

2009 John Wiley & Sons, Ltd.

J. Softw. Maint. Evol.: Res. Pract. 2009; 21:81–112 DOI: 10.1002/smr

DYNAMIC UPDATE OF JAVA APPLICATIONS

101

off the current target followed by turning on the new target into a single update-operation, in which no unloading of existing modules takes place. The main benefit of this change of semantics is that all state is now preserved, so programmers do not have to perform a sequence of actions to test new code changes. In addition, this increases the throughput of testing code changes. Even in situations where a programmer finds and corrects a faulty state, bugs in the old state can be corrected by the bug fix just introduced in the new code. Of course, this requires programmer’s insight and knowledge of the erroneous state. It will take some extra functionality to test for invalid state and the corrections hereof, but experiments show that such situations are easily solved with what we call an in-between update. An in-between update is a temporary update of a component that has the purpose (other than potentially being a regular update) of correcting faulty state in the previous version. 5.3. Performance analysis Focusing on the execution-time overhead, this section presents the performance characteristics of our approach. The approach also poses a class loading overhead caused by the dynamic bytecode manipulations. However, statically modifying classes before they are made available as updates removes most of this overhead. We want to show that the following aforementioned characteristics of our approach hold good: 1. The level of indirection remains constant regardless of the number of consecutive updates. 2. The In-Place Proxification method forwarding overhead can be removed by updating running clients, thereby linking with the new target components directly. 3. The overall performance penalty is acceptable to application programmers. The first two are shown by providing results of a carefully designed micro benchmark. The third is shown by measurements of updating a realistic application for manipulating weather data. In general, the execution overhead of the approach consists of five subparts: 1. 2. 3. 4. 5.

method and constructor invocation penalty across component boundaries, increased object creation time after updating, setting and getting fields through code-generated getters and setters, enhanced type- and identity-checking overhead, state migration overhead on first access.

In most cases, the added type checks in category four only add a few nanoseconds. No further results for this case will be presented. The test setup corresponds to Figure 3, consisting of one API module and one client module. All measurements are carried out on an Intel core 2 duo 3.0 GHz with 3.5 GB of RAM running Windows XP. Micro benchmarking Java applications often leads to misleading results. For that reason, we used the guidelines given at IBM developerworks [33]. Moreover, we used a second setup to make sure that the results were reliable. Each number reported is the calculated average of 1000 samples of 1 000 000 invocations if not otherwise stated. Furthermore, we report on the standard deviation and the number of used samples. The used samples are found by eliminating outliers that are above 1.5∗interquartile range (IQR) over the third quartile and below 1.5∗IQR below the first quartile.

Copyright q

2009 John Wiley & Sons, Ltd.

J. Softw. Maint. Evol.: Res. Pract. 2009; 21:81–112 DOI: 10.1002/smr

102

A. R. GREGERSEN AND B. N. JØRGENSEN

Measurement reliability. In the measurements we have tried to eliminate as much noise as possible to obtain reliable results. First of all we have tuned the JVM server hotspot for throughput. This was done by following the Java performance tuning white paper [34]. Looking at the 99% confidence intervals of the measurements gives a good hint on the precision of the samples. For instance, the 99% confidence interval of the measurements of a method invocation with an updateable parameter after one update is [332.62; 332.76]. For the same case before the update, the 99% confidence interval is [7.033; 7.038]. Method invocation. The In-Place Proxification technique lets clients of former components communicate with the target mainly through reflective calls and correspondence lookup in its API methods. Therefore, the performance hit of a single method invocation across a component boundary is highly dependent on its signature. Obviously, a void return-type method with no formal parameters can be carried across the version barrier by a simple reflective call. On the other hand, method invocations containing one or more object arguments will need to ask the DCC for correct versions before invoking the reflective call, thereby creating a larger overhead. The first four rows of Table I show the impact of different formal parameter types. In the measurements of the raw overhead, we have chosen to report on the absolute overhead due to the fact that the method body does not influence the overhead at all. Thus, the percentile increase will depend on the amount of work done by the method body. We report on the relative penalty of updating a realistic application later in this section. The first three rows in Table I are basically standard reflection measurements besides a check for updateability of the non-updateable argument in row three. The relatively higher cost of moving primitive arguments across the version barrier is the result of the boxing and un-boxing operations required to get primitive values in and out of the object parameter array in a reflective call. The cost of moving an updateable object across the version barrier seems to be high compared with a conventional method invocation. However, it has to be placed in the right perspective before any conclusions can be made. First, recall that only API methods experience this overhead. A well-designed component consists of a small number of public API-methods, which hide the implementation details inside the component. Typically, the number of internal methods in a component exceeds the number of API methods. This fact is the cornerstone of the framework in the sense that even though it poses a relatively large overhead in cross-component communication, it outperforms any approach trying to update at the granularity of classes or objects, because most of the execution runs in non-modified methods inside components. The numbers in Table I show that the claimed support for keeping a constant indirection level holds as the values in column four and five show consistently equal numbers. Furthermore, this is supported by the calculation of the correlations between three consecutive updates of a method invocation with a primitive parameter type. The averages for those measurements are 92.32, 92.18 and 93.51 ns, respectively. This calculation shows correlations in the range from 0.981 to 0.993. Thus the numbers have almost equal linear characteristics. Together with very similar averages, we conclude that the level of indirection remains constant regardless of the number of updates. Likewise, the method forwarding penalty is removed after a client update. This is emphasized by the fact that we were not able to measure any real difference between column three and six with the given precision. Object creation. Creating objects of updateable types introduces an overhead consisting of the same before-and-after-update penalties with respect to its arguments as seen in the case of method

Copyright q

2009 John Wiley & Sons, Ltd.

J. Softw. Maint. Evol.: Res. Pract. 2009; 21:81–112 DOI: 10.1002/smr

Copyright q

2009 John Wiley & Sons, Ltd.

Set field

Get field

Method invocation Method invocation Method invocation Method invocation Object creation Get field

Type 14.7 ns  : 0.15, n : 913 92.1 ns  : 0.34, n : 947 35.8 ns  : 0.24, n : 974 332.7 ns  : 0.84, n : 926 127.2 ns  : 0.39, n : 946 2.0 ns  : 0.012, n : 931 161.6 ns  : 0.40, n : 962 430.4 ns  : 0.98, n : 969

5.7 ns  : 0.032, n : 935 5.7 ns  : 0.027, n : 915 6.4 ns  : 0.036, n : 918 7.0 ns  : 0.030, n : 940 8.8 ns  : 0.23, n : 974 2.0 ns  : 0.010, n : 915 2.0 ns  : 0.011, n : 920 6.6 ns  : 0.032, n : 942

No arguments

Primitive argument

Primitive type (unmodified) Primitive type (modified) Updateable object

Non updateable object argument Updateable object argument Updateable object

After update

Before update

Variant

431.0 ns  : 0.80, n : 979

160.4 ns  : 0.46, n : 946

2.0 ns  : 0.010, n : 929

127.4 ns  : 0.43, n : 965

331.7 ns  : 0.65, n : 933

35.8 ns  : 0.24, n : 945

92.2 ns  : 0.43, n : 949

14.8 ns  : 0.15, n : 925

After consecutive update

Table I. Performance evaluation of approach. The measurements are made in nanoseconds.

6.6 ns  : 0.026, n : 916

2.0 ns  : 0.010, n : 907

2.0 ns  : 0.0098, n : 921

8.8 ns  : 0.21, n : 965

7.0 ns  : 0.029, n : 929

6.4 ns  : 0.031, n : 899

5.7 ns  : 0.029, n : 928

5.7 ns  : 0.026, n : 908

After client update

DYNAMIC UPDATE OF JAVA APPLICATIONS

103

J. Softw. Maint. Evol.: Res. Pract. 2009; 21:81–112 DOI: 10.1002/smr

104

A. R. GREGERSEN AND B. N. JØRGENSEN

invocation. In addition to this overhead, the DCC is asked to put corresponding objects into the correct correspondence maps after an update. The test setup invoking an empty constructor to create updateable objects shows an average execution time before the update of 8.8 ns. The execution time for clients to invoke updated constructors is 127 ns. This number is only applicable to crosscomponent object creations. The cost for the new target component to create objects in its own namespace remains at 8.8 ns. Field access. Very often a field access happens internally, thus only few field accesses do forward in practice. For that reason the code will in most cases already be in the target context. Therefore, we stress that the performance overhead after updates for field access only applies for accessing visible fields directly across component boundaries. The main overhead with regard to field access comes from lazily moving the state on the first access as reported in rows nine and ten. Rows six to eight of Table I give some interesting insights into the generated getter/setter methods. Especially the optimization that allows clients to read values directly in a former version when the state is present shows up in row six. However, after performing a single set field the numbers drop to that of the forwarding penalty as shown by the seventh row of Table I. Lazy update costs. Table II shows the costs of lazily adapting to a new target component. As seen, the overhead of moving a single field value to a new target component is roughly 280 ns. This number is obtained for a primitive field type. The number for an updateable type value would correspond to adding the difference of the involved types from Table I (roughly 250 ns). In order to get consistent results, we tweaked the approach to not update the state fields involved; thus, for each operation the retrieval of state from a former version is made. In addition, we ensured that there were no hits in the correspondence map for the involved objects, which of course could have had

Table II. Overhead of first time accesses after several updates for 1000 samples of 100.000 invocations each. Type

1st update

Update target information Lazy update Set field first access Lazy update Get field first access

2nd update

3rd update

4th update

517.2 ns  : 2.20, n : 974

516.8 ns  : 2.19, n : 954

518.0 ns  : 2.00, n : 961

517.3 ns  : 2.23, n : 964

279.8 ns  : 2.66, n : 946

386.9 ns  : 4.01, n : 940

481.8 ns  : 3.52, n : 937

631.2 ns  : 2.98, n : 918

289.0 ns  : 4.01, n : 945

416.7.6 ns  : 3.63, n : 967

473.9 ns  : 2.13, n : 959

631.0 ns  : 2.47, n : 958

Table III. Performance measurements of the effects of updating a real world application. The table shows average execution time in milliseconds for 1000 samples of 50 invocations each. Without instrumentation

Before update

Overhead %

2.93 ms  : 0.015, n : 910

 : 0.043, n : 909

5.55 ms

89.4

Copyright q

2009 John Wiley & Sons, Ltd.

After update

Overhead %

5.61 ms

91.5

 : 2.35, n : 940

After consecutive update

Overhead %

5.60 ms

91.1

 : 2.66, n : 946

J. Softw. Maint. Evol.: Res. Pract. 2009; 21:81–112 DOI: 10.1002/smr

DYNAMIC UPDATE OF JAVA APPLICATIONS

105

an unrealistic positive effect on the cost measured. Although this number seems high, remember that it is a one-time overhead paid for individual fields. The linear increase in execution time observed for field accesses after consecutive updates comes from the setup of the test. In order for the test to take place in the first place, we intentionally updated the Boolean state fields wrong to cheat the process to think that the state was still not present in the target even though we explicitly moved it. The result of this ‘hack’ is that extra checks are needed to look for the state in the intermediate components. If the field values were moved at least once per update, the penalty for consecutive updates would be in the same range as with the first update. As seen, the largest individual overhead is for setting up the target information. The benchmark measured the costs of retrieving the correct target component, class and object. An overhead of 500 ns corresponds to the ability to prepare over 2 million In-Place Proxies in 1 s. The costs for target information maintenance could be avoided by updating client components. However, this operation does not come for free, because of the need to transfer all state to the new client component. Effect of updates on realistic setup. In order to better understand the effect of updating components, we here report on a realistic application. One of its use cases is to produce an image for showing statistics of weather data fetched from a database. For this use case it requires an image creator component that depends on a data component. Table III gives the performance figures from the experiment. To avoid any unrealistically good numbers, we ensured that the use case did not execute any type of I/O, GUI painting or database access. As seen the update-enabled application adds 89.4 % to the execution time. This overhead comes from adding In-Place Proxification checks, along with the enhanced type and equality checks. We consider this overhead to be very small given the additional gain in functionality. Likewise, Table IV shows a remarkably small difference in the overhead after updates. For this use case, it showed how well the update granularity fits to modern software development. Heavy calculations are almost always done internally, which means they have no other performance impact than those of the update-enabled application in the first place.

6. SOFTWARE DEVELOPMENT FOR DYNAMIC UPDATEABLE SYSTEMS Even though the Dynamic Correspondence Proxification framework delivers an almost transparent solution to the dynamic update of Java components, there are situations in which no general solution can be fully automated. In this section we report on those situations and show how programmers can take advantage of a few simple programming patterns and conventions to overcome the identified problems. 6.1. Best practices for programming for dynamic software updating One point of concern is when new fields are introduced in an updateable class. Although the framework automatically assigns programmer-defined class-level values, programmers might not assign them, resulting in possible null-pointer exceptions on the first usage. This problem corresponds to an extension to a database schema. In this case the programmer using this schema would take actions to assign meaningful values automatically or ask users on first usage of already present tuples. If this common practice is translated to handling new fields in Java classes, then this would indeed be

Copyright q

2009 John Wiley & Sons, Ltd.

J. Softw. Maint. Evol.: Res. Pract. 2009; 21:81–112 DOI: 10.1002/smr

Copyright q

2009 John Wiley & Sons, Ltd.

POLUS [8] DSU [9]

Procedural languages

Java redefine class [3] DUCS [35] JDRUMS 2 [13] Dynamic C++ Classes [19] Dynamic Java Classes [36] DUSC [14] Iguana/J [37] Dynamically evolvable C++ Classes [15] Our framework

Object-oriented languages

Patch generation

State transfer functions Version Adapters

(Only predefined changes)

Incompatible interface changes

(Partly)

(Partly)

Class inheritance changes

Flexibility

(partly)

(Partly)

Non-fixed time of Refactorings update

(Impact on clients) (Impact on clients) (Partly) on clients) Require predefined Compatible Incompatible Refactorings Non-fixed programming changes changes time patterns of update

Require predefined Compatible programming interface patterns changes

Transparency (A plus shows high transparency)

Table IV. Overview of the transparency and flexibility of existing dynamic update approaches.

106 A. R. GREGERSEN AND B. N. JØRGENSEN

J. Softw. Maint. Evol.: Res. Pract. 2009; 21:81–112 DOI: 10.1002/smr

DYNAMIC UPDATE OF JAVA APPLICATIONS

107

transparent to the development. Another point of concern is the situation where we have a running thread in a component about to be updated. In Section 3 we explained a state transfer mechanism that allowed updates even though threads were running inside the component itself. This was achieved by atomically switching to In-Place Proxy behavior of the whole component, thereby saving any result finishing after this change in the updated state space. However, in case of an infinite loop in a control application, this updating scheme could lead to possible inconsistencies due to the fact that this thread continues to run ‘old’ code throughout its lifetime. A simple but powerful design idiom can avoid this situation and effectively forward a running thread to a new target component. The basic idea is to externalize long-lived threads by abstracting the control behavior out of the algorithm itself, so that it can change between updates. The semantics of the java.lang.Runnable interface is extended by defining another interface LongLivedRunnable, which can then be assigned to a worker-thread class. This worker-thread class has exactly the same semantics as that of the java.lang.Thread , when the start method is invoked, except for the fact that it polls a condition and, if it is true, it executes the run method of the class. After completion, the worker thread again tests the condition and determines if it should run once more and so forth. The infinite loop that was inside the updateable class before it has been externalized, so that when the class has transformed into an In-Place Proxy, will forward both the run method and the method performing the condition check. If this simple design idiom is followed, it is possible to update long-lived threads to change algorithm. Turning it off after an update would likewise be an easy task. This could be accomplished through returning false in the condition-check method. Of course, the gained flexibility comes at a price of making possible internal classes bound by the compatibility rules of an API. Indeed, if one were to remove the class that held the conditional check and the actual code to be run, it would result in a MethodNotFoundException. In these situations an application breakdown can be avoided by catching the method not found as exception in the worker-thread class, and then terminating the thread in a controlled way. 7. RELATED WORK The most widely accessible dynamic update approach today is the implementation of the first stage of Dmitriev’s four-step plan [3] in the Java platform. It allows redefinition of method bodies at runtime, and it is transparent in the sense that programmers do not need to provide any additional information. Unfortunately, this approach has obvious limitations with respect to flexibility, as changes to the class interface are prohibited. The same unmodifiable interface restriction applies to the DUSC technique [14], dynamic C++ classes [19], Milazzo et al.’s architecture-based approach to dynamic adaptation [11], Iguana/J [37] and to the online reconfiguration approach by Soules et al. [38]. At the other end of the flexibility scale, systems such as Dynamic Software Updating (DSU) [9] and Powerful Live Updating System (POLUS) [8] support almost arbitrary changes to a system. Unfortunately, this comes at a price of lost programming transparency, because programmers must assist either when dynamic updates are prepared, state are transferred or special human operators must control the update. Both DSU and POLUS support very fine-grained changes, which make a fully automated solution unachievable. The POLUS update system shares the basic idea of letting multiple versions coexist at runtime. However, it differs in the sense that it uses state synchronization mechanisms to maintain up-to-date state in all versions. This is contradictory to the In-Place

Copyright q

2009 John Wiley & Sons, Ltd.

J. Softw. Maint. Evol.: Res. Pract. 2009; 21:81–112 DOI: 10.1002/smr

108

A. R. GREGERSEN AND B. N. JØRGENSEN

Proxification technique in which only one version contains reachable state. Although their approach is efficient for non-object-oriented languages, maintaining state in all versions seems to introduce too much overhead in a statically typed object-oriented language. In addition, DSU, POLUS as well as the approach in [39] target procedural languages. Programming transparency is likewise compromised in Duggan’s approach for type-based hotswapping [16] in the sense that ‘the semantics of hot swapping is based on programmer-defined version adapters between old and new version values’. In JDRUMS 2 [13], programmers are made to write special state transfer functions and determine safe update points. JDRUMS 2 supports changes to class inheritance by class renaming. Unfortunately, this is accomplished through a declarative update specification file that further diminishes transparency. In Oreizy et al.’s approach [17] an explicit architecture model needs to be deployed together with the system, which adds to the complexity of writing updateable applications. Some approaches suggest modifying a virtual machine to allow dynamic class replacement [13,36,37]. Malabarba et al. [36] propose Dynamic Java Classes in which a class can be replaced at runtime. Contrary to our approach, their update granularity is a single class. Owing to Java’s binary compatibility rules, this reduces the set of applicable updates, as any change to the class signature will cause binary incompatibility with dependents. Our approach updates dependents, thereby guaranteeing binary compatibility. Other approaches suggest new language constructs to support runtime adaptation [16,21,22,40]. The approaches proposed in [21,22] support dynamic type widening for a group of collaborating objects. Dynamic type widening allows changes to both the functionality and the type hierarchy of objects at runtime. Changes to a group of collaborating objects are defined by a set of language-level wrappers organized into an extension package. An extension package is superimposed onto a group of objects by casting an object reference to an object in the group into the type of the corresponding wrapper in the extension. This concept of type widening is referred to as constructive downcast. The notion of superimposition for updating the behavior of collaborating objects is developed further in [41]. In [40] type-safe delegation is used to support unanticipated adaptation of objects at runtime. The DUSC approach presented in [14] shares similarities with our work as it neither requires VM support nor needs to introduce new language constructs. The approach uses a hotswapping mechanism based on indirection via object wrappers. One benefit of this approach is that it minimizes the performance overhead to be negligible due to the fact that no reflection is needed. However, unlike our work, this comes at a price of less flexibility, as class signatures cannot change without losing program state. Bialek et al. [35] present an approach based on partitioning Java applications to make those partitions the updateable unit. Methods, fields and constructors can be added and method signatures can be changed using version adapters. Although the approach provides a flexible solution to dynamic software evolution, it burdens the programmer with the responsibility of writing version adapters and state transfer functions. Table IV compares existing approaches to ours with respect to what we have chosen to be the most significant criteria, namely transparency and flexibility. It focuses mainly on approaches targeting object-oriented languages. However, for a more complete overview it also includes two important contributions for procedural languages at the bottom. As it can be noticed from Table IV, our approach is the only one that supports both flexibility and transparency. However, a number of more powerful approaches providing complex refactoring support are shown in the table. Our

Copyright q

2009 John Wiley & Sons, Ltd.

J. Softw. Maint. Evol.: Res. Pract. 2009; 21:81–112 DOI: 10.1002/smr

DYNAMIC UPDATE OF JAVA APPLICATIONS

109

approach only has partly refactoring support. For example, a refactoring that changes the visible API of a component will result in an incompatible change and hence have an impact on client components as described in Section 3.4. Moreover, changes that alter the internal state structure of components will result in loss of the changed state, as our approach only supports automatically update of structure-preserving state updates. A non-structure-preserving state update can be handled by an intermediate update, which explicitly stored the affected state on every state update and later converted (by the new code) to fit the new state layout. In the area of state mapping between versions some work has been conducted, and Hicks’ Dynamic Software Updating framework [9] automatically generates a template with the obvious correspondent state mappings. However, the programmers are responsible for mapping more complex changes to the running state. Not only does this break programming transparency, it also introduces the possibility of errors due to the inability of programmers to correctly map the state. Vandewoude presents a more intelligent component-state-mapping system in [24] that can be used to detect a range of standard state refactorings. Again, there are cases in which the tool support for finding correct mappings comes out short, leaving the responsibility to the programmers. We believe that the time is not right for supporting state transfer that would require programmer intervention. However, we have suggested the use of in-between updates to correct corrupted state. Such updates are also good candidates for changing the state layout, though we stress that there is no built-in support for achieving such means, and it would have to be done by programmers. 8. CONCLUSION We have presented our framework supporting dynamic updates of Java applications built on a lightweight runtime system that facilitates communication across class-loader boundaries. Our framework provides significant advances with regard to the overall programming flexibility, while maintaining an exceptionally high level of language transparency. It is novel by allowing updates at any time during execution, yet it ensures thread-safe lazy state migration following standard concurrency semantics. Experiments obtained from integrating our framework with the NetBeans platform’s runtime system show a moderate overhead when objects are passed across the version barrier caused by the class-loader constraints of Java. The overall performance characteristics of the approach are thus expected to be very close to a non-updateable Java application, when components are updated together. ACKNOWLEDGEMENTS

We would like to thank the anonymous reviewers for their elaborate and constructive feedback. Thanks to Sun Microsystems Inc. for their support of this project, most especially Jesse Glick for the technical contributions concerning the NetBeans Platform. REFERENCES 1. Boudreau T, Tulach J, Wielenga G. Rich Client Programming: Plugging into the NetBeans(TM) Platform. Prentice-Hall: Englewood Cliffs NJ, 2007. ISBN-13: 978-0132354806.

Copyright q

2009 John Wiley & Sons, Ltd.

J. Softw. Maint. Evol.: Res. Pract. 2009; 21:81–112 DOI: 10.1002/smr

110

A. R. GREGERSEN AND B. N. JØRGENSEN

2. McAffer J, Lemieux J-M. Eclipse Rich Client Platform: Designing, Coding, and Packaging Java(TM) Applications. Addison-Wesley: Reading MA, 2005. ISBN-13: 978-0321334619. 3. Dmitriev M. Safe evolution of large and long-lived Java applications. PhD Thesis, Department of Computing Science, University of Glasgow, Glasgow G12 8QQ, Scotland, 2001. 4. Sato Y, Chiba S. Loosely-separated ‘Sister’ namespaces in Java. Proceedings of ECOOP’05 (Lecture Notes in Computer Science, vol. 3586). Springer: Berlin, 2005; 49–70. 5. Drossopoulou S, Wragg D, Eisenbach S. What is Java binary compatibility? Proceedings of OOPSLA’98. ACM Press: New York NY, 1998; 341–361. 6. Gregersen AR, Jørgensen BN. Extending eclipse RCP with dynamic update of active plug-ins. Journal of Object Technology 2007; 6(6):67–89. Available at: http://www.jot.fm/issues/issue 2007 07/article1. 7. Gregersen AR, Jørgensen BN. Module reload through dynamic update—The case of NetBeans. Proceedings of CSMR’08. IEEE Computer Society: Silver Spring MD, 2008; 23–32. 8. Chen H, Yu J, Chen R, Zang B, Yew P. POLUS: A POwerful Live Updating System. Proceedings ICSE’07. IEEE Computer Society: Silver Spring MD, 2007; 271–281. 9. Hicks M, Nettles S. Dynamic software updating. Proceedings of ACM Transactions on Programming Languages and Systems 2005; 27(6):1049–1096. 10. Previtali SC, Gross TR. Dynamic updating of software systems based on aspects. Proceedings of ICSM’06. IEEE Computer Society: Silver Spring MD, 2006; 83–92. 11. Milazzo M, Pappalardo G, Tramontana E, Ursino G. Handling run-time updates in distributed applications. Proceedings SAC’05. ACM Press: New York NY, 2005; 1375–1380. 12. Segal ME, Frieder O. On-the-fly program modification: Systems for dynamic updating. IEEE Software 1993; 10(2): 53–65. 13. Gustavson J. Dynamic Updating of Computer Programs—Proposed Improvements to the JDrums Updating System. Rise Publications, 2005. Available online at: http://www.ida.liu.se/∼jengu/jdrums/proposed improvements.pdf [1 February 2009]. 14. Orso A, Rao A, Harrold MJ. A technique for dynamic updating of Java software. Proceedings of ICSM’02. IEEE Computer Society: Silver Spring MD, 2002; 649–658. 15. Stanek J, Kothari S, Nguyen TN, Cruz-Neira C. Online software maintenance for mission-critical systems. Proceedings of ICSM’06. IEEE Computer Society: Silver Spring MD, 2006; 93–103. 16. Duggan D. Type-based hot swapping of running modules. Acta Informatica 2005; 41:181–220. DOI: 10.1007/s00236004-0151-1. 17. Oreizy P, Medvidovic N, Taylor RN. Architecture-based runtime software evolution. Proceedings of ICSE’98. IEEE Computer Society: Silver Spring MD, 1998; 177–186. 18. Segal ME. Online software upgrading: New research directions and practical considerations. Proceedings of COMPSAC’02. IEEE Computer Society: Silver Spring MD, 2002; 977–981. 19. Hjalmtysson G, Gray R. Dynamic C++ classes. Proceedings of the USENIX 1998 Annual Technical Conference, USENIX Association, 15–19 June 1998; 65–76. 20. B¨uchi M, Weck W. Generic wrappers. Proceedings of ECOOP 2000 (Lecture Notes in Computer Science, vol. 1850). Springer: Berlin, 2000; 201–225. 21. Jørgensen BN, Truyen E. Evolution of collective object behavior in presence of simultaneous client-specific views. Proceedings of the Ninth International Conference on Object-oriented Information Systems OOIS’03 (Lecture Notes in Computer Science, vol. 2817). Springer: Berlin, 2003; 18–32. 22. Jørgensen BN. Integration of independently developed components through aliased multi-object type widening. Journal of Object Technology 2004; 3(11):55–76. Special issue: OOPS track at SAC 2004, Nicosia/Cyprus. 23. Sun Microsystems Inc. Java reflection specification. Available at: http://java.sun.com/javase/6/docs/technotes/guides/ reflection/index.html [1 February 2009]. 24. Arabnia HR. Proceedings of the 2005 International Conference on Programming Languages and Compilers, PLC 2005, Las Vegas NV, U.S.A., 27–30 June. CSREA Press, 2005. ISBN: 1-932415-75-0. 25. Kramer J, Magee J. The evolving philosophers problem: Dynamic change management. IEEE Transactions on Software Engineering 1990; 16(11):1293–1306. 26. Vandewoude Y, Ebraert P, Berbers Y, D’Hondt T. Tranquillity: A low disruptive alternative to quiescence for ensuring safe dynamic updates. IEEE Transactions on Software Engineering 2007; 33(12):4–5. 27. Gregersen AR, Jørgensen BN. Towards dynamic plug-in replacement in Eclipse plug-in development. Proceedings of the 2007 OOPSLA Workshop on Eclipse Technology Exchange. ACM Press: New York NY, 41–45. DOI: 10.1145/ 1328279.1328288. 28. Binder W, Hulaas J, Moret P. Reengineering standard Java runtime systems through dynamic bytecode instrumentation. Proceedings of SCAM’07, IEEE Explore, 2007; 91–100. 29. Bruneton E, Lenglet R, Coupaye T. ASM: A code manipulation tool to implement adaptable systems, Grenoble France 2002. Available online at: http://asm.objectweb.org/ [1 February 2009].

Copyright q

2009 John Wiley & Sons, Ltd.

J. Softw. Maint. Evol.: Res. Pract. 2009; 21:81–112 DOI: 10.1002/smr

DYNAMIC UPDATE OF JAVA APPLICATIONS

111

30. Kiczales G, Lamping J, Menhdhekar A, Maeda C, Lopes C, Loingtier J-M, Irwin J. Aspect-oriented programming. Proceedings of ECOOP’97, vol. 1241, Aksit M, Matsuoka S (eds.). Springer: Berlin, 1997; 220–242. 31. Lindholm T, Yellin F. JavaTM Virtual Machine Specification (2nd edn). Prentice-Hall: Englewood Cliffs NJ, 1999. ISBN: 978-0201432947. 32. Boudreau T, Tulach J, Wielenga G. NetBeans Lookup. Rich Client Programming: Plugging into the NetBeans(TM) Platform, ch. 4-5. Prentice-Hall: Englewood Cliffs NJ, 2007. ISBN-13: 978-0132354806. Recent descriptions are available online at: http://openide.netbeans.org/lookup [1 February 2009]. 33. Boyer B. Robust Java benchmarking, part 2. Available online at: http://www.ibm.com/developerworks/java/library/jbenchmark2/index.html [1 February 2009]. 34. Sun Microsystems Inc. Java tuning white paper. Available at: http://java.sun.com/performance/reference/whitepapers/ tuning.html [1 February 2009]. 35. Bialek R, Jul E, Schneider J-G, Jin Y. Partitioning of Java applications to support dynamic updates. Proceedings of APSEC’04. IEEE Computer Society Press: Silver Spring MD, 2004; 616–623. 36. Malabarba S, Pandey R, Gragg J, Barr E, Barnes F. Runtime support for type-safe dynamic Java classes. Proceedings of ECOOP’00 (Lecture Notes in Computer Science, vol. 1850). Springer: Berlin, 2000; 337–361. 37. Redmond B, Cahill V. Supporting unanticipated dynamic adaption of application behavior. Proceedings of ECOOP’02 (Lecture Notes in Computer Science, vol. 2374), Magnusson B (ed.). Springer: Berlin, 2002; 205–230. 38. Soules C, Appavoo J, Hui K, Wisniewski RW, Silva DD, Ganger GR, Krieger O, Stumm M, Auslander M, Ostrowski M, Rosenburg B, Xenidis J. System support for online reconfiguration. Proceedings of the USENIX 2003, 141–154. 39. Neamtiu J, Hicks M, Stoyle G, Oriol M. Practical dynamic software updating for C. SIGPLAN Conference on Programming Language Design and Implementation, 2006; 72–83. 40. Kniesel G. Type-safe Delegation for Run-time Component Adaptation (Lecture Notes in Computer Science, vol. 1628). Springer: Berlin, 1999; 351–366. 41. Jørgensen BN. Superimposed delegation. Proceedings of IASTED International Conference on Software Engineering (SE 2006). ACTA Press: Calgary, Canada, 2006; 262–269.

AUTHORS’ BIOGRAPHIES

Allan Raundahl Gregersen is a PhD student at the Maersk Mc-Kinney Moller Institute, University of Southern Denmark. He received his MSc in computer systems engineering from the University of Southern Denmark in 2006. As part of his PhD he visited Sun Microsystems Laboratories in California in 2009. His research interests include dynamic updating, reflective middleware and software evolution.

Bo Nørregaard Jørgensen became an associate professor at the Maersk Mc-Kinney Institute, University of Southern Denmark in 2004. Since 2001 he has been an external associate professor at the Department of Information Science, Aarhus School of Business, University of Aarhus. He received his MSc in computer system engineering from University of Odense in 1997. Prior to his master studies, he worked as an HPCN consultant at the Odense steel shipyard in close collaboration with the Daimler Benz Aerospace and British Aerospace. He received his PhD from University of Southern Denmark in 2000. As part of his PhD studies, he visited Distrinet at KULeven, Belgium. His research interest is software evolution, including the fields of programming language technology for dynamic adaptation of object-oriented systems, reflective object-oriented middleware for distributed systems and software technology for component-based development. He

Copyright q

2009 John Wiley & Sons, Ltd.

J. Softw. Maint. Evol.: Res. Pract. 2009; 21:81–112 DOI: 10.1002/smr

112

A. R. GREGERSEN AND B. N. JØRGENSEN

has served as reviewer in international conferences including European Conference on Object Oriented Programming (ECOOP), ACM Symposium on Applied Computing (SAC) and the European Workshop on Modeling Autonomous Agents in a Multi-Agent World (MAAMAW). Currently, he is the manager of the Sun Center of Excellence in Software Technology for Eco-Social Sustainable Development (ecosoc.sdu.dk), a collaboration project with SUN Microsystems, California.

Copyright q

2009 John Wiley & Sons, Ltd.

J. Softw. Maint. Evol.: Res. Pract. 2009; 21:81–112 DOI: 10.1002/smr