Generally, the heap segment grows from lower address to higher address. A certain amount of heap space is allocated to the process and has to be managed by the process only.
When you extend your memory space, if space exists to extend it directly without moving old contents, it does so. Else, it copies the old contents to a new space which it finds, and then extends it from that new space. The old memory area is "freed" and can be used in further allocations if needed. However, the old space is NOT returned back to the kernel.
The amount of space that can be allocated by realloc() thus is limited only by the amount of heap space allocated to the process (size of heap segment).
Also, many Linux implementations make use of the sbrk() system call for changing process' segment sizes. You might want to have a look at this link to get a better understanding -
How are sbrk/brk implemented in Linux?