Computer memory model
How a Java program sees memory: stack frames and the call stack, heap objects and their headers, references, garbage collection, and how CPU caches and cache lines make some access orders much faster than others.
Learning goals
- Explain where locals, objects and arrays live
- Estimate object sizes on a 64-bit HotSpot JVM
- Explain cache lines and why traversal order matters
Prerequisites
Stack frames
Every method call pushes a stack frame holding its parameters, local variables, a link to the caller and the return address. Primitives live directly in the frame; for objects the frame holds only the reference. When the method returns, its frame disappears and the memory is reused immediately.
Frames are cheap to create, but each one counts toward the depth limit of the thread's stack.
Factorial: calls, base case and return path
Interactive · edit the inputRecursion tree · 1 call
Call factorial(4). It is frame number 1 on the call stack.
1static long factorial(int n) {2if (n == 0) return 1; // base case3return n * factorial(n - 1); // recursive case4}
Live state
n
4
depth
1
Call stack (top first)
- factorial(4)
Execution trace (dry run table)Show 20 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L1 | n=4 depth=1 | Call factorial(4). It is frame number 1 on the call stack. |
| 2 | L2 | n=4 | n is not 0, so this call cannot answer yet. It needs factorial(3). |
| 3 | L3 | n=4 | Pause factorial(4) and call factorial(3). The pending multiplication by 4 waits on the stack. |
| 4 | L1 | n=3 depth=2 | Call factorial(3). It is frame number 2 on the call stack. |
| 5 | L2 | n=3 | n is not 0, so this call cannot answer yet. It needs factorial(2). |
| 6 | L3 | n=3 | Pause factorial(3) and call factorial(2). The pending multiplication by 3 waits on the stack. |
| 7 | L1 | n=2 depth=3 | Call factorial(2). It is frame number 3 on the call stack. |
| 8 | L2 | n=2 | n is not 0, so this call cannot answer yet. It needs factorial(1). |
| 9 | L3 | n=2 | Pause factorial(2) and call factorial(1). The pending multiplication by 2 waits on the stack. |
| 10 | L1 | n=1 depth=4 | Call factorial(1). It is frame number 4 on the call stack. |
| 11 | L2 | n=1 | n is not 0, so this call cannot answer yet. It needs factorial(0). |
| 12 | L3 | n=1 | Pause factorial(1) and call factorial(0). The pending multiplication by 1 waits on the stack. |
| 13 | L1 | n=0 depth=5 | Call factorial(0). It is frame number 5 on the call stack. |
| 14 | L2 | n=0 | Base case reached: no more recursion. Everything below this point is the return path. |
| 15 | L2 | n=0 return=1 | return 1. |
| 16 | L3 | n=1 return=1 | factorial(1) resumes: 1 × 1 = 1. Return it to the caller. |
| 17 | L3 | n=2 return=2 | factorial(2) resumes: 2 × 1 = 2. Return it to the caller. |
| 18 | L3 | n=3 return=6 | factorial(3) resumes: 3 × 2 = 6. Return it to the caller. |
| 19 | L3 | n=4 return=24 | factorial(4) resumes: 4 × 6 = 24. Return it to the caller. |
| 20 | L3 | return=24 | factorial(4) = 24, using 5 calls and a maximum stack depth of 5. |
Heap objects
Objects and arrays are allocated on the heap, shared by all threads, and live until the garbage collector finds them unreachable. Allocation is fast (a pointer bump in the young generation), but heap objects need headers, references to reach them and the collector's attention.
Locals that point to heap objects are references; the object may outlive the method if another reference keeps it.
1int x = 5; // primitive: stored in the stack frame2int[] a = new int[1000]; // reference in the frame, 1000 ints on the heap3List<Integer> xs = new ArrayList<>(); // reference + ArrayList object + backing Object[]
Call stack
The call stack is the chain of active frames, from main to the method running now. Recursion adds one frame per call, and the stack unwinds as calls return. A StackOverflowError means the chain exceeded the stack's size, usually because of a missing base case or a recursion deeper than about 10⁴ to 10⁵ frames.
Stack traces print this chain, innermost call first.
1static int depth(int n) { return depth(n + 1); } // no base case2// depth(0) -> StackOverflowError after roughly 10,000-20,000 frames with the default stack
Factorial: calls, base case and return path
Interactive · edit the inputRecursion tree · 1 call
Call factorial(4). It is frame number 1 on the call stack.
1static long factorial(int n) {2if (n == 0) return 1; // base case3return n * factorial(n - 1); // recursive case4}
Live state
n
4
depth
1
Call stack (top first)
- factorial(4)
Execution trace (dry run table)Show 20 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L1 | n=4 depth=1 | Call factorial(4). It is frame number 1 on the call stack. |
| 2 | L2 | n=4 | n is not 0, so this call cannot answer yet. It needs factorial(3). |
| 3 | L3 | n=4 | Pause factorial(4) and call factorial(3). The pending multiplication by 4 waits on the stack. |
| 4 | L1 | n=3 depth=2 | Call factorial(3). It is frame number 2 on the call stack. |
| 5 | L2 | n=3 | n is not 0, so this call cannot answer yet. It needs factorial(2). |
| 6 | L3 | n=3 | Pause factorial(3) and call factorial(2). The pending multiplication by 3 waits on the stack. |
| 7 | L1 | n=2 depth=3 | Call factorial(2). It is frame number 3 on the call stack. |
| 8 | L2 | n=2 | n is not 0, so this call cannot answer yet. It needs factorial(1). |
| 9 | L3 | n=2 | Pause factorial(2) and call factorial(1). The pending multiplication by 2 waits on the stack. |
| 10 | L1 | n=1 depth=4 | Call factorial(1). It is frame number 4 on the call stack. |
| 11 | L2 | n=1 | n is not 0, so this call cannot answer yet. It needs factorial(0). |
| 12 | L3 | n=1 | Pause factorial(1) and call factorial(0). The pending multiplication by 1 waits on the stack. |
| 13 | L1 | n=0 depth=5 | Call factorial(0). It is frame number 5 on the call stack. |
| 14 | L2 | n=0 | Base case reached: no more recursion. Everything below this point is the return path. |
| 15 | L2 | n=0 return=1 | return 1. |
| 16 | L3 | n=1 return=1 | factorial(1) resumes: 1 × 1 = 1. Return it to the caller. |
| 17 | L3 | n=2 return=2 | factorial(2) resumes: 2 × 1 = 2. Return it to the caller. |
| 18 | L3 | n=3 return=6 | factorial(3) resumes: 3 × 2 = 6. Return it to the caller. |
| 19 | L3 | n=4 return=24 | factorial(4) resumes: 4 × 6 = 24. Return it to the caller. |
| 20 | L3 | return=24 | factorial(4) = 24, using 5 calls and a maximum stack depth of 5. |
References
A reference is a small value (4 bytes with compressed object pointers) that identifies an object. Assigning a reference copies the address, not the object, which is why aliasing happens and why a List<Integer> costs a reference plus an Integer object per element.
Step through the alias, clone and deep-copy cases to see where references are shared.
Aliasing and copying arrays
Interactive · edit the inputvariables
@1 int[]
new int[] creates one object on the heap; the variable a holds only a reference to it.
1int[] a = { 10, 20, 30, 40 }; // options 0 and 1 use this 1D array2int[] b = a; // option 0: copies the reference only3int[] b = a.clone(); // option 1: copies the elements4int[][] a2 = { { 10, 20 }, { 30, 40 } }; // options 2 and 3 use this 2D array5int[][] b2 = a2.clone(); // option 2: new outer array, SAME row objects6int[][] b2 = Arrays.stream(a2).map(int[]::clone).toArray(int[][]::new); // option 3: rows copied too7b[idx] = v; // (b2[0][idx] = v for options 2 and 3)8System.out.println(Arrays.toString(a)); // does a see the change?
Live state
a.length
4
Execution trace (dry run table)Show 4 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L1 | a.length=4 | new int[] creates one object on the heap; the variable a holds only a reference to it. |
| 2 | L2 | same=true | b = a copies the reference, not the array. Both variables now point at the same object (@1). |
| 3 | L7 | idx=1 v=99 | b[1] = 99 writes into the object b points at. |
| 4 | L8 | a=[10, 99, 30, 40] b=[10, 99, 30, 40] | a shows the change too ([10, 99, 30, 40]), because a and b are two names for one object. |
Object headers
Every object starts with a header: a mark word (identity hash, lock and GC bits) and a class pointer, 12 bytes on a 64-bit HotSpot JVM with compressed class pointers. Arrays add a 4-byte length for a 16-byte header. Object sizes are rounded up to a multiple of 8 bytes, so tiny objects have large overhead.
That is why an int[] of one million ints takes about 4 MB while a List<Integer> of the same values takes about 20 MB.
| Object (typical 64-bit HotSpot) | Approx. size |
|---|---|
| new Object() | 16 bytes |
| Integer | 16 bytes |
| Long, Double | 24 bytes |
| int[n] | 16 + 4n, rounded up to 8 |
| long[n] | 16 + 8n |
| ArrayList<Integer> of n values | about 4n (references) + 16n (Integer objects) + header |
Garbage collection
The garbage collector reclaims heap objects that are no longer reachable from any live reference. Most objects die young, so the heap is split into young and old generations: minor collections clean the young generation quickly, and rarer major collections handle the old one. Collectors (G1, ZGC, Parallel) trade pause time against throughput.
For algorithm code the practical rule is to avoid allocating in hot loops. See the Java architecture chapter for the collectors in detail.
Cache locality
Memory is read in blocks. Accessing data close in memory (spatial locality) or reusing recently used data (temporal locality) is fast, because it is already in the cache. Walking a 2D array row by row reads consecutive addresses; walking column by column jumps a whole row each step.
The simulator below uses 4-int lines and a small LRU cache so you can count hits and misses for row-major versus column-major order.
Cache lines: row-major vs column-major traversal
Interactive · edit the inputmemory (row-major, 4 ints per cache line)
cache (2 lines, LRU)
A 4×8 matrix is stored row by row in one long run of memory. The CPU fetches memory in cache lines of 4 ints and keeps the 2 most recently used lines.
1int sumRowMajor(int[][] m) { // inner loop walks along a row2int sum = 0;3for (int r = 0; r < m.length; r++)4for (int c = 0; c < m[0].length; c++) sum += m[r][c];5return sum;6}7int sumColumnMajor(int[][] m) { // inner loop jumps down a column8int sum = 0;9for (int c = 0; c < m[0].length; c++)10for (int r = 0; r < m.length; r++) sum += m[r][c];11return sum;12}
Live state
rows
4
cols
8
lineSize
4
cache lines
2
Execution trace (dry run table)Show 34 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L3 | rows=4 cols=8 lineSize=4 cache lines=2 | A 4×8 matrix is stored row by row in one long run of memory. The CPU fetches memory in cache lines of 4 ints and keeps the 2 most recently used lines. |
| 2 | L4 | r=0 c=0 address=0 line=0 hits=0 misses=1 | m[0][0] is memory cell 0, on line 0, which is not cached: a miss. The CPU stalls while the whole line is loaded. |
| 3 | L4 | r=0 c=1 address=1 line=0 hits=1 misses=1 | m[0][1] is memory cell 1, on line 0, which is already in the cache: a hit (fast). |
| 4 | L4 | r=0 c=2 address=2 line=0 hits=2 misses=1 | m[0][2] is memory cell 2, on line 0, which is already in the cache: a hit (fast). |
| 5 | L4 | r=0 c=3 address=3 line=0 hits=3 misses=1 | m[0][3] is memory cell 3, on line 0, which is already in the cache: a hit (fast). |
| 6 | L4 | r=0 c=4 address=4 line=1 hits=3 misses=2 | m[0][4] is memory cell 4, on line 1, which is not cached: a miss. The CPU stalls while the whole line is loaded. |
| 7 | L4 | r=0 c=5 address=5 line=1 hits=4 misses=2 | m[0][5] is memory cell 5, on line 1, which is already in the cache: a hit (fast). |
| 8 | L4 | r=0 c=6 address=6 line=1 hits=5 misses=2 | m[0][6] is memory cell 6, on line 1, which is already in the cache: a hit (fast). |
| 9 | L4 | r=0 c=7 address=7 line=1 hits=6 misses=2 | m[0][7] is memory cell 7, on line 1, which is already in the cache: a hit (fast). |
| 10 | L4 | r=1 c=0 address=8 line=2 hits=6 misses=3 | m[1][0] is memory cell 8, on line 2, which is not cached: a miss. The CPU stalls while the whole line is loaded. |
| 11 | L4 | r=1 c=1 address=9 line=2 hits=7 misses=3 | m[1][1] is memory cell 9, on line 2, which is already in the cache: a hit (fast). |
| 12 | L4 | r=1 c=2 address=10 line=2 hits=8 misses=3 | m[1][2] is memory cell 10, on line 2, which is already in the cache: a hit (fast). |
| 13 | L4 | r=1 c=3 address=11 line=2 hits=9 misses=3 | m[1][3] is memory cell 11, on line 2, which is already in the cache: a hit (fast). |
| 14 | L4 | r=1 c=4 address=12 line=3 hits=9 misses=4 | m[1][4] is memory cell 12, on line 3, which is not cached: a miss. The CPU stalls while the whole line is loaded. |
| 15 | L4 | r=1 c=5 address=13 line=3 hits=10 misses=4 | m[1][5] is memory cell 13, on line 3, which is already in the cache: a hit (fast). |
| 16 | L4 | r=1 c=6 address=14 line=3 hits=11 misses=4 | m[1][6] is memory cell 14, on line 3, which is already in the cache: a hit (fast). |
| 17 | L4 | r=1 c=7 address=15 line=3 hits=12 misses=4 | m[1][7] is memory cell 15, on line 3, which is already in the cache: a hit (fast). |
| 18 | L4 | r=2 c=0 address=16 line=4 hits=12 misses=5 | m[2][0] is memory cell 16, on line 4, which is not cached: a miss. The CPU stalls while the whole line is loaded. |
| 19 | L4 | r=2 c=1 address=17 line=4 hits=13 misses=5 | m[2][1] is memory cell 17, on line 4, which is already in the cache: a hit (fast). |
| 20 | L4 | r=2 c=2 address=18 line=4 hits=14 misses=5 | m[2][2] is memory cell 18, on line 4, which is already in the cache: a hit (fast). |
| 21 | L4 | r=2 c=3 address=19 line=4 hits=15 misses=5 | m[2][3] is memory cell 19, on line 4, which is already in the cache: a hit (fast). |
| 22 | L4 | r=2 c=4 address=20 line=5 hits=15 misses=6 | m[2][4] is memory cell 20, on line 5, which is not cached: a miss. The CPU stalls while the whole line is loaded. |
| 23 | L4 | r=2 c=5 address=21 line=5 hits=16 misses=6 | m[2][5] is memory cell 21, on line 5, which is already in the cache: a hit (fast). |
| 24 | L4 | r=2 c=6 address=22 line=5 hits=17 misses=6 | m[2][6] is memory cell 22, on line 5, which is already in the cache: a hit (fast). |
| 25 | L4 | r=2 c=7 address=23 line=5 hits=18 misses=6 | m[2][7] is memory cell 23, on line 5, which is already in the cache: a hit (fast). |
| 26 | L4 | r=3 c=0 address=24 line=6 hits=18 misses=7 | m[3][0] is memory cell 24, on line 6, which is not cached: a miss. The CPU stalls while the whole line is loaded. |
| 27 | L4 | r=3 c=1 address=25 line=6 hits=19 misses=7 | m[3][1] is memory cell 25, on line 6, which is already in the cache: a hit (fast). |
| 28 | L4 | r=3 c=2 address=26 line=6 hits=20 misses=7 | m[3][2] is memory cell 26, on line 6, which is already in the cache: a hit (fast). |
| 29 | L4 | r=3 c=3 address=27 line=6 hits=21 misses=7 | m[3][3] is memory cell 27, on line 6, which is already in the cache: a hit (fast). |
| 30 | L4 | r=3 c=4 address=28 line=7 hits=21 misses=8 | m[3][4] is memory cell 28, on line 7, which is not cached: a miss. The CPU stalls while the whole line is loaded. |
| 31 | L4 | r=3 c=5 address=29 line=7 hits=22 misses=8 | m[3][5] is memory cell 29, on line 7, which is already in the cache: a hit (fast). |
| 32 | L4 | r=3 c=6 address=30 line=7 hits=23 misses=8 | m[3][6] is memory cell 30, on line 7, which is already in the cache: a hit (fast). |
| 33 | L4 | r=3 c=7 address=31 line=7 hits=24 misses=8 | m[3][7] is memory cell 31, on line 7, which is already in the cache: a hit (fast). |
| 34 | L5 | accesses=32 hits=24 misses=8 hit rate %=75 return=8 | 8 misss and 24 hits (75% hit rate). Both loops do identical arithmetic and are O(rows × cols), but walking along a row uses every int in a loaded line before moving on. Switch the order and compare. |
CPU caches
CPUs have small, fast caches between the cores and main memory: typically L1 (tens of KB, about 1 ns), L2 (hundreds of KB to a few MB, a few ns), and a shared L3 (many MB, ten-odd ns), against about 100 ns for main memory. A miss at every level costs roughly a hundred times a hit in L1.
Exact sizes and latencies vary by processor, but the ratios are what matter for algorithm design.
| Level | Typical size | Typical latency |
|---|---|---|
| L1 | 32-64 KB per core | about 1 ns |
| L2 | 256 KB-2 MB | about 3-5 ns |
| L3 | 8-64 MB shared | about 10-20 ns |
| Main memory | GBs | about 60-100 ns |
Cache lines
Data moves between memory and cache in cache lines, typically 64 bytes: 16 ints or 8 longs. Reading a[0] loads a[0..15] together, so the next 15 reads are free hits. A linked list node is a separate object, so each hop may load a new line.
Structure data so values used together sit next to each other; arrays of primitives are the most cache-friendly layout Java offers.
1int n = 4096; int[][] m = new int[n][n]; long s = 0;2for (int r = 0; r < n; r++) for (int c = 0; c < n; c++) s += m[r][c]; // row order: sequential access3for (int c = 0; c < n; c++) for (int r = 0; r < n; r++) s += m[r][c]; // column order: a new line each step
Cache lines: row-major vs column-major traversal
Interactive · edit the inputmemory (row-major, 4 ints per cache line)
cache (2 lines, LRU)
A 4×8 matrix is stored row by row in one long run of memory. The CPU fetches memory in cache lines of 4 ints and keeps the 2 most recently used lines.
1int sumRowMajor(int[][] m) { // inner loop walks along a row2int sum = 0;3for (int r = 0; r < m.length; r++)4for (int c = 0; c < m[0].length; c++) sum += m[r][c];5return sum;6}7int sumColumnMajor(int[][] m) { // inner loop jumps down a column8int sum = 0;9for (int c = 0; c < m[0].length; c++)10for (int r = 0; r < m.length; r++) sum += m[r][c];11return sum;12}
Live state
rows
4
cols
8
lineSize
4
cache lines
2
Execution trace (dry run table)Show 34 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L3 | rows=4 cols=8 lineSize=4 cache lines=2 | A 4×8 matrix is stored row by row in one long run of memory. The CPU fetches memory in cache lines of 4 ints and keeps the 2 most recently used lines. |
| 2 | L4 | r=0 c=0 address=0 line=0 hits=0 misses=1 | m[0][0] is memory cell 0, on line 0, which is not cached: a miss. The CPU stalls while the whole line is loaded. |
| 3 | L4 | r=0 c=1 address=1 line=0 hits=1 misses=1 | m[0][1] is memory cell 1, on line 0, which is already in the cache: a hit (fast). |
| 4 | L4 | r=0 c=2 address=2 line=0 hits=2 misses=1 | m[0][2] is memory cell 2, on line 0, which is already in the cache: a hit (fast). |
| 5 | L4 | r=0 c=3 address=3 line=0 hits=3 misses=1 | m[0][3] is memory cell 3, on line 0, which is already in the cache: a hit (fast). |
| 6 | L4 | r=0 c=4 address=4 line=1 hits=3 misses=2 | m[0][4] is memory cell 4, on line 1, which is not cached: a miss. The CPU stalls while the whole line is loaded. |
| 7 | L4 | r=0 c=5 address=5 line=1 hits=4 misses=2 | m[0][5] is memory cell 5, on line 1, which is already in the cache: a hit (fast). |
| 8 | L4 | r=0 c=6 address=6 line=1 hits=5 misses=2 | m[0][6] is memory cell 6, on line 1, which is already in the cache: a hit (fast). |
| 9 | L4 | r=0 c=7 address=7 line=1 hits=6 misses=2 | m[0][7] is memory cell 7, on line 1, which is already in the cache: a hit (fast). |
| 10 | L4 | r=1 c=0 address=8 line=2 hits=6 misses=3 | m[1][0] is memory cell 8, on line 2, which is not cached: a miss. The CPU stalls while the whole line is loaded. |
| 11 | L4 | r=1 c=1 address=9 line=2 hits=7 misses=3 | m[1][1] is memory cell 9, on line 2, which is already in the cache: a hit (fast). |
| 12 | L4 | r=1 c=2 address=10 line=2 hits=8 misses=3 | m[1][2] is memory cell 10, on line 2, which is already in the cache: a hit (fast). |
| 13 | L4 | r=1 c=3 address=11 line=2 hits=9 misses=3 | m[1][3] is memory cell 11, on line 2, which is already in the cache: a hit (fast). |
| 14 | L4 | r=1 c=4 address=12 line=3 hits=9 misses=4 | m[1][4] is memory cell 12, on line 3, which is not cached: a miss. The CPU stalls while the whole line is loaded. |
| 15 | L4 | r=1 c=5 address=13 line=3 hits=10 misses=4 | m[1][5] is memory cell 13, on line 3, which is already in the cache: a hit (fast). |
| 16 | L4 | r=1 c=6 address=14 line=3 hits=11 misses=4 | m[1][6] is memory cell 14, on line 3, which is already in the cache: a hit (fast). |
| 17 | L4 | r=1 c=7 address=15 line=3 hits=12 misses=4 | m[1][7] is memory cell 15, on line 3, which is already in the cache: a hit (fast). |
| 18 | L4 | r=2 c=0 address=16 line=4 hits=12 misses=5 | m[2][0] is memory cell 16, on line 4, which is not cached: a miss. The CPU stalls while the whole line is loaded. |
| 19 | L4 | r=2 c=1 address=17 line=4 hits=13 misses=5 | m[2][1] is memory cell 17, on line 4, which is already in the cache: a hit (fast). |
| 20 | L4 | r=2 c=2 address=18 line=4 hits=14 misses=5 | m[2][2] is memory cell 18, on line 4, which is already in the cache: a hit (fast). |
| 21 | L4 | r=2 c=3 address=19 line=4 hits=15 misses=5 | m[2][3] is memory cell 19, on line 4, which is already in the cache: a hit (fast). |
| 22 | L4 | r=2 c=4 address=20 line=5 hits=15 misses=6 | m[2][4] is memory cell 20, on line 5, which is not cached: a miss. The CPU stalls while the whole line is loaded. |
| 23 | L4 | r=2 c=5 address=21 line=5 hits=16 misses=6 | m[2][5] is memory cell 21, on line 5, which is already in the cache: a hit (fast). |
| 24 | L4 | r=2 c=6 address=22 line=5 hits=17 misses=6 | m[2][6] is memory cell 22, on line 5, which is already in the cache: a hit (fast). |
| 25 | L4 | r=2 c=7 address=23 line=5 hits=18 misses=6 | m[2][7] is memory cell 23, on line 5, which is already in the cache: a hit (fast). |
| 26 | L4 | r=3 c=0 address=24 line=6 hits=18 misses=7 | m[3][0] is memory cell 24, on line 6, which is not cached: a miss. The CPU stalls while the whole line is loaded. |
| 27 | L4 | r=3 c=1 address=25 line=6 hits=19 misses=7 | m[3][1] is memory cell 25, on line 6, which is already in the cache: a hit (fast). |
| 28 | L4 | r=3 c=2 address=26 line=6 hits=20 misses=7 | m[3][2] is memory cell 26, on line 6, which is already in the cache: a hit (fast). |
| 29 | L4 | r=3 c=3 address=27 line=6 hits=21 misses=7 | m[3][3] is memory cell 27, on line 6, which is already in the cache: a hit (fast). |
| 30 | L4 | r=3 c=4 address=28 line=7 hits=21 misses=8 | m[3][4] is memory cell 28, on line 7, which is not cached: a miss. The CPU stalls while the whole line is loaded. |
| 31 | L4 | r=3 c=5 address=29 line=7 hits=22 misses=8 | m[3][5] is memory cell 29, on line 7, which is already in the cache: a hit (fast). |
| 32 | L4 | r=3 c=6 address=30 line=7 hits=23 misses=8 | m[3][6] is memory cell 30, on line 7, which is already in the cache: a hit (fast). |
| 33 | L4 | r=3 c=7 address=31 line=7 hits=24 misses=8 | m[3][7] is memory cell 31, on line 7, which is already in the cache: a hit (fast). |
| 34 | L5 | accesses=32 hits=24 misses=8 hit rate %=75 return=8 | 8 misss and 24 hits (75% hit rate). Both loops do identical arithmetic and are O(rows × cols), but walking along a row uses every int in a loaded line before moving on. Switch the order and compare. |
Practice ladder
Now try it yourself
Easy
- Where are a local int and an array object stored?
- What is the approximate size of new Integer values in a list of 10⁶ elements?
Medium
- Explain what happens to the call stack during factorial(4)
- Estimate how many cache lines a sequential read of 10⁶ ints touches
Advanced
- Predict and then measure the difference between row-major and column-major traversal of a 4096 × 4096 int matrix
Key takeaways
- Primitives and references live in stack frames; objects and arrays live on the heap with a 12-16 byte header.
- Sequential access uses cache lines fully; scattered access wastes them.
- Garbage collection frees unreachable objects; fewer allocations mean less work for it.