JSR-282 SI 100.2 -------------- Provide Support for Logical Melding of Scopes Last Updated: Oct. 18, 2006 -------------------------- Summary ------- Enhance the RTSJ to allow incremental accumulation of multiple ScopedMemory allocation contexts into a single scope nesting level in order to improve encapsulation, separation of concerns, and modular composition of independently developed real-time Java software components. Specification References ------------------------ Chapter 7. Memory Management Problem being addressed ----------------------- Software engineers who develop and maintain real-time Java code that make use of ScopedMemory allocation contexts are required to specify the sizes of ScopedMemory allocation contexts at the point that each ScopedMemory object is constructed. Because the RTSJ prohibits objects allocated in outer-nested allocation contexts from referring to objects residing in inner-nested scopes, it is often necessary to establish memory scopes that serve the cumulative needs of multiple independently developed software components. For example, implementation of a scope-allocated ClassLoader object is likely to make use of a single ScopedMemory allocation context to represent (1) the ClassLoader object itself; (2) several collection objects to represent the classes loaded by this class loader, including configuration information for classes that may be subsequently loaded by this Classloader; (3) the byte-code, JIT translation, and symbolic information associated with each of the loaded classes. Since the code that instantiates the shared ScopedMemory context must calculate the size of the shared scoped, the engineers who develop and maintain this code must have intimate knowledge of the internal implementation details for ClassLoader, Class, byte-code and JIT-compiled representations of classes, and the relevant collection class implementations. This violates fundamental software engineering principles of encapsulation and separation of concerns which are designed to enable modular composition of independently developed software components. Proposed solution ----------------- We propose to enhance the RTSJ API to allow independently developed components to take responsibility for allocating and sizing their own memory scopes. To support the requirement that multiple independently allocated scopes reside at the same scope level, we introduce the ability to instantiate a new scope at the same scope level as an existing scope. The proposed API is outlined below. For each of the constructors, the melder argument represents the meldable allocation context with which the newly constructed scope is to be melded. If this equals null, the newly constructed scope is (initially) independent. public abstract class MemoryExpansion { public MemoryExpansion(SizeEstimator size); // Query the size of the memory area, measured in bytes. public long size(); // Returns the number of bytes of memory that are currently consumed // by allocations within this LTMemoryExpansion area. public long memoryConsumed(); // Returns the amount of memory remaing to be allocated within this // LTMemoryExpansion area, measured in bytes. public long memoryRemaining(); // Melds this MemoryExpansion area with area meldwith, so that // both allocation regions are treated conceptually as if they // reside at the same scoping level. Thus, objects allocated within // this LTMemoryExpansion area can refer to objects residing in the // meldwith area, and vice versa. // // While melded, this memory area shares the same reference count as // meldwith. When that reference count reaches zero, objects // allocated within this memory area are finalized, if necessary, // and all of their memory is reclaimed. // // The regions remain melded // together until they are subsequently unmelded. Regions can only // be unmelded if the reference count on meldwith equals zero. // // In order to meld with a ScopedMemory object, this MemoryExpansion // object must be allocated within the same MemoryArea as the // ScopedMemory object. // // Throws IllegalStateException if this object is already melded // with a ScopedMemory region // // Throws IllegalArgumentException if this is LTMemoryExpansion and // meldwith is VTMemory, or if this is VTMemoryExpansion and // meldwith is LTMemory. // // Throws IllegalArgumentException if this MemoryExpansion object is // not allocated within the same MemoryArea that holds meldwith. // public void meldWith(ScopedMemory meldwith) throws IllegalStateException, IllegalArgumentException; // Returns the ScopedMemory object with which this MemoryExpansion // object has been melded, or null if this object is not currently // melded. public ScopedMemory meldedScope(); // Unmelds this MemoryExpansion region from the ScopedMemory object // with which it has been melded, assuming the reference count for // that ScopedMemory region equals zero. // // Throws IllegalStateException if this is not currently melded with // a ScopedMemory region, or if the reference count of the // corresponding ScopedMemory region does not equal zero. // public void unmeld() throws IllegalStateException; // Waits for the melded ScopedMemory region's reference count to equal // zero, and then unmelds this MemoryExpansion object from that // ScopedMemory region. There may be multiple threads waiting to // joinAndEnter() on the same ScopedMemory area. After the // reference count reaches zero and all of the region's memory has // been reclaimed, any pending joinAndUnmeld() requests are // processed before the system selects a thread to execute // joinAndEnter(). // // Throws IllegalStateException if this MemoryExpansion object // is not currently melded with an ScopedMemory area. // public void joinAndUnmeld() throws IllegalStateException; // Waits at longest until the time specified by the time argument // for the melded ScopedMemory region's reference count to equal // zero, and then unmelds this MemoryExpansion object from that // ScopedMemory region. If time is specified by a RelativeTime // argument, it describes a maximum wait duration measured from // when the request is issued. If time is specified by an // AbsoluteTime argument, it describes the absolute time beyond // which this method should not continue to wait. In both cases, // the timeout is rounded up to the nearest multiple of the // corresponding clock's resolution. // // There may be multiple threads waiting to // joinAndEnter() the same ScopedMemory area. After the reference // count reaches zero and all of the region's memory has been // reclaimed, any pending joinAndUnmeld() requests are processed // before the system selects a thread to execute joinAndEnter(). // // Throws InterruptedException if the timeout is reached before the // join() is detected. // // Throws IllegalStateException if this MemoryExpansion object // is not currently melded with a ScopedMemory area. // public void joinAndUnmeld(HighResolutionTime time) throws java.lang.InterruptedException; // Execute the run() method from the logic parameter using this // MemoryExpansion area as the current allocation context assuming // that the currently active allocation context is at the same scope // level as this MemoryExpansion area. // // Throws IllegalThreadStateException if the currently executing // thread is a Java Thread. // // Throws IllegalStateException if this MemoryExpansion area is // not currently melded to a ScopedMemory region, or if the // currently active allocation context does not correspond to the same // ScopedMemory area with which this object is melded. // // Throws IllegalArgumentException if the caller is a schedulable // object and logic equals null. // public void executeInExpansion(Runnable logic) throws IllegalThreadStateException, IllegalStateException, IllegalArgumentException; // Allocate an array of the given type within this MemoryExpansion // area. // // Throws IllegalArgumentException if number is less than zero, type // is null, or type is java.lang.Void.TYPE. // // Throws OutOfMemoryError if space within this MemoryExpansion area // has been exhausted. // // Throws IllegalThreadStateException if the caller is a Java // thread. // // Throws IllegalStateException if this MemoryExpansion area is not // currently melded with a ScopedMemory object. // // Throws InaccessibleAreaException if the ScopedMemory object with // which this MemoryExpansion is melded is not in the currently // execution schedulable object's scope stack. // public Object newArray(Class type, int number); // Allocate an object of this type within this MemoryExpansion area. // // Throws IllegalArgumentException if type is null. // // Throws IllegalAccessException if the class or initializer is // inaccessible. // // Throws ExceptionInInitializerError if an unexpected exception has // occured in a static initializer. // // Throws OutOfMemoryError if space within this MemoryExpansion area // has been exhausted. // // Throws IllegalThreadStateException if the caller is a Java // thread. // // Throws InstantiationException if the specified class object could // not be instantiated. Possible causes are: it is an interface, it is // abstract, it is an array, or an exception was thrown by the // constructor. // // Throws IllegalStateException if this MemoryExpansion area is not // currently melded with a ScopedMemory object. // // Throws InaccessibleAreaException if the ScopedMemory object with // which this MemoryExpansion is melded is not in the currently // execution schedulable object's scope stack. // public Object newInstance(Class type) throws IllegalAccessException, InstantiationException; // Allocate an object in this memory area. // // Throws IllegalArgumentException if c equals null, or the args // array does not contain the number of arguments required by c. A // null value of args is treated like an array of length 0. // // Throws IllegalAccessException if the class or initializer is // inaccessible under Java access control. // // Throws InstantiationException if the specified class object could // not be instantiated. Possible causes are: it is an interface, it is // abstract, it is an array, or an exception was thrown by the // constructor. // // Throws OutOfMemoryError if space within this MemoryExpansion area // has been exhausted. // // Throws IllegalThreadStateException if the caller is a Java // thread. // // Throws java.lang.reflect.InvocationTargetException if the // underlying constructor throws an exception. // // Throws IllegalStateException if this MemoryExpansion area is not // currently melded with a ScopedMemory object. // // Throws InaccessibleAreaException if the ScopedMemory object with // which this MemoryExpansion is melded is not in the currently // execution schedulable object's scope stack. // public Object newInstance(java.lang.reflect.Constructor c, Object args[]) throws IllegalAccessException, InstantiationException, InvocationTargetException; // Returns a reference to the MemoryExpansion object within which // object o was allocated, or null if object o was not allocated in // a MemoryExpansion area. If o resides within a MemoryExpansion // area, MemoryArea.getArea() returns a reference to the // ScopedMemory area with which the corresponding MemoryExpansion // region is currently melded. public static MemoryExpansion getMemoryArea(Object o); } // LTMemoryExpansion may only meld with LTMemory public class LTMemoryExpansion extends MemoryExpansion { public LTMemoryExpansion(SizeEstimator size); } // VTMemoryExpansion may only meld with VTMemory public class VTMemoryExpansion extends MemoryExpansion { public VTMemoryExpansion(SizeEstimator size); } Proposed Semantics: 1. For purposes of scope assignment checking, objects allocated within a MemoryExpansion area are treated as if they had been allocated within the ScopedMemory area with which the MemoryExpansion area is melded. 2. Memory allocation budgets are independently enforced for each MemoryExpansion area. 3. When the reference for the ScopedMemory decrements to zero, all of the melded MemoryExpansion areas are finalized and their memory is reclaimed in the same manner as the ScopedMemory area. Example usage: Code for the scope-allocated ClassLoader application discussed in the motivation section above might be structured as shown below: // This example assumes current allocation area is LTMemory ClassLoader cl = new ClassLoader(16, 16000) -------- public class ClassLoader { HashTable loaded_classes; HashTable assertion_status; LTMemoryExpansion auxiliary_buffer; // Instantiate a scope-compatible ClassLoader which has sufficient // memory to represent num_classes classes requiring no more than // auxiliary_buffer_size bytes for the representation of Strings, // byte code, and JIT code. public ClassLoader(int num_classes, int auxiliary_buffer_size) { // Not showing RTSJ gymnastics required to allocate z in a // private temporary buffer MemoryArea meld_candidate, candidate_scope; SizeEstimator z = new SizeEstimator(); // Here, we reserve space for two HashTables, but we don't // know what sort of internal allocations need to be performed // to implement each of those HashTables, so that additional // memory has to be reserved separately. z.reserve(HashTable.class, 2); z.reserve(Class.class, num_classes); z.reserveArray(auxiliary_buffer_size, Byte.TYPE); meld_candidate = MemoryArea.getMemoryArea(this); candidate_scope = MemoryArea.getMemoryArea(meld_candidate); // example ignores potential exception problems auxiliary_buffer = (LTMemoryExpansion) candidate_scope.newInstance(LTMemoryExpansion.class, [z]); z.meldWith(meld_candidate); loaded_classes = (HashTable) auxiliary_buffer.newInstance(HashTable.class, [num_classes, String.class, Class.class]); assertion_status = (HashTable) auxiliary_buffer.newInstance(HashTable.class, [num_classes, String.class, Boolean.class]); } ... } ------- class CloneHelper implements Runnable { Cloner client; CloneHelper(Cloner c) { client = c; } public void run() { client.result = client.source.clone(); } } class Cloner implements Runnable { LTMemoryExpansion expansion; Object source, result; CloneHelper cloneHelper; Cloner(LTMemoryExpansion x) { expansion = x; cloneHelper = (CloneHelper) x.newInstance(CloneHelper.class, [this]); } void setCopy(Object orig) { source = orig; } Object getCopy() { return result; } public void run() { expansion.executeInExpansion(cloneHelper); } } public class HashTable { Object hash_keys[]; Object hash_values[]; LTMemoryExpansion reentrant_scope; Cloner cloner; public HashTable(int max_entries, Class key_type, Class value_type) { // allocate this SizeEstimator in a private temporary scope // that is discarded upon return from this constructor. I'm not // showing the RTSJ gymnastics required to support this. MemoryArea meld_candidate, candidate_scope; SizeEstimator z = new SizeEstimator(); z.reserveArray(max_entries * 2); z.reserveArray(max_entries * 2); // Note: I'm assuming that the HashTable insertion routine will // make a scope-appropriate copy of each key and value inserted // into the HashTable. I'm also assuming that a shallow copy is // sufficient. A more general solution might require key_type // and value_type to implement an interface that allows analysis // or dynamic inquiry as to worst-case size required to make a // deep copy. z.reserve(key_type, max_entries); z.reserve(value_type, max_entries); z.reserve(Cloner.class, 1); z.reserve(CloneHelper.class, 1); meld_candidate = MemoryArea.getMemoryArea(this); candidate_scope = MemoryArea.getMemoryArea(meld_candidate); // example ignores potential exception problems reentrant_scope = (LTMemoryExpansion) candidate_scope.newInstance(LTMemoryExpansion.class, [z]); hash_values = (Object []) reentrant_scope.newArray(Object.class, 2 * max_entries); hash_keys = (Object []) reentrant_scope.newArray(Object.class, 2 * max_entries); cloner = (Cloner) reentrant_scope.newInstance(Cloner.class, [reentrant_scope]); } V put(K key, V value) { key = (K) makeClone(reentrant_scope, key); value = (V) makeClone(reentrant_scope, value); } // syncronize this method so we can make private use of cloner public synchronized Object makeClone(LTMemoryExpansion scope, Object orig) { cloner.setCopy(orig); scope.meldedScope().executeInArea(cloner); return cloner.getCopy(); } Discussion points ----------------- Rather than provide a library of explicit unmeld() operations, we could make unmelding implicit. At the time the corresponding ScopedMemory region's reference count decrements from one to zero, all of the melded ExpansionMemory areas are immediately unmelded. If we adopted this semantics, then we could allocate ExpansionMemory objects within the scope of the ScopedMemory region with which they are melded. We would need to think a bit about sequencing of the memory finalization activities. Compatibility issues -------------------- Existing code that makes use of existing ScopedMemory APIs are unaffected by this change.