Monitors or ObjectMonitors are a multithreading construct, they rely on the OS’ mutex lock primitive and when created are a part of an object, used to track its ownership (Which thread has the object’s lock). Because of this they are also known as intrinsic locks.
Monitors are part of a larger system that help control ownership of specific objects with the synchronize keyword.

They are not Java Objects, instead, ObjectMonitors are native C++ structs and are allocated in a portion of the heap that is managed directly by the JVM and is not GC’d.

They contain:

  • Native OS Mutex (the lock)
  • Pointer to the currently owning thread
  • Entry Set - Set of threads that are blocked, waiting to acquire the lock
  • Wait Set - Set of threads that called wait() in a synchronized block and thus temporarily given up the lock
  • Displaced Header - metadata of the object

When is a Monitor Created?

1 Monitor always belongs to 1 object, but not all objects have monitors at any given time. Monitors are only created when threads are in contention for ownership over a single object.
As for why a Monitor isn’t needed when only a single thread is locking an object, see how the object header efficiently handles such cases.

Example of when a Monitor is created for an object

class Main {
	public static void main(String[] args) {
		ThreadSafeCounter counter = new ThreadSafeCounter();
		
		// This thread calls this method on an instance of ThreadSafeCounter,
		// No monitor is created, as only 1 thread is contending for the lock
		new Thread(()-> counter.incr()).start();
 
		// This thread then calls a method on the same instance,
		// while the first thread is still running the incr() method.
		// Now a Monitor is created as 2 threads are contending for ownership of the same object
		new Thread(()-> counter.get()).start();
	}
}
 
class ThreadSafeCounter {
	private int count = 0;
 
	public synchronized int get(){
		return count;
	}
 
	public synchronized void incr() {
		// artificially delay this method
		try {  Thread.sleep(1000); } catch(InterruptedException _e) {}
		count++;
	}
}