When you create a primitive type variable (int, bool, etc) as a local variable or method parameter, these are stored on the stack of the running thread.

When creating an object, such as Op op = new Op(2, 5, "*");, you create:

  • a reference which is stored on the stack
  • the object itself which is stored on the heap

Any primitives on objects are also stored on the heap. String literals such as “SOME_TEXT” are stored in a special reserved area of the heap.

Going back to the Op example, when calling this constructor method the two integers and a reference to the string are copied to the stack.

Implications for threads

Let’s consider what happens if objects are shared between threads.

public class MemoryDemoRunnable2 implements Runnable {
	private int counter = 0;
	
	public void run() {
		for (int i = 0; i < 100_000; i++) {
			counter++;
		}
		
		System.out.println(Thread.currentThread().getName()
			+ " " + counter);
	}
	
	public static void main(String[] args) {
		MemoryDemoRunnable2 runnable1 = new MemoryDemoRunnable2();
		Thread thread1 = new Thread(runnable1);
		Thread thread2 = new Thread(runnable1);
		thread1.start();
		thread2.start();
	}
}

The results for this may vary:

  • Thread-0 125520, Thread-1 129595
  • Thread-1 107462, Thread-0 137097
  • etc

Non-atomic operations & race conditions

The operation counter++ looks atomic but actually isn’t, instead it involves three separate operations of reading, incrementing and writing the value.

This creates race conditions where one thread could read a value, at which point it is interrupted by another thread which also reads that same value, they both increment the value and write it back in any order. Here we lose one increment as both threads read the original value at the same time.