Existem dois limites de memória diferentes. O limite de memória virtual e o limite de memória física.
Memória virtual
A memória virtual é limitada pelo tamanho e layout do espaço de endereço disponível. Geralmente, no início, está o código executável, os dados estáticos e o passado que aumentam o heap, enquanto no final a área é reservada pelo kernel, antes dele as bibliotecas e pilhas compartilhadas (que na maioria das plataformas crescem). Isso dá ao heap e empilhe espaço livre para crescer, sendo as outras áreas conhecidas na inicialização do processo e corrigidas.
A memória virtual livre não é inicialmente marcada como utilizável, mas é marcada durante a alocação. Embora o heap possa aumentar para toda a memória disponível, a maioria dos sistemas não aumenta automaticamente as pilhas. O limite padrão do IIRC para a pilha é 8MiB no Linux e 1MiB no Windows e pode ser alterado nos dois sistemas. A memória virtual também contém arquivos e hardware mapeados na memória.
Uma razão pela qual a pilha não pode ser aumentada automaticamente (arbitrariamente) é que os programas multiencadeados precisam de uma pilha separada para cada encadeamento, para que acabem atrapalhando um ao outro.
Em plataformas de 32 bits, a quantidade total de memória virtual é 4GiB, Linux e Windows normalmente reservam o último 1GiB para o kernel, fornecendo no máximo 3GiB de espaço de endereço. Existe uma versão especial do Linux que não reserva nada, oferecendo 4GiB completo. É útil no caso raro de grandes bancos de dados nos quais o último 1GiB salva o dia, mas para uso regular é um pouco mais lento devido às recargas adicionais da tabela de páginas.
Nas plataformas de 64 bits, a memória virtual é 64EiB e você não precisa pensar nisso.
Memória física
Normalmente, a memória física é alocada pelo sistema operacional quando o processo precisa acessá-la. Quanta memória física um processo está usando é um número muito nebuloso, porque alguma memória é compartilhada entre processos (o código, bibliotecas compartilhadas e outros arquivos mapeados), os dados dos arquivos são carregados na memória sob demanda e descartados quando há falta de memória e A memória "anônima" (aquela não suportada por arquivos) pode ser trocada.
No Linux, o que acontece quando você fica sem memória física depende da vm.overcommit_memory
configuração do sistema. O padrão é confirmar demais. Quando você solicita que o sistema aloque memória, ele fornece algumas informações, mas apenas aloca a memória virtual. Quando você realmente acessa a memória, ele tenta usar alguma memória física, descartando dados que podem ser relidos ou trocando as coisas conforme necessário. Se achar que não pode liberar nada, simplesmente removerá o processo da existência (não há como reagir, porque essa reação poderia exigir mais memória e isso levaria a um loop sem fim).
É assim que os processos morrem no Android (que também é Linux). A lógica foi aprimorada com a lógica de qual processo remover da existência com base no que o processo está fazendo e na sua idade. Então, os processos do Android simplesmente param de fazer qualquer coisa, mas ficam em segundo plano e o "esgotador de memória" os mata quando precisa de memória para novos.