Processes can be defined as two or more threads with disjoint memory spaces, where each group of threads (ThreadGroup or not) which shares a memory space constituting a process. Because Java lacks pointers, disjoint namespaces are sufficient to ensure disjoint memory spaces. (So long as object references are not shared between the threads we wish to distinguish as separate processes.) Java namespaces are managed by classloaders; therefore, disjoint classloaders generate processes.
While Java lacks pointers, and is therefore that much less vulnerable to the class of potential problems usually cited as motivation for establishing (conventional, native) processes in (conventional, native-code-oriented) operating systems, the single-process assumptions made in the Java class libraries and virtual machine spec lend us ample reason to desire multiple Java processes. (Where my desire is to pass the 'ultimate test': piping the output of one legacy program into the input of another.) The desire for multiple Java processes in a single JVM stems from two sources: first, the JJOS kernel's current lack of multiple native processes (that is, we can't take the 'easy way out' and run multiple instances of decaf); second, the potential space efficiency benefits. Most elegant implentations of multiple processes require shared-object IPC, if for nothing aside from the created process object. (Strictly, the process object returned to the creating function could be a message-passing stub, like RMI.)
The insight of disjoint classloaders generating processes combined with an examination of the JVM spec leads to the realization that we must 'clarify' it to allow more than a single primordial classloader [6]. A Java application can not (and must not) be able to distinguish between running in its own (conventional) JVM and in its own primordial classloader. That is, the from the application's point of view, a JVM is comprised of the statics in the classes returned by findSystemClass(). This clarification, then, gives us multiple processes in a single JVM, but quite inefficiently (sp?). Efficiency is gained by sharing class definitions [3], which are the portions of class invariant under writes to static variables. (From the implementation standpoint, a class definition is the everything in the native representation of class that isn't the value of the statics.) This sharing of class definitions -- that is, /not/ reloading the whole class library for each process -- must be done very carefully to ensure two things: type-safety [1] and transparency. (That is, legacy applications must behave identically in decaf with multiple processes as in decaf with a single process.)
A share may be attempted if and only if [4]
These conditions have two sources; the source for rule one is rather obvious. The source for rule two is the requirement that legacy programs which utilize custom classloaders must operate properly; that is, custom classloaders do not expect to share definitions unless they explicitly arrange for it, and it appears to be impossible to assure the correctness of any implicit sharing we may arrange.
With processes and an object-oriented language, the natural extension is object-oriented IPC. Message-based OO-IPC is already handled by the class libraries and conventional JVM spec. (See my earlier mail about Serializable and RMI.) Shared-memory (shared-object) OO-IPC requires special handling by the JVM. Without a spec, we (JOS) may define our own. The key realization for shared-memory OO-IPC is that the classloaders, being disjoint, do not share classes, and thus, the object can not be cast from one process to another. We solve this by allowing 'illegal' cast operations as follows:
Furthermore, we must assure that native methods are accessible from any classloader which would 'normally' (e.g. in another JVM process) have access. Currently, as JJOS / decaf has one library and one revision of it loadable at a time, we don't need to worry about this; everyone's native calls resolve the same way. Later, it seems that a model more-or-less identical to the one above described for sharing class definitions would be applicable to native methods; in fact, in may be possible to fold the location (e.g. instance of loaded library for this classloader) into the class definition, and have everything work out auto-magically. (I'll need to read up more on libraries for this.)
Once an object is shared, it must explicitly generate additional shares if it wishes to share objects. Otherwise, objects generated by that shared object for a process will remain specific to that process. Objects generated before the share will retain their ownership. This ought to take care of everything.
-_Quinn
[1] Amusingly enough, Java isn't type-safe anyway, though this may have been fixed in the 1.2 JDK. Likewise, Java's memory model -- which I'm mostly ignoring, in part because of this -- is horribly broken.
[2] Gilbert has some very interesting ideas about Java primordial classloaders that I must admit I'm avoiding because they'd confuse things (or me, anyway!) immensely before I get the processes thing nailed down.
[3] A later refinement (like the above footnote) may be gained by utilising a bytecode cache; see other emails for my thoughts on that. Like the previous footnote, I'm ignoring a perfectly good idea because it might confuse me.
[4] The phrase "if and only if" means I'm suggesting that when these conditions are met, you /must/ share the definition.
[5] Ryan suggests classfile/archive location and modification date. Gilbert suggested integration with the bytecode cache, and a straight call to memcmp(); I've commented on both of these before. However, it occured to me that location & date could utilise URIs instead of filenames, which would allow the primordial (java or otherwise) classloaders to do more interesting things and still share class definitions; one would expect the classloaders which implement remote URIs to use a bytecode cache.
[6] Ryan's rheise.os package demonstrating that a very large chunk of the functionality can be duplicated with smoke & mirrors rather than native support. :)
-- ToddMiller - 25 Apr 100
/* hey, look I found a y2k bug in wiki! */