The principle that you can efficiently share as many read-only copies of an object as you want until you need to modify it. Then you need to have your own copy.
Several optimizations exist; taking into account that a copy need not be made if an object isn't shared (i.e. you can mutate it in place).
Create a ReferenceCount? to the object; anytime the object is shared, the refcount is incremented by one. Any time a reference to the object goes out of scope, the refcount is decremented by one. An attempt to write to an object with refcount > 1 causes CopyOnWrite, an attempt to write to an object with refcount == 1 causes in-place mutation (assuming the mutation can occur in the same amount of space).
Create a "shared bit" which is set whenever an object is shared, and never cleared by user code. If the shared bit is set, mutation causes CopyOnWrite.
In the "shared bit" case, if the GC discovers that an object with the shared bit set is only accessed once (i.e. it was shared and subsequently unshared), the GC can reset the shared bit.
This is typically used to efficiently implement fork() on modern Unices.
The system call fork() makes a copy of the current process, but the new
process doesn't actually get its own copy of a specific memory page until
either the original or the new process attempts to write to such a page.
Then, and only then, is the page copied.
Lots of OS memory management issues are handled using CopyOnWrite semantics; process creation is just one.CategoryLazyPattern