Boa parte das linguagens não precisam ter conhecimento do tamanho do objeto para alocar na pilha. Java precisa, no momento, isso pode ser mudado. Se a intenção era falar especificamente de Java, está certo, mas não ficou claro. Todas as linguagens que alcançam um nível um pouco mais baixo, até mesmo C#, consegue alocar na stack sabendo o tamanho apenas no momento.
Boa, aqui estava querendo falar de "tempo de alocação", vou corrigir.
Há uma confusão sobre tipos por valor serem liberados quando saem de escopo. Existem tipos por valor que são liberados pelo GC, afinal se um objeto está no heap e ele contém objetos por valor dentro dele, é o GC que o libera, não é sobre o escopo
Boa, não tratei de casos em que os tipos de valor estão atrelados a um tipo de referência e tem um lifetime maior. Nesse caso realmente é o GC que trata de liberá-los.
No post tentei dissociar completamente os dois tipos de tipos, por brevidade.
Como adendo, a memória das strings fixas, como as usadas nos códigos exemplo, não ficam nem no heap, nem na stack, ficam em memória estática e duram por toda a aplicação, a alocação é feita já na carga do executável.
Boa, isso é algo que não tratei no artigo, infelizmente não dá pra entrar em todos os detalhes.
No C#, e outras linguagens, além da memória estática existe a string pool, que evita a alocação desnecessária de strings quando possível.
O tempo de vida do objeto no heap pode ser conhecido, o ideal até é que seja em tempo de compilação, e por sorte a maioria é
De fato, como exemplo tipos de referência que não tem a referência propagada e perdem escopo ao sair do local que o instanciou. Não queria dar a entender que objetos no heap nunca vão ter um lifetime definido.
Em geral os sistemas operacionais não impõem limite de tamanho para a stack de cada thread e todas podem ser configuradas durante a sua criação (a principal na carga). No Windows, até a última vez que eu vi, o padrão era 1MB, mas nada impede de criar com 4KB ou 4GB. É possível até ultrapassar esse limite.
Isso é outra coisa em que falhei em dizer que o limite padrão, mas como disse, pode ser configurado. Falhei também nos 2MB, para processos de 64bit está em 4MB.
Linguagens com GC são usadas em jogos de "tempo real", em locais de alta eficiência como o site Stack Overflow. Basta saber fazer.
Com certeza, o intuito aqui não é criticar o GC ou implementação específica de algum GC. Mas é evidente que, em busca de performance, é necessário facilitar seu trabalho
Usar algo que tem tamanho suficiente ajuda porque se precisar aumentar o tamanho do objeto, não pode, então a solução é criar um outro objeto maior em outro lugar e copiar os dados para esse novo lugar, e depois o GC terá que coletar o objeto velho. Isso pode gerar uma progressão geométrica e te destruir.
Assumo que isso ainda seja sobre o StringBuilder. É verdade, e o benefício dele começa a vir quando o custo de "gerar" a string final e seu processo é maior que o custo do SB em si.
Concordo que o post ficou muito específico para o C#, e não evidencio isso o suficiente, outra alteração que vou fazer.
Valeu por entrar mais em detalhes sobre as especificidades de stack e heap, e também sobre os tempos de execução e algoritmos do GC