Skip to content

Isolate Implementation

opencode-agent[bot] edited this page May 9, 2026 · 1 revision

Isolate Implementation

How JNode achieves lightweight process isolation within a single address space using per-isolate static field tables and the @SharedStatics annotation.

Overview

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.

Key Components

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

The Dual-Statics Architecture

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)                │    │
│  └─────────────────────────────────────────────────────┘    │
└─────────────────────────────────────────────────────────────┘

VmSharedStatics

A singleton per VM holding data that must be shared across all isolates:

  • Class objects (VmType instances)
  • Compiled method code
  • String constants
  • Any class annotated with @SharedStatics

Allocated via VmSharedStatics.allocClass(), allocMethodCode(), allocConstantStringField().

VmIsolatedStatics

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 Dual Indexing

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 fields

At compile time, the L1/L2 compilers determine which table to use based on whether the class is marked @SharedStatics.

How @SharedStatics Works

Annotation Definition

@Documented
@Retention(CLASS)
@Target(TYPE)
public @interface SharedStatics {
    // Marker interface — signals VM that static fields must be shared
}

Processing at Compile Time

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.TreeMap
  • org.apache.log4j.LogManager
  • Native VM classes (NativeUnsafe, NativeString, NativeInetAddress)

Runtime Effect

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.

Classes Using @SharedStatics

Over 244 classes are marked @SharedStatics. Key examples:

  • NativeUnsafe, NativeString, NativeInetAddress — native code helpers
  • ResolverImpl, ARPNetworkLayer — network layer singletons
  • JNodeToolkit, SwingToolkit — GUI toolkit internals
  • VmIsolate inner classes: State enum, StaticData, IsolatedStaticData, AppSupport

Isolate Lifecycle

    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 isolate

Inter-Isolate Communication: Links

Isolates 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.

Gotchas & Non-Obvious Behavior

  • 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.
  • @SharedStatics is 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 IsolateThread with the root isolate as parent.
  • Isolate I/O is isolatedVmStreamBindings binds stdin/stdout/stderr per-isolate. Switching isolates via switchToIsolate() does not automatically switch I/O streams — they are set explicitly during isolate startup.
  • ThreadLocal vs IsolateLocalVmIsolateLocal is analogous to ThreadLocal but scoped to the entire isolate. All threads within an isolate see the same value.

Related Pages

Clone this wiki locally