The entirety of
fork() is implemented using mmap / copy on write.
This not only affects the heap, but also shared libraries, stack, BSS areas.
Which, incidentally, means that fork is a extremely lightweight operation, until the resulting 2 processes (parent and child) actually start writing to memory ranges. This feature is a major contributor to the lethality of fork-bombs - you end up with way too many processes before kernel gets overloaded with page replication and differentiation.
You'll be hard-pressed to find in a modern OS an example of an operation where kernel performs a hard copy (device drivers being the exception) - it's just far, far easier and more efficient to employ VM functionality.
execve() is essentially "please mmap the binary / ld.so / whatnot, followed by execute" - and the VM handles the actual loading of the process to RAM and execution. Local uninitialized variables end up being mmaped from a 'zero-page' - special read-only copy-on-write page containing zeroes, local initialized variables end up being mmaped (copy-on-write, again) from the binary file itself, etc.