Perguntas sobre entrevistas de gerenciamento de memória em Java (+ respostas)
1. Introdução
Neste artigo, exploraremos algumas questões de gerenciamento de memória que frequentemente surgem durante entrevistas com desenvolvedores Java. O gerenciamento de memória é uma área com a qual muitos desenvolvedores não estão familiarizados.
Na verdade, os desenvolvedores geralmente não precisam lidar com esse conceito diretamente - já que a JVM cuida dos detalhes essenciais. A menos que algo dê errado, mesmo os desenvolvedores experientes podem não ter informações precisas sobre gerenciamento de memória na ponta dos dedos.
Por outro lado, esses conceitos são bastante prevalentes em entrevistas - então vamos direto ao assunto.
2. Questões
Q1. O que significa a declaração “A memória é gerenciada em Java”?
A memória é o principal recurso que um aplicativo requer para executar com eficiência e, como qualquer recurso, é escassa. Como tal, sua alocação e desalocação para e de aplicativos ou partes diferentes de um aplicativo exigem muito cuidado e consideração.
No entanto, em Java, um desenvolvedor não precisa alocar e desalocar explicitamente a memória - a JVM e mais especificamente o Garbage Collector - tem a função de lidar com a alocação de memória para que o desenvolvedor não precise.
Isso é contrário ao que acontece em linguagens como C, onde um programador tem acesso direto à memória e literalmente faz referência a células de memória em seu código, criando muito espaço para vazamentos de memória.
Q2. O que é coleta de lixo e quais são suas vantagens?
A coleta de lixo é o processo de examinar a memória heap, identificar quais objetos estão em uso e quais não estão e excluir os objetos não utilizados.
Um objeto em uso, ou um objeto referenciado, significa que alguma parte do seu programa ainda mantém um ponteiro para esse objeto. Um objeto não utilizado ou não referenciado não é mais referenciado por nenhuma parte do seu programa. Portanto, a memória usada por um objeto não referenciado pode ser recuperada.
A maior vantagem da coleta de lixo é que ela remove o ônus da alocação / desalocação manual de memória, para que possamos nos concentrar na solução do problema em questão.
Q3. Existem desvantagens na coleta de lixo?
Yes. Sempre que o coletor de lixo é executado, ele afeta o desempenho do aplicativo. Isso ocorre porque todos os outros threads no aplicativo precisam ser interrompidos para permitir que o thread do coletor de lixo efetivamente faça seu trabalho.
Dependendo dos requisitos do aplicativo, isso pode ser um problema real que é inaceitável pelo cliente. No entanto, esse problema pode ser bastante reduzido ou mesmo eliminado por meio da otimização hábil e do ajuste do coletor de lixo e do uso de diferentes algoritmos de GC.
Q4. Qual é o significado do termo “Pare o mundo”?
Quando o encadeamento do coletor de lixo está em execução, outros encadeamentos são interrompidos, o que significa que o aplicativo é parado momentaneamente. Isso é análogo a limpeza ou fumigação da casa, onde os ocupantes têm acesso negado até o processo ser concluído.
Dependendo das necessidades de um aplicativo, a coleta de lixo "pare o mundo" pode causar um congelamento inaceitável. É por isso que é importante fazer o ajuste do coletor de lixo e a otimização da JVM para que o congelamento encontrado seja pelo menos aceitável.
Q5. O que são pilha e pilha? O que é armazenado em cada uma dessas estruturas de memória e como elas estão relacionadas?
A pilha é uma parte da memória que contém informações sobre chamadas de método aninhadas para a posição atual no programa. Ele também contém todas as variáveis locais e referências a objetos no heap definidos nos métodos atualmente em execução.
Essa estrutura permite que o tempo de execução retorne do método sabendo o endereço de onde foi chamado e também limpe todas as variáveis locais após sair do método. Cada thread tem sua própria pilha.
O heap é uma grande quantidade de memória destinada à alocação de objetos. Quando você cria um objeto com a palavra-chavenew, ele é alocado no heap. No entanto, a referência a esse objeto está na pilha.
Q6. O que é coleta de lixo geracional e o que a torna uma abordagem de coleta de lixo popular?
A coleta de lixo geracional pode ser definida livremente como a estratégia usada pelo coletor de lixo em que o heap é dividido em várias seções chamadas gerações, cada uma das quais armazenará objetos de acordo com sua "idade" no heap.
Sempre que o coletor de lixo estiver em execução, a primeira etapa do processo é chamada de marcação. É aqui que o coletor de lixo identifica quais pedaços de memória estão em uso e quais não. Este pode ser um processo muito demorado, se todos os objetos em um sistema tiverem que ser verificados.
À medida que mais e mais objetos são alocados, a lista de objetos aumenta e aumenta, levando a um tempo de coleta de lixo cada vez maior. No entanto, a análise empírica das aplicações mostrou que a maioria dos objetos tem vida curta.
Com a coleta de lixo geracional, os objetos são agrupados de acordo com sua “idade” em termos de quantos ciclos de coleta de lixo eles sobreviveram. Dessa forma, a maior parte do trabalho se espalhou por vários ciclos de coleta menores e maiores.
Hoje, quase todos os coletores de lixo são geracionais. Essa estratégia é tão popular porque, com o tempo, provou ser a solução ideal.
Q7. Descreva em detalhes como funciona a coleta de lixo geracional
Para entender corretamente como a coleta de lixo geracional funciona, é importante primeiroremember how Java heap is structured para facilitar a coleta de lixo geracional.
A pilha é dividida em espaços menores ou gerações. Esses espaços são: Geração jovem, Geração antiga ou Tenured e Geração permanente.
Oyoung generation hosts most of the newly created objects. Um estudo empírico da maioria das aplicações mostra que a maioria dos objetos tem vida curta e, portanto, logo se tornam elegíveis para coleta. Portanto, novos objetos começam sua jornada aqui e só são "promovidos" para o espaço da velha geração depois de atingirem uma certa "idade".
O termo“age” na coleta de lixo geracionalrefers to the number of collection cycles the object has survived.
O espaço da geração jovem é dividido em três espaços: um espaço do Éden e dois espaços de sobreviventes, como Survivor 1 (s1) e Survivor 2 (s2).
Oold generation hosts objects thathave lived in memory longer than a certain “age”. Os objetos que sobreviveram à coleta de lixo da geração jovem são promovidos para esse espaço. Geralmente é maior que a geração jovem. Por ser maior em tamanho, a coleta de lixo é mais cara e ocorre com menos frequência do que na geração jovem.
Opermanent generationor more commonly called, PermGen, contains metadata required by the JVM para descrever as classes e métodos usados no aplicativo. Ele também contém o conjunto de cadeias para armazenar cadeias internas. É preenchido pela JVM em tempo de execução com base nas classes em uso pelo aplicativo. Além disso, as classes e métodos da biblioteca da plataforma podem ser armazenados aqui.
Primeiro,any new objects are allocated to the Eden space. Ambos os espaços de sobreviventes começam vazios. Quando o espaço do Éden se enche, uma pequena coleta de lixo é acionada. Objetos referenciados são movidos para o primeiro espaço sobrevivente. Objetos não referenciados são excluídos.
Durante o próximo GC menor, o mesmo acontece com o espaço do Éden. Objetos não referenciados são excluídos e objetos referenciados são movidos para um espaço sobrevivente. No entanto, nesse caso, eles são movidos para o segundo espaço sobrevivente (S2).
Além disso, os objetos do último GC menor no primeiro espaço de sobrevivência (S1) têm sua idade aumentada e são movidos para S2. Depois que todos os objetos sobreviventes forem movidos para S2, o espaço S1 e o Eden serão limpos. Neste ponto, o S2 contém objetos com diferentes idades.
No próximo GC menor, o mesmo processo é repetido. No entanto, desta vez, os espaços dos sobreviventes mudam. Objetos referenciados são movidos para o S1 do Eden e do S2. Objetos sobreviventes envelhecem. Eden e S2 são limpos.
Após cada ciclo menor de coleta de lixo, a idade de cada objeto é verificada. Aqueles que atingiram uma certa idade arbitrária, por exemplo, 8 anos, são promovidos da geração jovem para a geração antiga ou permanente. Para todos os ciclos menores de GC subsequentes, os objetos continuarão a ser promovidos para o espaço de geração antiga.
Isso praticamente esgota o processo de coleta de lixo na geração jovem. Eventualmente, uma grande coleta de lixo será realizada na geração antiga, que limpa e compacta esse espaço. Para cada GC principal, existem vários GCs menores.
Q8. Quando um objeto se torna elegível para coleta de lixo? Descreva como o Gc coleta um objeto elegível?
Um objeto torna-se elegível para coleta de lixo ou GC se não for acessível a partir de nenhum encadeamento ativo ou de referências estáticas.
O caso mais direto de um objeto se tornar elegível para coleta de lixo é se todas as suas referências forem nulas. Dependências cíclicas sem nenhuma referência externa ativa também são elegíveis para o GC. Portanto, se o objeto A fizer referência ao objeto B e o objeto B fizer referência ao objeto A e eles não tiverem nenhuma outra referência ativa, os objetos A e B serão elegíveis para a coleta de lixo.
Outro caso óbvio é quando um objeto pai é definido como nulo. Quando um objeto de cozinha faz referência interna a um objeto de geladeira e a um objeto de pia, e o objeto de cozinha é definido como nulo, tanto a geladeira quanto a pia se tornam elegíveis para a coleta de lixo ao lado da cozinha principal.
Q9. Como você aciona a coleta de lixo do código Java?
You, as Java programmer, can not force garbage collection in Java; ele só será acionado se a JVM achar que precisa de uma coleta de lixo baseada no tamanho de heap Java.
Antes de remover um objeto do encadeamento de coleta de lixo da memória, invoca o método finalize () desse objeto e oferece a oportunidade de executar qualquer tipo de limpeza necessária. Você também pode chamar esse método de um código de objeto, no entanto, não há garantia de que a coleta de lixo ocorra quando você chamar esse método.
Além disso, existem métodos como System.gc () e Runtime.gc () que são usados para enviar solicitação de coleta de lixo à JVM, mas não é garantido que a coleta de lixo ocorra.
Q10. O que acontece quando não há espaço de pilha suficiente para acomodar o armazenamento de novos objetos?
Se não houver espaço de memória para criar um novo objeto no Heap, o Java Virtual Machine lançaOutOfMemoryError ou mais especificamentejava.lang.OutOfMemoryError heap space.
Q11. É possível «ressuscitar» um objeto que se tornou elegível para a coleta de lixo?
Quando um objeto se torna elegível para a coleta de lixo, o GC deve executar o métodofinalize nele. O métodofinalize tem garantia de execução apenas uma vez, portanto, o GC sinaliza o objeto como finalizado e dá a ele um descanso até o próximo ciclo.
No métodofinalize, você pode tecnicamente “ressuscitar” um objeto, por exemplo, atribuindo-o a um campostatic. O objeto volta a ficar vivo e não é elegível para a coleta de lixo, para que o GC não o colete durante o próximo ciclo.
O objeto, no entanto, seria marcado como finalizado, portanto, quando se tornasse elegível novamente, o método finalize não seria chamado. Em essência, você pode ativar esse truque de "ressurreição" apenas uma vez durante a vida útil do objeto. Esteja ciente de que esse hack feio deve ser usado apenas se você realmente sabe o que está fazendo - no entanto, entender esse truque dá algumas dicas sobre como funciona o GC.
Q12. Descreva referências fortes, fracas, suaves e fantasmas e seu papel na coleta de lixo.
Por mais que a memória seja gerenciada em Java, um engenheiro pode precisar executar o máximo de otimização possível para minimizar a latência e maximizar o rendimento em aplicativos críticos. Tanto quantoit is impossible to explicitly control when garbage collection is triggered na JVM,it is possible to influence how it occurs as regards the objects we have created.
Java nos fornece objetos de referência para controlar o relacionamento entre os objetos que criamos e o coletor de lixo.
Por padrão, todo objeto que criamos em um programa Java é fortemente referenciado por uma variável:
StringBuilder sb = new StringBuilder();
No fragmento acima, a palavra-chavenew cria um novo objetoStringBuilder e o armazena no heap. A variávelsb então armazena umstrong reference para este objeto. O que isso significa para o coletor de lixo é que o objetoStringBuilder específico não é elegível para coleta devido a uma referência forte mantida porsb. A história só muda quando anulamossb assim:
sb = null;
Após chamar a linha acima, o objeto será elegível para coleta.
Podemos mudar essa relação entre o objeto e o coletor de lixo, envolvendo-o explicitamente em outro objeto de referência que está localizado dentro do pacotejava.lang.ref.
Umsoft reference pode ser criado para o objeto acima assim:
StringBuilder sb = new StringBuilder();
SoftReference sbRef = new SoftReference<>(sb);
sb = null;
No snippet acima, criamos duas referências ao objetoStringBuilder. A primeira linha cria umstrong referencesbe a segunda linha cria umsoft referencesbRef. A terceira linha deve tornar o objeto elegível para coleta, mas o coletor de lixo irá adiar a coleta por causa desbRef.
A história só mudará quando a memória ficar apertada e a JVM estiver prestes a lançar um erroOutOfMemory. Em outras palavras, objetos com apenas referências suaves são coletados como último recurso para recuperar a memória.
Umweak reference pode ser criado de maneira semelhante usando a classeWeakReference. Quandosb é definido como nulo e o objetoStringBuilder tem apenas uma referência fraca, o coletor de lixo da JVM não terá absolutamente nenhum compromisso e coletará imediatamente o objeto no próximo ciclo.
Umphantom reference é semelhante a uma referência fraca e um objeto com apenas referências fantasmas será coletado sem espera. No entanto, as referências fantasmas são colocadas na fila assim que seus objetos são coletados. Podemos pesquisar a fila de referência para saber exatamente quando o objeto foi coletado.
Q13. Suponha que tenhamos uma referência circular (dois objetos que se referem um ao outro). Esse par de objetos poderia se tornar elegível para a coleta de lixo e por quê?
Sim, um par de objetos com uma referência circular pode se qualificar para a coleta de lixo. Isso ocorre por causa de como o coletor de lixo do Java lida com referências circulares. Ele considera os objetos vivos não quando eles têm alguma referência a eles, mas quando são acessíveis navegando no gráfico de objetos a partir de alguma raiz de coleta de lixo (uma variável local de um thread ativo ou de um campo estático). Se um par de objetos com uma referência circular não estiver acessível a partir de nenhuma raiz, ele será considerado elegível para coleta de lixo.
Q14. Como as strings são representadas na memória?
Uma instânciaString em Java é um objeto com dois campos: um campochar[] value e um campoint hash. O campovalue é uma matriz de caracteres que representa a própria string e o campohash contémhashCode de uma string que é inicializada com zero, calculada durante os primeiroshashCode() chamada e em cache desde então. Como um caso curioso, sehashCode de uma string tem valor zero, ele deve ser recalculado cada vez quehashCode() é chamado.
O importante é que uma instânciaString é imutável: você não pode obter ou modificar a matrizchar[] subjacente. Outro recurso das strings é que as strings estáticas constantes são carregadas e armazenadas em cache em um conjunto de strings. Se você tiver vários objetosString idênticos em seu código-fonte, todos eles serão representados por uma única instância no tempo de execução.
Q15. O que é um construtor de cordas e quais são seus casos de uso? Qual é a diferença entre anexar uma string a um construtor de cordas e concatenar duas strings com um operador +? Como o Stringbuilder difere do Stringbuffer?
StringBuilder permite manipular sequências de caracteres anexando, excluindo e inserindo caracteres e strings. Esta é uma estrutura de dados mutável, ao contrário da classeString que é imutável.
Ao concatenar duas instânciasString, um novo objeto é criado e as strings são copiadas. Isso pode causar uma sobrecarga enorme no coletor de lixo, se precisarmos criar ou modificar uma string em um loop. StringBuilder permite lidar com manipulações de strings com muito mais eficiência.
StringBuffer é diferente deStringBuilder porque é seguro para threads. Se você precisar manipular uma string em um único thread, useStringBuilder.
3. Conclusão
Neste artigo, abordamos algumas das perguntas mais comuns que aparecem com frequência em entrevistas de engenheiro Java. As perguntas sobre gerenciamento de memória são principalmente solicitadas aos candidatos a desenvolvedor Java sênior, pois o entrevistador espera que você tenha desenvolvido aplicativos não triviais que são, muitas vezes, afetados por problemas de memória.
Isso não deve ser tratado como uma lista exaustiva de perguntas, mas como uma plataforma de lançamento para futuras pesquisas. Nós, por exemplo, desejamos sucesso em todas as próximas entrevistas.