Áreas de memória no Delphi

Documentação: http://docwiki.embarcadero.com/RADStudio/Tokyo/en/Memory_Management

Uma aplicação em Delphi possui basicamente 3 áreas de memória com a qual pode trabalhar: Memória Global ou “segmento de dados”, Stack e Heap. Cada qual possui sua utilizada, seu objetivo e sua particularidade.

Para trabalhar com o Windows, o Delphi utiliza uma versão modificada do FastMM como seu gerenciador de memória. Com o surgimento do FireMonkey, outras plataformas além do Windows foram integradas ao produto. Assim, um novo gerenciador de memória foi adicionado para as novas plataformas (MacOS, IOS, Android e Linux) chamado POSIX.

A documentação da Embarcadero quanto a esse assunto é um pouco decepicionante, porque data de um tempo onde apenas havia o suporte para Windows 32 bits.

Memória Global

Armazena os valores de todas as variáveis globais estáticas. É alocada no momento da criação da aplicação e desalocado no momento do seu término. O seu conceito, funcionamento e utilização é bem simples, ficando para a Stack e Heap os maiores detalhamentos.

Stack (ou pilha)

Área da memória que armazena os valores de todas as variáveis de escopo local das funções e procedures. Quando uma função ou procedure é chamada, ela cria uma pilha com as informações das variáveis, que é liberada pela finalização da função ou procedure, ou por uma otimização do compilador. Isso faz com que cada chamada de uma função ou procedure tenha sua própria pilha. Cada thread da aplicação possui sua prória área de “stacks”.Em aplicações multi-thread, o gerenciamento da memória não cria qualquer problema, porque não trata-se de uma memória compartilhada.

A stack existe para um melhor gerenciamento da memória, visto que ela é liberada assim que não for mais necessária. Contudo, o seu funcionamento propiciona a facilidade de não gerenciar essa memória, que é feita automaticamente pelo compilador. Aliás, você não aloca manualmente a memória de um parâmetro de uma função ou quando declara que “I” é do tipo Integer:

A alocação e a desalocação da memória ocorre conforme a regra do FIFO (PEPS), ou first in, first out (por isso é chamado de pilha!).

Ela é utilizada em:

  • Variáveis locais de métodos, procedures e funções.
  • Parâmetros dos de métodos, procedures e funções e seus respectivos retornos.
  • Chamadas de API (do Windows, por exemplo).
  • Tipos Records, desde que eles não estejam alocados globalmente ou que sua alocação tenha sido feita explicitamente de forma dinâmica, como por exemplo, através de New e Dispose.

Exceção

Existe uma exceção, contudo. Long strings, wide strings, dynamic arrays, variants, e interfaces não são alocadas em uma stack (pilha), e sim na Heap (ver adiante), mesmo que variáveis locais, fugindo da lógica. Isso porque tratam-se de tipos dinâmicos, cuja a alocação do tamanho não é determinado no momento da chamada do função ou procedure, e sim com a evolução do código dentro delas.

$MINSTACKSIZE e $MAXSTACKSIZE e aplicações Win32.

Essas duas directivas informam qual o tamanho mínimo e máximo da pilha. O padrão é 16K como mínimo, e 1MB como máximo. Esses dois valores podem ser alterados através dessas directivas. Quando uma aplicação é iniciada, é garantido que o mínimo dos tamanhos estejam disponíveis, de outra forma, o Windows irá sinalizar essa situação e ocorrerá um erro no início da aplicação. A tamanho máximo de uma pilha nunca deve ser ultrapassado, visto que isso ocasionaria o famoso erro de StackOverflow. Partindo do mínimo, quando a aplicação precisa de maior quantidade de espaço dentro da pilha, é realizado o incremento de 4K a cada nova necessidade, até que o máximo disponível seja atingido. A verificação dessa ocorrência é totalmente automática.

Heap

Heap é uma área de memória da aplicação (processo) e não da thread. Ela é utilizada quando não é possível determinar o tamanho do espaço a ser alocado antes da real utilização desse espaço. Variáveis dinâmicas criadas com GetMem ou New são alocados na Heap, até que sejam chamados FreeMem ou Dispose. Além disso, todos os tipos referenciados, como classes por exemplo, também são alocados na Heap.

As aplicações possuem esse espaço de memória para esse propósito porque é custoso ficar solicitando, sempre que necessário, ao sistema operacional a disponibilidade de mais espaço na memória. Assim, a existência de um espaço na memória que possa sempre ser utilizado pela aplicação e que fique ao domínio dela, gera um ganho de performance significativo.

Essa área da memória é utilizando quando:

  • Instanciamos um objeto.
  • Criamos e alteramos elementos dinâmicos, como arrays.
  • Sempre que explicitamente utilizamos funções de alocação de memória com GetMem e New.
  • Trabalhamos com strings, variants e interface.

A alocação da memória Heap não possui uma otimização. Imagine uma área de memória onde sempre que necessário, um espaço é reservado dentro do espaço disponível, mas onde não existe uma sequência lógica para isso, e sim apenas a delimitação de uma “área” para aquela utilização. Com isso, diferente da pilha, quando necessário encontrar um conteúdo na Heap, basicamente é necessário sair verificando cada elemento da Heap até encontrar o correto. Isso obviamente não é o melhor das otimizações.

O compilador pode utlizar tanto a memória RAM quanto a virtual (Disco) para geração da Heap.

Segmento de texto

Como curiosidade, ainda existe o segmento de texto, que basicamente é a área da memória onde o código-fonte (compilado) permanece para a execução da aplicação.

Abaixo encontra-se uma imagem para ajudá-lo no entendimento:

Áreas da memória
Créditos a http://www.tenouk.com/ModuleW.html

Conclusão

Veja que não existe nada de anormal e que tudo segue uma lógica bem simples. Com esse conhecimento é possível desenhar sistemas que aproveitam melhor cada carcterística de alocação de memória e evitar imprevistos.

2 respostas para “Áreas de memória no Delphi”

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *