-
Notifications
You must be signed in to change notification settings - Fork 0
Isolate Implementation
How JNode achieves lightweight process isolation within a single address space using per-isolate static field tables and the @SharedStatics annotation.
JNode runs all code in a single address space but provides process-like isolation through isolates — lightweight execution contexts with separate static fields. This design allows the VM to avoid the overhead of virtual memory page tables while still providing strong isolation of process-local state. Communication between isolates uses rendezvous-style links.
| Class / File | Role |
|---|---|
core/src/core/org/jnode/vm/isolate/VmIsolate.java |
Core isolate implementation — lifecycle, creation, thread startup |
core/src/core/org/jnode/vm/isolate/IsolateThread.java |
Main thread entry point for a new isolate |
core/src/core/org/jnode/vm/isolate/VmStreamBindings.java |
Redirectable stdin/stdout/stderr per isolate |
core/src/core/org/jnode/vm/isolate/VmLink.java |
Inter-isolate communication channel |
core/src/core/org/jnode/vm/isolate/VmIsolateLocal.java |
Isolate-scoped storage (analogous to ThreadLocal) |
core/src/classlib/org/jnode/annotation/SharedStatics.java |
Annotation marking classes whose statics are shared across all isolates |
core/src/core/org/jnode/vm/memmgr/VmIsolatedStatics.java |
Per-isolate static field storage (131,072 slots) |
core/src/core/org/jnode/vm/memmgr/VmSharedStatics.java |
Global static field storage shared by all isolates |
core/src/core/org/jnode/vm/scheduler/VmThread.java |
Thread state including VmIsolatedStatics reference |
core/src/core/org/jnode/vm/scheduler/VmProcessor.java |
CPU core state including current isolate's statics table |
core/src/core/org/jnode/vm/classmgr/VmType.java |
Class metadata with separate indices into shared and isolated statics |
JNode maintains two static field tables per class:
┌─────────────────────────────────────────────────────────────┐
│ CLASS METADATA (VmType, in VmSharedStatics) │
│ • Class object reference │
│ • Method code │
│ • TIB, vtable │
├─────────────────────────────────────────────────────────────┤
│ STATIC FIELDS (per-isolate) │
│ ┌─────────────────────┐ ┌─────────────────────┐ │
│ │ Isolate A's copy │ │ Isolate B's copy │ │
│ │ (VmIsolatedStatics)│ │ (VmIsolatedStatics)│ │
│ └─────────────────────┘ └─────────────────────┘ │
├─────────────────────────────────────────────────────────────┤
│ STATIC FIELDS (shared, if @SharedStatics) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Single global copy (VmSharedStatics) │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
A singleton per VM holding data that must be shared across all isolates:
- Class objects (
VmTypeinstances) - Compiled method code
-
Stringconstants - Any class annotated with
@SharedStatics
Allocated via VmSharedStatics.allocClass(), allocMethodCode(), allocConstantStringField().
One instance per isolate, holding that isolate's copy of non-shared static fields. The table has 2^17 (131,072) slots. When a thread enters an isolate, VmThread.switchToIsolate() updates the processor's reference:
public final void switchToIsolate(VmIsolatedStatics isolatedStatics) {
this.isolatedStatics = isolatedStatics;
VmProcessor proc = VmMagic.currentProcessor();
if (proc.currentThread == this) {
proc.setIsolatedStatics(isolatedStatics);
}
}VmType stores two offsets — one into the shared table and one into the isolated table:
private final int staticsIndex; // VmSharedStatics for class metadata
private final int isolatedStaticsIndex; // VmIsolatedStatics for instance fieldsAt compile time, the L1/L2 compilers determine which table to use based on whether the class is marked @SharedStatics.
@Documented
@Retention(CLASS)
@Target(TYPE)
public @interface SharedStatics {
// Marker interface — signals VM that static fields must be shared
}The annotation is recognized during class loading in ClassDecoder:
new PragmaAnnotation(SharedStatics.class, TypePragmaFlags.SHAREDSTATICS)Additionally, several classes are hardcoded as implicitly shared (bypassing the annotation check):
java.util.TreeMaporg.apache.log4j.LogManager- Native VM classes (
NativeUnsafe,NativeString,NativeInetAddress)
When the JIT compiler generates code to access a static field:
if (!cls.isSharedStatics()) {
// Access from VmIsolatedStatics — per-isolate
offset = getIsolatedStaticsOffset(field);
} else {
// Access from VmSharedStatics — shared across all isolates
offset = getSharedStaticsOffset(field);
}This decision is baked into the generated machine code at compile time, so runtime overhead is minimal.
Over 244 classes are marked @SharedStatics. Key examples:
-
NativeUnsafe,NativeString,NativeInetAddress— native code helpers -
ResolverImpl,ARPNetworkLayer— network layer singletons -
JNodeToolkit,SwingToolkit— GUI toolkit internals -
VmIsolateinner classes:Stateenum,StaticData,IsolatedStaticData,AppSupport
new VmIsolate(...)
│
▼
CREATED ──────► isolate.start(links)
│ │
▼ ▼
TERMINATED ◄────── STARTING
▲ │
│ ▼
│ RUNNING (main() executes)
│ │
│ ▼
└────────────── EXITING ──► TERMINATED
Creation:
Isolate isolate = new VmIsolate(mainClass, args, bindings, properties);
isolate.start(links);Current isolate introspection:
VmIsolate.currentIsolate() // Returns the current isolate
VmIsolate.isRoot() // Checks if current is the root isolateIsolates communicate via VmLink — a rendezvous-style message channel:
VmLink link = VmLink.create();
// Send from isolate A
link.send(new StringLinkMessage("hello"));
// Receive in isolate B
StringLinkMessage msg = (StringLinkMessage) link.receive();Message types: DataLinkMessage (byte[]), StringLinkMessage, ObjectLinkMessage, LinkLinkMessage, StatusLinkMessage, IsolateLinkMessage.
- Not virtual memory isolation — Static fields are isolated in software, not by page tables. A bug in the statics indexing code could corrupt another isolate's fields.
-
Code is always shared — Method bytecode and compiled native code live in
VmSharedStatics. Only static field values are per-isolate. -
@SharedStaticsis all-or-nothing — A class either has all its static fields shared or all per-isolate. There is no per-field granularity. -
Root isolate — The kernel runs in the root isolate. Child isolates are created via
IsolateThreadwith the root isolate as parent. -
Isolate I/O is isolated —
VmStreamBindingsbinds stdin/stdout/stderr per-isolate. Switching isolates viaswitchToIsolate()does not automatically switch I/O streams — they are set explicitly during isolate startup. -
ThreadLocal vs IsolateLocal —
VmIsolateLocalis analogous toThreadLocalbut scoped to the entire isolate. All threads within an isolate see the same value.
-
Architecture — VM layer overview,
org.jnode.vm.isolatepackage - Memory-Management — How static tables are laid out in memory
- VM-Magic — Magic annotations and unsafe operations
-
Code-Conventions —
@SharedStaticsusage conventions - Glossary — Isolate, VmProcessor, VmThread definitions