Onde mora o pool de constantes String do Java, o heap ou a pilha?

104

Eu conheço o conceito de um pool de constantes e o pool de constantes String usado por JVMs para lidar com literais String. Mas eu não sei qual tipo de memória é usado pela JVM para armazenar literais constantes de String. A pilha ou a pilha? Uma vez que é um literal que não está associado a nenhuma instância, presumo que ele será armazenado na pilha. Mas se não for referido por nenhuma instância, o literal tem que ser coletado pela execução do GC (corrija-me se eu estiver errado), então como isso será tratado se estiver armazenado na pilha?

Rengasami Ramanujam
fonte
11
Como um pool pode ser armazenado na pilha? você conhece o conceito de pilha?
The Scrum Meister
1
Olá Scrum Meister, tentei dizer que não pode ser. Desculpe pela convenção errada. Sobre GC Acabei de saber. Obrigado por isso
Rengasami Ramanujam
@TheScrumMeister - na verdade, em algumas circunstâncias, eles podem ser coletados como lixo. O "fator decisivo" é que o objeto de código para qualquer classe que menciona um literal de string terá uma referência ao objeto String que representa o literal.
Stephen C

Respostas:

74

A resposta é tecnicamente nenhuma. De acordo com a Java Virtual Machine Specification, a área para armazenar literais de string está no pool de constantes de tempo de execução . A área de memória do pool constante de tempo de execução é alocada por classe ou por interface, portanto, não está vinculada a nenhuma instância de objeto. O pool de constantes de tempo de execução é um subconjunto da área de método que "armazena estruturas por classe, como o pool de constantes de tempo de execução, dados de campo e método e o código para métodos e construtores, incluindo os métodos especiais usados ​​na inicialização e interface de classe e instância inicialização de tipo ". A especificação VM diz que embora a área do método é logicamente parte do heap, não determina que a memória alocada na área do método esteja sujeita à coleta de lixo ou outros comportamentos que estariam associados às estruturas de dados normais alocadas ao heap.

Duane Moore
fonte
8
Na verdade, quando as classes são carregadas na VM, as constantes de string serão copiadas para o heap, para um pool de string de toda a VM (no permgen, como Stephen C disse), uma vez que literais de string iguais em classes diferentes devem ser os mesmo objeto String (pelo JLS).
Paŭlo Ebermann
1
Obrigado a todos por suas respostas. Eu entendi muito com essa discussão. É bom conhecer vocês :)
Rengasami Ramanujam
4
Paŭlo, isso é verdade para a máquina virtual da Sun, mas não necessariamente para todas as implementações da JVM. Como a especificação JVM menciona, embora o conjunto de constantes de tempo de execução e a área de método sejam logicamente parte do heap, eles não precisam ter o mesmo comportamento. Apenas uma pequena diferença semântica, na verdade :)
Duane Moore
54

Conforme explicado por esta resposta , a localização exata do conjunto de sequências não é especificada e pode variar de uma implementação JVM para outra.

É interessante notar que até o Java 7, o pool estava no espaço permgen do heap no ponto de acesso JVM, mas foi movido para a parte principal do heap desde o Java 7 :

Área : HotSpot
Sinopse : No JDK 7, as sequências internadas não são mais alocadas na geração permanente do heap Java, mas, em vez disso , são alocadas na parte principal do heap Java (conhecido como as gerações jovem e velha), junto com as outras objetos criados pelo aplicativo. Essa alteração resultará em mais dados residentes no heap Java principal e menos dados na geração permanente e, portanto, pode exigir que os tamanhos de heap sejam ajustados. A maioria dos aplicativos verá apenas diferenças relativamente pequenas no uso de heap devido a essa mudança, mas os aplicativos maiores que carregam muitas classes ou fazem uso intenso do método String.intern () verão diferenças mais significativas. RFE: 6962931

E no Java 8 Hotspot, Permanent Generation foi completamente removido.

assilias
fonte
30

Literais de string não são armazenados na pilha. Nunca. Na verdade, nenhum objeto é armazenado na pilha.

Literais de string (ou mais precisamente, os objetos String que os representam) são armazenados historicamente em um Heap chamado heap "permgen". (Permgen é a abreviação de geração permanente.)

Em circunstâncias normais, os literais String e muitas das outras coisas no heap permgen são "permanentemente" alcançáveis ​​e não são coletados como lixo. (Por exemplo, literais String são sempre alcançáveis ​​a partir dos objetos de código que os utilizam.) No entanto, você pode configurar uma JVM para tentar encontrar e coletar classes carregadas dinamicamente que não são mais necessárias, e isso pode fazer com que literais String sejam coletados como lixo .

ESCLARECIMENTO # 1 - Não estou dizendo que o Permgen não recebe GC. Sim, normalmente quando a JVM decide executar um GC Completo. Meu ponto é que literais de String serão alcançáveis ​​contanto que o código que os usa seja alcançável, e o código será alcançável contanto que o classloader do código seja alcançável, e para os classloaders padrão, isso significa "para sempre".

ESCLARECIMENTO # 2 - Na verdade, o Java 7 e posterior usa o heap regular para conter o pool de strings. Portanto, os objetos String que representam literais String e strings internas estão na verdade no heap regular. (Veja a resposta de @assylias para detalhes.)


Mas ainda estou tentando descobrir a linha tênue entre o armazenamento do literal de string e a string criada com new.

Não existe uma "linha tênue". Isto é realmente muito simples:

  • String objetos que representam / correspondem a literais de string são mantidos no pool de string.
  • String objetos que foram criados por um String::intern chamada são mantidos no string pool.
  • Todos os outros String objetos NÃO são mantidos no pool de strings.

Depois, há a questão separada de onde o pool de strings está "armazenado". Antes do Java 7, era o heap permgen. Do Java 7 em diante, é o heap principal.

Stephen C
fonte
23

Agrupamento de cordas

O agrupamento de strings (às vezes também chamado de canonização de strings) é um processo de substituição de vários objetos String com valor igual, mas com identidade diferente, por um único objeto String compartilhado. Você pode atingir esse objetivo mantendo seu próprio mapa (com referências possivelmente suaves ou fracas, dependendo de seus requisitos) e usando os valores do mapa como valores canônicos. Ou você pode usar o método String.intern () que é fornecido a você pelo JDK.

Na época do Java 6, o uso de String.intern () era proibido por muitos padrões devido a uma grande possibilidade de obter OutOfMemoryException se o pool saísse de controle. A implementação do Oracle Java 7 de string pooling foi alterada consideravelmente. Você pode procurar detalhes em http://bugs.sun.com/view_bug.do?bug_id=6962931 e http://bugs.sun.com/view_bug.do?bug_id=6962930 .

String.intern () em Java 6

Naqueles bons velhos tempos, todas as strings internadas eram armazenadas no PermGen - a parte de tamanho fixo do heap usada principalmente para armazenar classes carregadas e conjunto de strings. Além das strings explicitamente internadas, o pool de strings PermGen também continha todas as strings literais usadas anteriormente em seu programa (a palavra importante aqui é usada - se uma classe ou método nunca foi carregado / chamado, quaisquer constantes definidas nele não serão carregadas).

O maior problema com esse pool de strings no Java 6 era sua localização - o PermGen. PermGen tem um tamanho fixo e não pode ser expandido em tempo de execução. Você pode defini-lo usando a opção -XX: MaxPermSize = 96m. Até onde eu sei, o tamanho padrão do PermGen varia entre 32M e 96M dependendo da plataforma. Você pode aumentar seu tamanho, mas seu tamanho ainda será fixo. Essa limitação exigia o uso muito cuidadoso de String.intern - é melhor você não internar nenhuma entrada não controlada do usuário usando este método. É por isso que o agrupamento de strings na época do Java 6 era implementado principalmente nos mapas gerenciados manualmente.

String.intern () em Java 7

Os engenheiros da Oracle fizeram uma mudança extremamente importante na lógica do agrupamento de strings no Java 7 - o pool de strings foi realocado para o heap. Isso significa que você não está mais limitado por uma área de memória separada de tamanho fixo. Todas as strings agora estão localizadas no heap, como a maioria dos outros objetos comuns, o que permite que você gerencie apenas o tamanho do heap enquanto ajusta seu aplicativo. Tecnicamente, isso por si só poderia ser uma razão suficiente para reconsiderar o uso de String.intern () em seus programas Java 7. Mas existem outras razões.

Os valores do pool de strings são coletados como lixo

Sim, todas as cadeias no conjunto de cadeias JVM são elegíveis para coleta de lixo se não houver referências a elas nas raízes do programa. Isso se aplica a todas as versões discutidas do Java. Isso significa que se sua sequência interna saiu do escopo e não há outras referências a ela - ela será coletada como lixo do conjunto de sequências JVM.

Sendo elegível para a coleta de lixo e residindo no heap, um conjunto de sequências JVM parece ser o lugar certo para todas as suas sequências, não é? Em teoria, é verdade - as strings não utilizadas serão coletadas como lixo do pool, as strings usadas permitirão que você economize memória no caso de obter uma string igual da entrada. Parece ser uma estratégia perfeita para economizar memória? Quase isso. Você deve saber como o pool de strings é implementado antes de tomar qualquer decisão.

fonte.

Tentando
fonte
11

Como outras respostas explicam, a memória em Java é dividida em duas partes

1. Pilha: Uma pilha é criada por thread e armazena quadros de pilha que, novamente, armazenam variáveis ​​locais e, se uma variável for um tipo de referência, essa variável se refere a um local de memória no heap para o objeto real.

2. Heap: Todos os tipos de objetos serão criados apenas em heap.

A memória heap é novamente dividida em 3 porções

1. Geração jovem: armazena objetos que têm uma vida curta. A própria Geração jovem pode ser dividida em duas categorias Espaço Éden e Espaço Sobrevivente .

2. Antiga Geração: Armazena objetos que sobreviveram a muitos ciclos de coleta de lixo e ainda estão sendo referenciados.

3. Geração permanente: armazena metadados sobre o programa, por exemplo, pool de constantes de tempo de execução.

O pool de constantes de string pertence à área de geração permanente da memória Heap.

Podemos ver o pool de constantes de tempo de execução para nosso código no bytecode usando o javap -verbose class_namequal nos mostrará referências de método (#Methodref), objetos de classe (#Class), literais de string (#String)

runtime-constant-pool

Você pode ler mais sobre isso em meu artigo Como a JVM trata a sobrecarga e a substituição de métodos internamente .

Naresh Joshi
fonte
Divulgue quaisquer afiliações e não use o site como uma forma de promovê-lo por meio de publicações. Veja como escrevo uma boa resposta? .
9

Às ótimas respostas que já incluímos aqui, quero acrescentar algo que está faltando na minha perspectiva - uma ilustração.

Como você já, a JVM divide a memória alocada para um programa Java em duas partes. um é pilha e outro é pilha . Stack é usado para fins de execução e heap é usado para fins de armazenamento. Nessa memória heap, a JVM aloca alguma memória especialmente destinada a literais de string. Esta parte da memória heap é chamada de pool de constantes de string .

Por exemplo, se você inicializar os seguintes objetos:

String s1 = "abc"; 
String s2 = "123";
String obj1 = new String("abc");
String obj2 = new String("def");
String obj3 = new String("456);

Literais de string s1e s2irão para o pool de constantes de string, objetos obj1, obj2, obj3 para o heap. Todos eles serão referenciados no Stack.

Além disso, observe que "abc" aparecerá no heap e no pool de constantes de string. Por que é String s1 = "abc"e String obj1 = new String("abc")será criado dessa forma? É porque String obj1 = new String("abc")cria explicitamente uma instância nova e referencialmente distinta de um objeto String e String s1 = "abc"pode reutilizar uma instância do pool de constantes de string, se houver uma disponível. Para uma explicação mais elaborada: https://stackoverflow.com/a/3298542/2811258

insira a descrição da imagem aqui

Johnny
fonte
No diagrama fornecido, onde existiriam os literais "def" e "456". E como isso seria referenciado?
Satyendra,
Obrigado pelo seu comentário @Satyendra, atualizei a ilustração e a resposta.
Johnny
@Stas porque outro objeto String "abc" é criado ... ele deve usar a referência obj1 para apontar o literal certo?
É porque String obj1 = new String ("abc") cria explicitamente uma instância nova e referencialmente distinta de um objeto String e String s1 = "abc" pode reutilizar uma instância do pool de constantes de string, se houver uma disponível. Para uma explicação mais elaborada: stackoverflow.com/a/3298542/2811258
Johnny