Towards a dynamic-update-enabled JVM - ACM Digital Library

9 downloads 0 Views 594KB Size Report
Jul 7, 2009 - Maersk McKinney Moller Institute. University of Southern Denmark campusvej 55, 5230 Odense M. (+45) 65 50 74 20 [email protected].
Towards a Dynamic-Update-Enabled JVM Allan Raundahl Gregersen

Douglas Simon

Bo Nørregaard Jørgensen

Maersk McKinney Moller Institute University of Southern Denmark campusvej 55, 5230 Odense M (+45) 65 50 74 20

Sun Microsystems Laboratories 16 Network Circle Menlo Park, California, 94025

Maersk McKinney Moller Institute University of Southern Denmark campusvej 55, 5230 Odense M (+45) 65 50 35 45

[email protected]

[email protected]

[email protected]

ABSTRACT This paper advocates that de facto dynamic updates of Java applications will eventually require a dynamic-update-enabled Java virtual machine. We argue that our approach for dynamic updates of component-based Java applications complements the new module system planned for upcoming Java releases. We conclude that simple extensions to an existing JVM can bring full flexibility and transparency to dynamic updates in Java.

Categories and Subject Descriptors D.3.4 [Programming Languages]: Processors - Run-time environments

General Terms Design, Experimentation.

Keywords Dynamic software updates, Java, Virtual machine research.

1. INTRODUCTION Evolving critical applications at runtime are both problematic and costly, as they require high availability and reliability. Such applications must therefore be enabled for unanticipated runtime changes. Furthermore the development of software applications is becoming more expensive, as the complexity of software systems increases to take advantage of advances in computer technology. A transparent dynamic update approach of Java-based applications will help lower the costs of performing timeconsuming redeploys of systems during development. Our approach for dynamic updates of Java applications, takes advantage of common practices in modern component systems. The update granularity in our system equates with the logical building blocks of such systems; namely components. It is defined strictly at the application level; thereby it runs on a standard virtual machine (VM) without using language extensions. This approach extends component systems that assign one class loader per component, by defining one new class loader per update per Permission to make digital or hard copies of all or part of this work for personal or classroom use is granted without fee provided that copies are not made or distributed for profit or commercial advantage and that copies bear this notice and the full citation on the first page. To copy otherwise, or republish, to post on servers or to redistribute to lists, requires prior specific permission and/or a fee. RAM-SE '09, July 7, 2009 Genova, Italy Copyright © 2009 ACM 978-1-60558-548-2/09/07... $10.00"

component. In contrary to wrapper-based dynamic updating, i.e. DUSC [9], our approach allows co-existent distinct class versions in the running system by using a reflective mechanism and type correspondence handling. Therefore, the type hierarchy of classes can be successfully changed at runtime. In our previous work we have explained how our approach can be integrated with NetBeans [5] and Eclipse [4]. Our recent work focuses on the general applicability of the approach. Here, we have added support for lazy state transfer and arbitrary timing of updates. It uses a novel concept of letting already running application threads perform lazy updates and state transfer on a per method/field basis. Please refer to [3] for details. While our dynamic update approach is fully operational at the application level, there are scenarios in which support from the runtime would be beneficial. This paper advocates that de facto dynamic updates of Java applications will eventually require a dynamic-update-enabled VM. For that reason, we are investigating an implementation of our dynamic update approach in a VM. Furthermore, we explain how our approach complements the new Java module system, planned for Java 7, [12]. The remainder of this paper is organized as follows. Section 2 provides a brief overview of existing support for VM dynamic updates. Section 3 explains our approach supporting transparent and flexible dynamic updates of Java applications. After having explained the basics of our approach section 4 discusses how our approach would benefit from being implemented directly in a VM, and reason about why a VM-based approach will be necessary in order to hit mainstream support for dynamic updates. Then section 5 looks into how the upcoming Java module system will affect dependency handling and how java modules will provide a new de-facto standard for declaring meta-information in Java applications. Section 6 will document how our approach could be implemented in a VM, and discuss some of the important optimizations possible with a modern VM implementation. Finally we conclude in section 7.

2. STATE OF THE ART Several approaches for dynamic updates of Java classes have been integrated with a VM. Most commonly known is the work done by Dmitriev [1], which is now Java’s de facto support for runtime changes to classes. Redefinitions may change method bodies, the constant pool and attributes. A redefinition must not add, remove or rename fields or methods, change the signatures of methods, change modifiers, or change inheritance. In practice these restrictions render the re-definition capability of Java unsuitable for supporting most dynamic updates. Dmitriev identified non-

trivial issues with supporting more flexible updates to classes. For example, removing a static field of a class requires a complete analysis of all loaded classes in a VM to identify usages of the field. Altering the class type hierarchy would be even more troublesome, in the sense that any cast operation in the system would have to be located and checked for conflicts. In essence, his research provided great insight into the complexity of a true single-class redefinition mechanism at the VM level. Malarbarba et al. described how the VM could be extended to support type-safe dynamic redefinition of Java classes within the current binary compatibility framework of Java [8]. In their approach all class redefinitions are allowed, as long as they preserve dynamic and static type-safety and maintain binary compatibility with the pre-existing binaries of the running program. However, Performing dynamic redefinition at the level of a single Java class is too restrictive in scope to deal with larger program changes, as it reduces the changes that can be performed without causing binary compatibility conflicts. Part of a solution to successful dynamic updates is to support dynamic redefinition of a set of dependent classes at once. This set of classes, referred to as the change set, must enclose all classes for which binary compatibility conflicts exist. Hence a class subject to dynamic redefinition and its client classes (dependents) with which it has binary compatibility constraints is part of the change set. This is an extension of §13.3 in the Java language specification, [2]. Thus, the change set is the set of classes that must be redefined together for the dynamic class redefinition process to succeed. If it turns out during class reloading that the provided set of class files for a dynamic update differs from the change set, the dynamic class redefinition process must abort, leaving the program in the same state as it was before an update was attempted. By contrast, our approach eliminates the possibility of dynamic class redefinition failing by choosing a different way of composing change sets. To manage change sets we need a module system which keeps track of dependents, similar to what we find for components in Rich Client Component platforms. A recent VM-centric approach called JVOLVE, [11] has extended the Jikes research VM, [7], to support dynamic updates of Java classes. JVOLVE supports many changes to running Java classes. It is implemented by using the existing VM sate-point mechanism to determine if an update can be applied. It piggybacks on the garbage collector to find and allocate new instances conforming to the new class version. Unfortunately JVOLVE does not support changes to the type hierarchy of classes, which hinders common refactoring in object oriented programming. For instance introducing common superclasses or changes to the implements clause are restricted in JVOLVE. Furthermore, the fact that every object in the system needs to be examined and converted in one atomic operation is likely to cause unwanted bumps in application with a large number of live objects.

3. DYNAMIC UPDATES OF COMPONENT-BASED SYSTEMS In this section we explain how our approach [3] overcomes the binary compatibility constraints in dynamic updates of single classes, by raising the granularity to the level of components. This leads us to how our approach fits the new Java module system, which will be discussed in section 5. The main idea of our dynamic update approach called Javeleon, [6] is to weave in a component level switch into the public API of

components using bytecode manipulation. We have named this technique In-Place Proxification, because it injects code directly into original application classes. This is contrary to traditional wrapper-based techniques in which a special wrapper class completely replaces the original class. Post update-time this switch enables reflective forwarding to the most recent component available in the system. Figure 1 illustrates how the In-Place Proxification technique operates, by intercepting incoming requests. It shows the flow of an invocation from component A to component B. Every such incoming invocation is intercepted in the component API of B, which after bytecode transformations, done at load-time, contains the check needed to determine if updates have been applied to component B. If the model component has been updated it forwards the request to the most recent correspondent component version (B’ in figure 1).

Figure 1. In-Place Proxification technique. While figure 1 shows how incoming requests are forwarded it does not show what happens to request when multiple updates have been applied to the containing component. This situation is illustrated by figure 2.

Figure 2. Maintaining one level of indirection after subsequent updates. Figure 2 shows how Javeleon maintains one level of indirection after multiple component updates. The In-Place Proxification transformation to classes also contains a check to see whether the cached target component has been updated since the last request. Consider the scenario where only the update of component B has been applied. Then the evaluation of the “target updated?” box states no and the current knowledge of the target component is cached. The semantics at that point equates with that of figure 1. However, when the update to B’’ has been applied the cached instance of component B is no longer the most recent one. Therefore, it refreshes its view of the target component and forwards to B’’ instead. At that point any object and class in

component B’ can be garbage collected unless other clients holds references to those specific objects and classes. In order to reach an updated version of an incoming request standard reflection cannot be applied directly since the VM defines a separate set of classes for each version of a component because they each have unique class loaders. Hence, the type

signatures are distinct for each version. To overcome this problem, also known as the version barrier [10], our update approach maintains correspondence [3] between class and object versions in order to correctly locate and invoke the newly defined methods. Figure 3 shows how our approach translates incoming method invocations in order to successfully reach the most recent version of the corresponding method.

Figure 3. Method invocation across the version barrier is done reflectively by looking up corresponding objects in the map representing the version barrier between the two component versions. Figure 3 illustrates how corresponding objects are looked up dynamically. This is done in order to execute the new target method in accordance with its known formal parameters. However, looking up the target object (foo’) and target method (bar’) on every invocation is too expensive. Therefore these are cached in generated fields and maintained upon multiple updates similar to that of the target component in figure 2. As seen by figure 3 any parameter that crosses the version barrier must be looked up in the appropriate version barrier map unless the formal parameter types are system classes or primitive types. These are known to be compatible in any version, which is why they can go beyond the version barrier directly, illustrated by the grey lines. The result (res’) of such an invocation needs to be shipped back across the version barrier. This situation is shown by the arrows pointing left in figure 3. Therefore, the value returned to the original caller in component A is known to be compatible with the type namespace defined by component A’s classloader. It is likely that the return value (res’) is a newly created object. In those situations the lookup in the version barrier map fails, which triggers the creation process of a new correspondent In-Place Proxy object (res). This In-Place Proxy object will forward any request to the most recent version which in figure 3 is res’. Another important capability of Javeleon is that it supports state migration of running objects. It does so lazily by intercepting all field accesses. Any field access is transformed into a static interceptor method in the same class as the declared field. These generated methods perform the checks needed to reach the current

version of that particular state. The state is always maintained in one version only which is accomplished by migrating individual fields on-demand upon first usage after an update has been applied. Figure 4 shows this migration process.

Figure 4. Lazy state migration in Javeleon. Figure 4 illustrates the situation occurring after an update has been applied to component A. At some point when executing code in component A’ there is a getField instruction which is transformed into a generated getter method. This getter method then determines that this particular state is currently not present in this component version. Then it invokes a findState method declared in the Javeleon component. This method locates the state in

component A based on a lookup of currently live correspondent objects. It does so by using a consistently unique ID of correspondent objects, easily obtained in sequence of update time by simply associating a sequential incremented update ID along with component creation. When the state is located it triggers the convert method if the particular formal type of the field needs conversion. The conversion is then done in accordance with the semantics for bringing parameters across the version barrier as shown in figure 3. The choice of completely distinct types between versions has the big advantage, that the type hierarchy of classes can be changed at runtime, while running client classes continues to interact through the old type. Thus, clients are left truly oblivious of updates. Any updated component will link with the most recent version of its component dependencies, thereby allowing it to use the most recent types. Thus, our update approach provides the change flexibility that is required by common refactoring schemes. For example, with Javeleon the abstraction of common features can be moved from two distinct classes into a common super class through dynamic update of the classes, something that is not possible with any other existing technique that we know of.

corresponding to the original size. To handle such scenarios Javeleon introduces the notion of origin objects. The idea is that any field value is maintained in the origin instance (the one and only version created from a user-written constructor). This implies that any field-interceptor in system classes will delegate fieldaccesses to the origin object. While this scheme ensures consistent field-accesses in system classes it may not be the case for all usages of native user-written code. The withstanding challenge which cannot be easily dealt with at the application level is interception of native field-writes, without having to perform very expensive behind-the-scenes checks and redundant state maintenance, which is not desirable.

Summarizing the capabilities of Javeleon brings us to the point where any change possible with the standard off-line scheme consisting of serialize, halt, redeploy, restart and de-serialize can be effectuated at runtime.

4. WITHSTANDING CHALLENGES While Javeleon supports very flexible changes to a running system there are still situations where support from the runtime would greatly simplify and optimize the approach. In this section we will list these challenges and argue that these cannot be dealt with at the application layer. We have divided these challenges into two categories, namely; technicalities and performance challenges.

4.1 Technicalities The present implementation of Javeleon depends on bytecode transformations to make classes updateable. However, bytecode transformations of user-written application classes are not enough to support dynamic updates in all scenarios. Consider the scenario in figure 5 in which a user-written class is derived from a system class. The figure captures the essence of why any non-final class in the system library must be capable of forwarding requests to instances of updated sub-classes. Furthermore it illustrates why field interceptors must be injected to those system classes in order to allow consistent state access from different versions of objects in the system. Javeleon performs all necessary transformations in order to support these situations. However, consider what would happen if a method in a non-final system class had been declared as native. As an example consider the class JFrame from the swing library. A typical user interface (say MyJFrame) will extend this class. Now, if the MyJFrame class is later updated and the user decides to resize the frame, what happens is that the operating system informs the VM directly on this change and the VM will invoke the setIntField native method which updates the x and y fields in the JFrame object. The challenge with this situation is that when the redraw method of the original MyJFrame method is automatically invoked, due to the UI change, the In-Place Proxification code will forward the redraw method to the new instance. The new instance will not have seen the update to the x and y values and therefore only repaints the area of the frame

Figure 5. User-written class (MyList) that inherits from a system class. Invoking size() on the original instance (L) triggers the forwarding to the new instance (L’) in which the size field is not automatically migrated unless field interceptors are inserted into system classes. Another challenge regarding native methods is how to instrument these methods in order to forward the methods to the currently active instance of the object. Fortunately, since Java 6 it has been possible to setup a native prefix to let the VM lookup native methods by appending a prefix if the original lookup fails. Native method can then be intercepted by providing a wrapper method which injects the In-Place Proxification and treats the original native method as the method body. However, this functionality is only available to classes not already loaded in the VM. This poses a challenge for Javeleon as some bootstrap classes has already been loaded into the VM. Any such class cannot have native wrapper methods injected. Therefore Javeleon needs to statically transform those classes and startup a second VM instance in order to use these pre-instrumented classes. This is a cumbersome procedure, which will certainly benefit from VM-level support.

4.2 Performance challenges

6.1 The Maxine VM

Javeleon performs reasonably well. In [3] we reported on a roughly 90% runtime increase of a typical usage, both before and after updates. This overhead comes mostly from the In-Place Proxification code which needs to be injected at load-time. These injections are based on static system knowledge at load-time which is far from optimal. Given the current technologies of a modern VM, these transformations can be highly optimized and in many cases reduced to zero if they were made on-demand. Section 6 will report on a number of optimizations of this kind.

The Maxine VM, [15] is a meta-circular research VM, developed by Sun Microsystems. The Maxine project is best summarized as an exercise in productivity. It attempts to apply modern Java application programming methodology and tools to systems programming. Embodied in the Maxine development process is an extreme emphasis on leveraging meta-circularity in creating a compact, well-structured code base. To that end, the VM is written in Java and leverages most of the language features added in release 5 of the JDK. Maxine's software architecture is modular, with a dedicated replacement mechanism for many important components, e.g. compilers, GC, object layout, monitor implementation, startup sequence. This modularity exists primarily to facilitate experimentation by VM researchers and to support alternate configurations for varying runtime requirements.

5. NEXT GENERATION JAVA: MODULES ARE COMING As described in section 3 the update granularity of Javeleon is components. To that end the current and previous Java versions (Java 6 as of writing) do not fit well with the higher abstraction level of Javeleon. In order for Javeleon to be effective for any Java application a mechanism to partition standalone applications must be present. Otherwise, any update to the system will result in updating the whole system in one big chunk. Fortunately, one of the most prominent goals of the Java 7 release is to modularize Java. Java modules will leverage best practices from modern component systems and provide language level support for developing applications as independent building blocks. It will be possible to declare imports and exports of classes and packages, thereby enforcing well-defined communication through module boundaries. The new module support implies an important change to the runtime system, stating that “a class's runtime module is the pair of its module name and defining classloader”, [13].The fact that any class is a declared member of a certain module with a distinct classloader opens up the possibility to raise the granularity level of updateable units from classes to modules at the VM level. Promoting the granularity of updates to modules makes incompatible class changes easier to handle. Binary compatibility constraints between classes within modules are dealt with when compiling the new version. Binary compatibility constraints between modules are managed by recursively updating dependent modules.

6. VM INTEGRATION Integrating our approach into the Java VM itself allows us to solve certain remaining problems as well as dramatically reduce the performance overhead of In-Place Proxification. While tightly integrating our solution with a particular VM reduces its generality (i.e. it will not work with other unmodified VMs), we believe the benefits offered by this level of integration outweigh the disadvantages, especially in a server environment. Modifying the VM requires understanding the details of the VM implementation. This is not a trivial task for a modern, high performance Java VM. In addition, for a Java VM written in C or C++, it also means compromising our pure-Java nature of our current solution. For these reasons, we would ideally like to integrate with a VM that is written in Java and has a modular architecture that simplifies modifying any of its features. This remainder of this section first provides an overview of a VM that satisfies the above criteria of an ideal VM with which to integrate our approach. It then discusses the benefits of VM integration, while noting how an ideal VM simplifies the integration.

There are many other aspects of the Maxine VM that make it a compelling platform for VM research. First of all it integrates with the standard JDK, arguably the most mature and robust implementation of the Java core classes. Moreover, it has been (and continues to be) developed using modern Java IDEs. In addition, it comes with an advanced tool (called the Inspector) that is intimately aware of the VM’s internal data structures and greatly simplifies the task of debugging the VM.

6.2 Intercepting native code Recall the possibility that native code can update a Java field via JNI functions. Such behind-the-scenes accesses to Java fields cannot be easily intercepted from an application-level-based approach as previously mentioned. There is a risk that the native code may alter some state in an updated class/object, which is never moved to the most recent class/object, because it did not go through our field-write interceptors. However, if we can modify the implementation of JNI functions within the VM, such field updates can be intercepted so that they always access the currently active objects. In Maxine, modifying the implementation of the JNI functions is straightforward as they are expressed as Java methods. These methods are compiled with a slightly different ABI (application binary interface) by the compiler, which allows them to be directly called from C code. In addition, VM integration will provide the support for intercepting all native method invocations, even if they are declared in the bootstrap classes. One option is to inject wrapper methods at load-time enhancing the prefix capability to also work for these classes. Another option is to do it lazily on the first invocation of the native method, by inserting the special wrapper code before the actual native lookup takes place.

6.3 Utilizing de-optimization Maxine comes with a framework for performing bytecode transformations. As our In-Place Proxification technique utilizes bytecode transformations, we can simply re-use the existing infrastructure in Maxine for such transformations. Although it would be possible to perform the same load-time bytecode transformations as Javeleon does at the application level, this would not give the desired performance boost that we argued for in section 3. Instead in a modern VM we can utilize the dynamic mechanisms by which runtime knowledge of the code can be used to apply speculative optimizations. For instance the JIT (Just In time) compiler in HotSpot, [14] can perform speculative optimizations based on the set of currently loaded classes or profiled data gathered from the running program. If subsequent

execution invalidates the assumptions upon which the speculative optimizations were based, HotSpot de-optimizes the code containing the speculative optimizations, taking the execution back to the original state. With this powerful mechanism in hand, we can perform speculative optimizations related to dynamic class updating. First of all there need not be any instrumented code in the original classes upon startup. So instead of preparing all classes for possible updates we leave them untouched until updates are applied. This will basically eliminate most of the runtime overhead in the system we reported previously. Both updateable classes, which no longer contain In-Place Proxification code, as well as the non-final system classes will benefit from this approach. Once an update of a component is present, the VM will perform the necessary operations that cause old code to trigger deoptimization when next executed. After being de-optimized, the new code that takes into account the updates is enabled. This effect of this replacement is shown in Listing 1, which shows how a method declared in an updated class looks like before and after the de-optimization. // before update public Object getElemment(int index) { return localMap.get(index); } // after update public Object getElement(int index) { updateMetaInformation(); return GlobalMethodInvoker.invoke( $cachedMethod, $targetObject, index ); } Listing 1. De-optimization of method after update has been applied to declaring class. In the above listing the updateMetaInformation() only has effect on the first usage of this particular method. That is, the first usage per update of this method, because it needs to maintain an indirection level of one. It makes sure to cache the corresponding object and method in order to forward the invocation in accordance with Javeleon’s In-Place Proxification model. Not only does this de-optimization eliminate the runtime overhead before updates, it also eliminates the original application code afterwards, thereby freeing up memory compared to the application level approach. Recall how Javeleon also transforms non-final system classes to enable forwarding to the correct subclass as mentioned in section 3. During the update the JIT compiler will also need to flag any superclass in the system in which running subclasses may be updated in future execution. That is, for any loaded class contained in the original component the JIT compiler will flag the necessary super-class de-optimizations needed to introduce the checks to determine if the executing instance (“this” reference) is updated or not. The easiest solution in this case would be to insert this code (as well as the code actually needed to forward the invocation) once and for all in every method in the superclass. Doing so would result in an unnecessary runtime overhead for instances of non-updated subclasses or instances of the superclass itself. Instead, the JIT compiler will insert a very fast type check in the method dispatch of virtual methods and maintain two versions of the target code, one with and one without the extra

runtime checks. The semantics of this scenario would correspond to introduce an overridden method (dynamically at runtime) in the original subclass even if the method was declared final. Figure 6 illustrates this approach. Furthermore, this scenario reduces the need to de-optimize certain inlined virtual methods.

Figure 6. Optimized method dispatch based on runtime information about which classes have been updated. Only objects which are instances of the updated MyList class use the instrumented version of the size method. All other objects in the the type hierarchy continues to use the original full speed size method. Furthermore, Javeleon modifies invocations to certain type related operations such as the native isInstance method in the java.lang.Class class. To circumvent false negatives arising from such methods we perform these operations on classes and instances declared in the same component type space only. Refer to [3] for details. Fortunately, Maxine comes with Java implementations of otherwise native calls to the VM, so that enhancing those methods to handle comparison of multiple class versions for the same object is straightforward.

7. CONCLUSION We have argued that de facto dynamic updates of Java applications will eventually require a dynamic-update-enabled VM. Furthermore, we have discussed some limitations with providing single class level update mechanisms in the current VMs. We have found the new module support planned for Java 7 to be a good platform for increasing the update granularity from classes to components. We conclude that our approach integrates well with Maxine and expects that to be the case for any other production VM as well.

8. REFERENCES [1] Dmitriev M.: Towards Flexible and Safe Technology for Runtime Evolution of Java Language Applications, presented at Workshop on Engineering Complex Object-Oriented Systems for Evolution, Tampa Bay, Florida, USA, 2001. [2] Gosling J.,Joy B., Steele G., Bracha G.: The Java Language Specification, third edition, Addison Wesley 2005, ISBN-13: 978-0321246783. [3] Gregersen A. G., Jørgensen B.N.: Dynamic update of Java applications - balancing change flexibility vs. programming transparency, In Journal of Software Maintenance and Evolution: Research and Practice, John Wiley & Sons 2009, Volume 21, Issue 2, Pages 81-112.

[4] Gregersen A. R., Jørgensen B. N.: “Extending eclipse RCP with dynamic update of active plug-ins”, in Journal of Object Technology, vol. 6, no.6, July-August 2007, pp. 67-89. http://www.jot.fm/issues/issue_2007_07/article1

[10] Sato Y., Chiba S.: Loosely-separated "Sister" Namespaces in Java. In proceedings of ECOOP’05. Lecture Notes in Computer Science, Vol. 3586. Springer-Verlag, (2005) pp. 49-70.

[5] Gregersen A. R., Jørgensen, B. N.: Module Reload through Dynamic Update – the case of NetBeans. In Proceedings of CSMR 2008, pp. 23 - 32, IEEE Computer Society, 2008.

[11] Subramanian S., Hicks M., McKinley K.: Dynamic Software Updates: A VM-centric Approach. In proceedings of PLDI’09, ACM, to appear.

[6] Javeleon project page available at: www.javeloen.org

[12] Sun Microsystems inc., Project http://openjdk.java.net/projects/jigsaw/

[7] Jikes research VM, project information available at: http://jikesrvm.org/ [8] Malabarba S., Pandey R., Gragg J., Barr E., and Barnes F.: Runtime Support for Type-Safe Dynamic Java Classes. In proceedings of ECOOP’00. Lecture Notes in Computer Science, Vol. 1850. Springer-Verlag, (2000) pp. 337-361. [9] Orso A., Rao,A., Harrold M.J.: A Technique for Dynamic Updating of Java Software. In: proceedings of the IEEE International Conference on Software Maintenance (ICSM’02). (2002), pp. 649-658.

Jigsaw,

[13] Sun Microsystems inc., OpenJDK, Project Jigsaw VM changes for modules. http://openjdk.java.net/projects/jigsaw/doc/vm.html [14] Sun Microsystems inc. Java SE HotSpot http://java.sun.com/javase/technologies/hotspot/ [15] Sun Microsystems Laboratories, http://research.sun.com/projects/maxine

Maxine

VM. Project.

Suggest Documents