- Prerequisites
- B2V1 1.2 JVM Architecture · B2V2 1.2 JVM Stack
- Covers
- Process · Thread · Lifecycle · Scheduling
- Related
- 1.2 Concurrency vs Parallelism · 1.3 Thread States · Volume V (virtual threads)
On this page
1Concept / Definition
A process is a running program with its own, isolated address space and operating-system resources; a JVM is one process. A thread is an independent flow of execution *inside* a process: it has its own stack, program counter and registers, but shares the process's heap, code and open resources with all other threads of that process. The operating system's scheduler decides which threads run on which CPU cores and when, moving each thread through a lifecycle of creation, readiness, running, blocking and termination.

2Why It Exists
Processes give isolation: a crash or memory corruption in one cannot damage another, and the OS can account and limit resources per process. Threads give cheap concurrency *within* one program: they can cooperate on shared data without copying it between address spaces. Java chose threads as its primary concurrency primitive so a single JVM can serve many requests, run background work and use all cores.

3Core Concepts
Process
- Address space — virtual memory mapping private to the process; other processes cannot read or write it (except via explicit shared memory).
- Resources — file descriptors, sockets, signal handlers, environment, user and limits (
ulimit, cgroups in containers). - Creation —
fork/exec(orCreateProcess); in Java,ProcessBuilder/Processstart child processes;ProcessHandleinspects them. - Communication — sockets, pipes, files, shared memory, message brokers — never plain object references.
- Failure isolation — the unit of restart in container platforms: one JVM process per container.
Thread
What a thread owns
| Private to each thread | Shared by all threads of the process |
|---|---|
| JVM stack: frames, local variables, operand stacks | heap: all objects and arrays |
| program counter, CPU registers | Metaspace / class metadata, code cache |
| native stack | static fields |
| ThreadLocal values, interrupt flag, name | open files, sockets, JVM-wide settings |
Because objects on the heap are shared, any object reachable from two threads can be accessed concurrently — the root of every visibility and race problem in Chapter 2. Local variables are thread-confined, which is why stateless methods are naturally thread-safe.
Platform and virtual threads

Costs

Lifecycle

At the Java level the same lifecycle is expressed by Thread.State (1.3): NEW before start(), RUNNABLE while ready or running (or in native I/O), BLOCKED/WAITING/TIMED_WAITING while waiting for locks, signals or time, and TERMINATED after run() completes. A Java thread object can be started only once; a finished thread cannot be restarted.
Scheduling

- Preemptive — the OS can suspend a running thread at any instruction to run another; Java code must never assume it runs without interruption between two statements.
- Time slices — runnable threads share cores in slices of microseconds to milliseconds; a switch saves and restores registers (a context switch, typically a few microseconds, plus cache pollution).
- Fairness — Linux's scheduler aims for fair CPU shares among runnable threads; Java thread priorities (1–10) are hints that are usually ignored on Linux.
- Blocking — threads waiting for I/O, locks or timers are not runnable and consume no CPU.
- Containers — CPU quotas (cgroups) limit how much CPU all threads together get; oversubscribed thread counts increase throttling and switching (Volume III 4.2).
4How It Works Internally
HotSpot creates each platform thread with pthread_create (Linux), reserving a native stack whose size comes from -Xss or the builder's stack size; Java frames and native frames share this stack. The JVM keeps a JavaThread structure per thread (thread-local allocation buffer, handles, safepoint state, interrupt flag) and registers the thread for GC root scanning and safepoints. Scheduling is entirely the OS's job; the JVM influences it only indirectly (parking, safepoints). Virtual threads add a user-mode scheduler (a ForkJoinPool of carrier threads) on top.
5APIs / Features / Syntax
// Processes
Process p = new ProcessBuilder("git", "rev-parse", "HEAD").redirectErrorStream(true).start();
String sha = new String(p.getInputStream().readAllBytes()).trim();
int exit = p.waitFor();
ProcessHandle self = ProcessHandle.current();
System.out.println(self.pid() + " " + self.info().command().orElse("?"));
// Threads (Java 21+ builder)
Thread worker = Thread.ofPlatform().name("importer-", 1).daemon(false).start(() -> importFiles());
Thread vt = Thread.ofVirtual().name("request-", 0).start(() -> handle(request));
// cores visible (container-aware)
System.out.println(Runtime.getRuntime().availableProcessors());# Threads of a JVM as the OS sees them
# LWP = OS thread id, PSR = core
$ ps -L -o pid,lwp,psr,stat,comm -p <pid> | head
$ top -H -p <pid> # CPU per thread
$ cat /proc/<pid>/status | grep -E "Threads|voluntary_ctxt|nonvoluntary"
$ jcmd <pid> Thread.print | grep -c '^"' # Java-visible threads6Practical Examples
| Situation | Process or thread? | Reason |
|---|---|---|
| Handle HTTP requests in a service | threads (or virtual threads) | share caches, pools, config cheaply |
| Run an untrusted script or external tool | process | isolation, resource limits, crash containment |
| Scale out a service | more processes (pods) | failure isolation, independent deployment |
| Parallel CPU work within one request | threads (ForkJoin, parallel streams) | shared data, low overhead |
| Isolate a memory-hungry batch | separate process/JVM | own heap, OOM cannot kill the service |
7Common Problems & Mistakes
- Creating a new platform thread per task at high rates — creation cost, memory, scheduler overload (use pools or virtual threads).
- Thinking threads are isolated — any shared object can be corrupted by concurrent access.
- Relying on thread priorities for correctness.
- Ignoring container CPU limits when choosing thread counts.
- Unnamed threads — impossible to read thread dumps.
8Performance & Production Considerations
- Monitor thread counts (
jvm_threads_live_threads) and context switches (/proc/<pid>/status). - Each platform thread's stack adds to native memory (NMT 'Thread' category).
- Too many runnable threads on few cores → throttling, latency; size pools by workload type (1.2, Chapter 8).
9Architecture & Design Considerations
Process boundaries are isolation and deployment boundaries (containers, services); thread boundaries are concurrency boundaries within a process. Architecture decides what must be isolated — tenants, untrusted code, memory-heavy jobs — into separate processes, and what can share memory safely within one. Within a process, design for shared-nothing or clearly owned shared state to minimise synchronisation.
10Interview Questions & Answers
What is the difference between a process and a thread?
A process is a running program with its own isolated virtual address space and its own operating-system resources, such as open files and sockets. One process cannot read another's memory without explicit inter-process communication. A thread is a flow of execution inside a process. Each thread has its own stack, program counter and registers, but all threads in a process share its heap and resources. A JVM is one process; the threads inside it, including application threads and GC threads, share the Java heap. This is why threads can communicate cheaply through shared objects, and also why they need synchronisation.
What do threads of the same JVM share, and what do they not?
Threads in the same JVM share everything in the process's memory and resources: the heap with all objects and arrays, static fields, class metadata, compiled code, and open files and sockets. Each thread has its own private state: its stack of method frames with local variables and operands, its program counter, its CPU registers when running, its ThreadLocal values and its interrupt flag. Local variables are therefore safe from other threads, but any object reachable from a shared reference is not. Knowing exactly what is shared is the starting point for reasoning about thread safety.
How are Java threads scheduled?
Platform threads map one-to-one onto operating-system threads, and the OS scheduler decides which run on which CPU cores. Scheduling is preemptive: the OS gives each runnable thread a time slice and can interrupt it at almost any instruction to run another thread, so Java code must never assume an order of execution between threads. Virtual threads, introduced in Java 21, are scheduled by the JVM instead. They run on a small pool of platform threads called carriers, and the JVM unmounts a virtual thread when it blocks on I/O, freeing the carrier for other work.
What does a context switch cost?
A context switch happens when a CPU core stops running one thread and starts another. The direct cost is saving the old thread's registers and state and loading the new one's, typically a few microseconds including the kernel transition. The indirect cost is often larger: the new thread finds CPU caches and translation lookaside buffers filled with the old thread's data, so it runs slowly until they warm up again. With thousands of runnable threads, a server can spend a significant share of CPU time switching instead of working, which lowers throughput and increases tail latency. Bounding thread counts limits that cost.
Why do thread priorities rarely matter in Java?
Thread priorities in Java are only hints to the operating system, and on Linux the JVM by default ignores them for normal threads, so setting a priority usually changes nothing. Even where they are applied, behaviour differs between operating systems and versions. Code that relies on priorities for correctness or fairness, for example to make one task finish before another, is therefore non-portable and fragile. Use explicit coordination instead: separate executors with their own sizing, queues with ordering, rate limiting, or structured concurrency. Virtual threads do not support priorities at all, which reinforces the point.
When would you use separate processes instead of threads in a Java system?
Use separate processes when you need isolation that threads cannot give. Untrusted or crash-prone code, such as native libraries or user-supplied plug-ins, can bring down the whole JVM if run in a thread, but not if it runs in its own process. Separate processes also get separate heaps, so one workload's garbage collection and memory leaks do not affect another, and separate resource limits, deployments and scaling, which is the basis of microservices and per-tenant isolation. The cost is communication: processes exchange data through IPC or the network, with serialisation and failure handling, instead of sharing objects directly.
How do thread costs influence the choice between thread pools and virtual threads?
Platform threads are expensive: each reserves a stack, takes tens of microseconds to create and adds scheduling overhead, so servers keep a bounded number of them in pools. With pooled threads, an I/O-bound service is limited by pool size, which leads to asynchronous or reactive code to avoid blocking. Virtual threads are cheap enough to create one per task, even millions, because blocking frees the underlying carrier. That makes simple thread-per-request code scale for I/O-bound workloads. CPU-bound work is still limited by cores, so it still belongs in pools sized to the CPU count. Choose by workload type, not fashion.