What happens when you run a program
From a double click to a process: loading, memory, the operating system's part, and where your code sits in it.
You type java Hello and something appears. Between those two events is a sequence worth knowing, because almost every "it works on my machine" and every "why is it using 2 GB" lives in it.
From a command to a process
The operating system is the thing in charge. When you ask it to run something, it:
- Finds the executable. This is where
PATHcomes in — the lesson on environment variables is about exactly this, and it is the reasonjavacan mean different versions on two machines. - Creates a process. A process is the OS's unit of "a running program": it gets its own memory, its own view of the world, and an identity (a PID) you can look up and kill.
- Loads the program into that memory, and starts it at its entry point.
- Schedules it. Your program does not own the CPU. The OS gives it slices, takes them away, and hands them to something else. On a machine running forty processes, all of them believe they are running continuously.
For Java there is a wrinkle worth being clear about: the process is the JVM, not your class. java Hello starts a JVM, and the JVM then loads and runs Hello. Your code is a guest inside a program that was already running.
That explains several things that otherwise look arbitrary — why startup is not instant, why ps shows java rather than your application's name, and why memory limits are set on the JVM and not on your code.
find it. The shell walks PATH in order and takes the FIRST java it finds. Everything after that point in the list is invisible — which is why two machines with the same command can run different versions.
make a process. The OS creates a process: its own memory, its own view of the world, and a PID you can look up and kill. Note whose process it is — the JVM's, not your class's.
load and start. The JVM starts, and only then loads Hello. Your code is a guest inside a program that was already running — which is why startup is not instant and why `ps` shows `java`.
schedule. Your program does not own the CPU. The OS grants slices and takes them away. On a machine running forty processes, all forty believe they are running continuously.
it ends. Four different endings, and they are not the same event: main returned, something threw, the OS killed it, or it is still there doing nothing. Only the first two leave you a message.
Memory, in the shape you will keep meeting
A process gets a block of memory and divides it into parts that all exist at once. Step through them below, watching the last column — what separates them is not what they hold but how long it lives:
All at once. One one process's memory, divided into 3 parts that all exist at the same time. Nothing here replaces anything else — that is the difference between this and a sequence of steps.
the stack. One per thread, and small — typically under a megabyte. It is fast because allocating is just moving a pointer, and freeing is moving it back. Nothing decides when a frame dies: returning is what kills it. This is why runaway recursion reports a stack error rather than running the machine out of memory.
the heap. Shared by every thread, and large. Anything made with new lives here, which means it outlives the method that made it — that is the whole reason it exists. Something has to decide when a piece is finished with, and in Java that something is the garbage collector.
the code. Your program, loaded and read-only. Worth drawing because it makes the shape honest: the instructions are in memory too, they simply never change and never need collecting. It is also where the JVM puts the native code it compiles for you while the program runs.
The stack holds what a method is doing right now: its local variables, and where to return when it finishes. It grows when you call a method and shrinks when you return. It is fast because it is just moving a pointer up and down, and it is limited — which is why runaway recursion produces an error about the stack rather than about memory in general.
The heap holds the things that outlive a single method. Anything you create with new lives there. It is large, and something has to decide when a piece of it is no longer needed. In Java that something is the garbage collector, and it is one of the JVM's main jobs.
You do not need the detail yet. What you need is the shape — regions whose contents die on different schedules — because it explains errors you will meet in your first week and performance questions you will meet for the rest of your career.
The thing you are actually building: a notional machine
Everything in this lesson and the two before it has been assembling one object in your head — a simplified machine that runs your code. It is not the real CPU, which is vastly more complicated, and it is not nothing. It is a working model, accurate enough to predict what your program will do.
Computing education has a name for it, coined by du Boulay and colleagues in 1981: a notional machine. Their paper was called The Black Box Inside the Glass Box — the idea being that a beginner needs the machine's behaviour made visible, not hidden, even though the visible version is a simplification.
That name is worth knowing because it turns a vague feeling into a checkable question. When you cannot predict what a piece of code will do, the gap is not usually in the syntax. It is that your notional machine is missing a part, and the useful move is to find out which one:
| You cannot predict… | The missing part |
|---|---|
| why this variable changed | that assignment destroys the old value |
| why the loop ran one time too many | where the condition is checked |
| why the object the caller sees changed | that a reference is copied, not the object |
| why this is slow after a restart | that code is compiled while it runs |
| why it worked yesterday | that the process reads an environment |
The literature is consistent that this is the hard part of learning to program — harder than syntax, which is why a reader who knows the keywords can still be unable to say what a program does. Syntax you can look up. A notional machine you have to build, and it is built by predicting and being wrong.
What "the program stopped" can mean
Programs end in several ways, and they are not the same event:
- It finished.
mainreturned; there is nothing left to do. This is the ordinary case and produces exit code 0. - It threw and nobody caught it. An error travelled up and out. You get a stack trace and a non-zero exit code.
- It was killed. The OS or an operator ended it. The program gets no say and may print nothing at all — which is why a service that "just vanished" with an empty log is usually a kill, and usually about memory.
- It is still running and doing nothing. Waiting on a network call that will never answer, or deadlocked. The hardest kind, because nothing has gone wrong in any way the machine can see.
Concurrency, in one paragraph
A process can do more than one thing at once, using threads. Each thread has its own stack (so each has its own idea of "what method am I in") and they all share the heap (so they can see each other's objects).
That sharing is where concurrency gets hard, and it is a whole phase of this course later. For now: when you read that a web server "handles many requests at once", it usually means many threads inside one process, all sharing one heap.
Misconceptions
- "My program runs continuously." It runs in slices the OS grants. On a busy machine your program may be paused thousands of times a second, and it cannot tell.
- "Memory is freed when I am done with a variable." On the stack, yes, when the method returns. On the heap, when nothing can reach the object — which may be much later, and is the collector's decision.
- "Exit code 0 means it worked." It means
mainreturned without an uncaught error. A program can finish successfully having done the wrong thing entirely.