Memória de pilha e pilha em Java


99

Pelo que entendi, em Java, a memória da pilha contém primitivas e invocações de métodos e a memória heap é usada para armazenar objetos.

Suponha que eu tenha uma aula

class A {
       int a ;
       String b;
       //getters and setters
}
  1. Onde o primitivo ada classe Aserá armazenado?

  2. Por que a memória heap existe? Por que não podemos armazenar tudo na pilha?

  3. Quando o objeto é coletado como lixo, a pilha associada ao objeto é destruída?



@ S. Lott, exceto que este é sobre Java, não C.
Péter Török

@ Péter Török: De acordo. Embora a amostra de código seja Java, não há tag para indicar que é apenas Java. E o princípio geral também deve se aplicar a Java e C. Além disso, há muitas respostas para essa pergunta no Stack Overflow.
S.Lott

9
@SteveHaigh: neste site, todos estão preocupados demais com a questão de saber se algo pertence aqui ... Gostaria de saber o que esse site realmente está compartilhando com toda a nitidez de saber se as perguntas pertencem ou não aqui.
Sam Goldberg

Respostas:


106

A diferença básica entre pilha e pilha é o ciclo de vida dos valores.

Os valores da pilha existem apenas dentro do escopo da função em que são criados. Quando ele retorna, eles são descartados.
No entanto, os valores de heap existem no heap. Eles são criados em algum momento e destruídos em outro (pelo GC ou manualmente, dependendo do idioma / tempo de execução).

Agora, o Java armazena apenas primitivas na pilha. Isso mantém a pilha pequena e ajuda a manter os quadros individuais da pilha pequenos, permitindo assim mais chamadas aninhadas.
Os objetos são criados no heap e apenas as referências (que por sua vez são primitivas) são passadas na pilha.

Portanto, se você criar um objeto, ele será colocado no heap, com todas as variáveis ​​que pertencem a ele, para que possa persistir após o retorno da chamada da função.


2
"e apenas referências (que por sua vez são primitivas)" Por que dizer que as referências são primitivas? Você pode esclarecer por favor?
31712 Geek

5
@ Geek: Como a definição comum de tipos de dados primitivos se aplica: "um tipo de dados fornecido por uma linguagem de programação como um componente básico" . Você também pode observar que as referências estão listadas entre os exemplos canônicos mais adiante no artigo .
back2dos

4
@ Geek: Em termos de dados, você pode visualizar qualquer um dos tipos de dados primitivos - incluindo referências - como números. Mesmo chars são números e podem ser usados ​​de forma intercambiável. As referências também são apenas números referentes a um endereço de memória, com 32 ou 64 bits de comprimento (embora não possam ser usados ​​como tal - a menos que você esteja brincando sun.misc.Unsafe).
Sune Rasmussen 02/02

3
A terminologia desta resposta está errada. De acordo com a Java Language Specification, as referências NÃO são primitivas. A essência do que a Resposta diz está correta. (Enquanto você pode fazer um argumento que as referências estão "em um sentido" primitivo é pelo pelo A JLS define a terminologia para Java, e ele diz que os tipos primitivos são. boolean, byte, short, char, int, long, floatE double.)
Stephen C

4
Como descobri neste artigo , Java pode armazenar objetos na pilha (ou mesmo em registros de pequenos objetos de vida curta). A JVM pode ficar um pouco escondida. Não é exatamente correto dizer "Agora o Java armazena apenas primitivas na pilha".

49

Onde os campos primitivos são armazenados?

Os campos primitivos são armazenados como parte do objeto que é instanciado em algum lugar . A maneira mais fácil de pensar onde é isso - é a pilha. No entanto , esse nem sempre é o caso. Conforme descrito na teoria e na prática de Java: Lendas do desempenho urbano, revisitadas :

As JVMs podem usar uma técnica chamada análise de escape, pela qual eles podem dizer que certos objetos permanecem confinados a um único encadeamento por toda a vida útil e que a vida útil é limitada pela vida útil de um determinado quadro de pilha. Esses objetos podem ser alocados com segurança na pilha em vez da pilha. Melhor ainda, para objetos pequenos, a JVM pode otimizar completamente a alocação e simplesmente içar os campos do objeto em registros.

Assim, além de dizer "o objeto é criado e o campo também existe", não se pode dizer se algo está na pilha ou na pilha. Observe que, para objetos pequenos e de curta duração, é possível que o 'objeto' não exista na memória como tal e, em vez disso, tenha seus campos colocados diretamente nos registradores.

O artigo conclui com:

As JVMs são surpreendentemente boas em descobrir coisas que costumamos assumir que apenas o desenvolvedor poderia saber. Ao permitir que a JVM escolha entre alocação de pilha e alocação de heap caso a caso, podemos obter os benefícios de desempenho da alocação de pilha sem fazer com que o programador se agonize sobre a alocação na pilha ou no heap.

Portanto, se você tiver um código parecido com:

void foo(int arg) {
    Bar qux = new Bar(arg);
    ...
}

onde o ...não permite quxdeixar esse escopo, qux pode ser alocado na pilha. Na verdade, isso é uma vitória para a VM, porque significa que ela nunca precisa ser coletada como lixo - ela desaparecerá quando sair do escopo.

Mais sobre a análise de escape na Wikipedia. Para aqueles que desejam se aprofundar em documentos, Escape Analysis for Java da IBM. Para quem vem de um mundo C #, pode encontrar boas leituras de A pilha é um detalhe de implementação e A verdade sobre os tipos de valor, por Eric Lippert (são úteis para os tipos Java, pois muitos dos conceitos e aspectos são iguais ou semelhantes) . Por que os livros .Net falam sobre alocação de memória pilha versus pilha? também entra nisso.

Nos porquês da pilha e da pilha

Na pilha

Então, por que ter a pilha ou a pilha? Para coisas que deixam o escopo, a pilha pode ser cara. Considere o código:

void foo(String arg) {
    bar(arg);
    ...
}

void bar(String arg) {
    qux(arg);
    ...
}

void qux(String arg) {
    ...
}

Os parâmetros também fazem parte da pilha. Na situação em que você não tem um heap, você passaria o conjunto completo de valores na pilha. Isso é bom para "foo"pequenas strings ... mas o que aconteceria se alguém colocasse um arquivo XML enorme nessa string. Cada chamada copiava toda a cadeia enorme para a pilha - e isso seria um grande desperdício.

Em vez disso, é melhor colocar os objetos que têm alguma vida fora do escopo imediato (passados ​​para outro escopo, presos em uma estrutura que outra pessoa está mantendo, etc ...) em outra área chamada heap.

Na pilha

Você não precisa da pilha. Poderia-se, hipoteticamente, escrever uma linguagem que não use uma pilha (de profundidade arbitrária). Um antigo BASIC que aprendi na juventude o fez, só era possível fazer 8 níveis de gosubchamadas e todas as variáveis ​​eram globais - não havia pilha.

A vantagem da pilha é que, quando você tem uma variável que existe com um escopo, quando você sai desse escopo, o quadro da pilha é exibido. Simplifica realmente o que está lá e o que não está lá. O programa passa para outro procedimento, um novo quadro de pilha; o programa retorna ao procedimento e você retornou ao que vê seu escopo atual; o programa deixa o procedimento e todos os itens da pilha são desalocados.

Isso realmente facilita a vida da pessoa que está escrevendo o tempo de execução do código para usar uma pilha e uma pilha. Eles simplesmente muitos conceitos e maneiras de trabalhar no código, permitindo que a pessoa que escreve o código na linguagem seja liberada de pensar neles explicitamente.

A natureza da pilha também significa que ela não pode se fragmentar. A fragmentação da memória é um problema real com a pilha. Você aloca alguns objetos, coleta o lixo do meio e tenta encontrar espaço para o próximo grande a ser alocado. É uma bagunça. Ser capaz de colocar as coisas na pilha significa que você não precisa lidar com isso.

Quando algo é coletado de lixo

Quando algo é coletado, o lixo se foi. Mas é apenas lixo coletado porque já foi esquecido - não há mais referências ao objeto no programa que podem ser acessadas a partir do estado atual do programa.

Vou apontar que essa é uma simplificação muito grande da coleta de lixo. Existem muitos coletores de lixo (mesmo no Java - você pode ajustá-lo usando vários sinalizadores ( docs ).) Eles se comportam de maneira diferente e as nuances de como cada um faz as coisas são um pouco profundas para esta resposta. Noções básicas sobre coleta de lixo de Java para ter uma idéia melhor de como isso funciona.

Dito isto, se algo estiver alocado na pilha, ele não será coletado como parte System.gc()dele - será desalocado quando o quadro da pilha aparecer. Se algo estiver na pilha e referenciado a partir de algo na pilha, não será coletado lixo naquele momento.

Por que isso importa?

Para a maior parte, a sua tradição. Os livros de texto escritos e as classes de compilador e a documentação de vários bits são importantes para o heap e a pilha.

No entanto, as máquinas virtuais de hoje (JVM e similares) fizeram um grande esforço para tentar manter isso escondido do programador. A menos que você esteja ficando sem um e outro e precise saber o motivo (em vez de apenas aumentar o espaço de armazenamento adequadamente), isso não importa muito.

O objeto está em algum lugar e está no local em que pode ser acessado corretamente e rapidamente pela quantidade adequada de tempo em que ele existe. Se estiver na pilha ou na pilha - isso realmente não importa.


7
  1. No heap, como parte do objeto, que é referenciado por um ponteiro na pilha. ie aeb serão armazenados adjacentes um ao outro.
  2. Porque se toda a memória fosse de pilha, não seria mais eficiente. É bom ter uma pequena área de acesso rápido onde começamos e ter esses itens de referência na área de memória muito maior que resta. No entanto, isso é um exagero quando um objeto é simplesmente uma única primitiva que ocuparia aproximadamente a mesma quantidade de espaço na pilha que o ponteiro para ele.
  3. Sim.

1
Eu acrescentaria ao seu ponto 2 que se você armazenasse objetos na pilha (imagine um objeto de dicionário com centenas de milhares de entradas), a fim de passá-lo ou devolvê-lo de uma função que você precisa copiar o objeto a cada Tempo. Usando um ponteiro ou referência a um objeto na pilha, passamos apenas a referência (pequena).
Scott Whitlock

1
Eu pensei que 3 seria 'não' porque se o objeto está sendo coletado de lixo, não há referência na pilha apontando para ele.
Luciano

@ Luciano - Entendo o seu ponto. Eu li a pergunta 3 de maneira diferente. O "ao mesmo tempo" ou "a essa hora" está implícito. :: shrug ::
pdr

3
  1. No heap, a menos que o Java aloque a instância da classe na pilha como uma otimização após provar por meio da análise de escape que isso não afetará a semântica. Porém, este é um detalhe de implementação; portanto, para todos os propósitos práticos, exceto a micro-otimização, a resposta está "na pilha".

  2. A memória da pilha deve ser alocada e desalocada na última ordem de saída. A memória heap pode ser alocada e desalocada em qualquer ordem.

  3. Quando o objeto é coletado de lixo, não há mais referências apontando para ele da pilha. Se houvesse, eles manteriam o objeto vivo. As primitivas de pilha não são coletadas como lixo porque são destruídas automaticamente quando a função retorna.


3

A memória da pilha é usada para armazenar variáveis ​​locais e chamadas de função.

Enquanto a memória heap é usada para armazenar objetos em Java. Não importa, onde o objeto é criado no código.

Onde vai o primitivo aem class Aser armazenados?

Nesse caso, a primitiva a está associada ao objeto de classe A. Por isso, cria na memória de pilha.

Por que a memória heap existe? Por que não podemos armazenar tudo na pilha?

  • As variáveis ​​criadas na pilha ficarão fora do escopo e serão destruídas automaticamente.
  • A pilha é muito mais rápida para alocar em comparação com as variáveis ​​no heap.
  • Variáveis ​​na pilha devem ser destruídas pelo Garbage Collector.
  • Mais lento para alocar em comparação com variáveis ​​na pilha.
  • Você usaria a pilha se souber exatamente quantos dados precisa alocar antes do tempo de compilação e eles não forem muito grandes. (Variáveis ​​locais primitivas armazenam na pilha)
  • Você usaria o heap se não souber exatamente quantos dados precisará em tempo de execução ou se precisar alocar muitos dados.

Quando o objeto é coletado como lixo, a pilha associada ao objeto é destruída?

O Garbage Collector trabalha sob o escopo da memória Heap, destruindo objetos que não possuem cadeia de referência da raiz.


Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.