Pergunta

Python usa o método de contagem de referência para lidar com o tempo de vida do objeto.Portanto, um objeto que não tenha mais utilidade será imediatamente destruído.

Mas, em Java, o GC (coletor de lixo) destrói objetos que não são mais utilizados em um determinado momento.

Por que Java escolhe essa estratégia e qual é o benefício disso?

Isso é melhor que a abordagem Python?

Foi útil?

Solução

Existem desvantagens no uso da contagem de referência.Uma das mais citadas são as referências circulares:Suponha que A faça referência a B, B faça referência a C e C faça referência a B.Se A descartasse sua referência a B, tanto B quanto C ainda terão uma contagem de referência de 1 e não serão excluídos com a contagem de referência tradicional.CPython (a contagem de referências não faz parte do próprio python, mas de sua implementação C) captura referências circulares com uma rotina de coleta de lixo separada que é executada periodicamente ...

Outra desvantagem:A contagem de referências pode tornar a execução mais lenta.Cada vez que um objeto é referenciado e desreferenciado, o intérprete/VM deve verificar se a contagem caiu para 0 (e então desalocar se isso acontecer).A coleta de lixo não precisa fazer isso.

Além disso, a coleta de lixo pode ser feita em um tópico separado (embora possa ser um pouco complicado).Em máquinas com muita RAM e para processos que usam memória lentamente, talvez você não queira fazer GC!A contagem de referências seria uma desvantagem em termos de desempenho...

Outras dicas

Na verdade, a contagem de referência e as estratégias usadas pela Sun JVM são tipos diferentes de algoritmos de coleta de lixo.

Existem duas abordagens amplas para rastrear objetos mortos:rastreamento e contagem de referência.No rastreamento, o GC começa nas "raízes" - coisas como referências de pilha e rastreia todos os objetos acessíveis (ativos).Qualquer coisa que não possa ser alcançada é considerada morta.Na contagem de referências cada vez que uma referência é modificada os objetos envolvidos têm sua contagem atualizada.Qualquer objeto cuja contagem de referência seja definida como zero é considerado morto.

Com basicamente todas as implementações de GC, há compensações, mas o rastreamento geralmente é bom para alto rendimento (ou seja,operação rápida), mas tem tempos de pausa mais longos (lacunas maiores onde a interface do usuário ou o programa podem congelar).A contagem de referência pode operar em partes menores, mas será mais lenta no geral.Isso pode significar menos congelamentos, mas pior desempenho geral.

Além disso, um GC de contagem de referência requer um detector de ciclo para limpar quaisquer objetos em um ciclo que não serão capturados apenas pela contagem de referência.Perl 5 não tinha um detector de ciclo em sua implementação de GC e poderia vazar memória cíclica.

Também foram feitas pesquisas para obter o melhor dos dois mundos (tempos de pausa baixos, alto rendimento):http://cs.anu.edu.au/~Steve.Blackburn/pubs/papers/urc-oopsla-2003.pdf

Darren Thomas dá uma boa resposta.No entanto, uma grande diferença entre as abordagens Java e Python é que, com a contagem de referências no caso comum (sem referências circulares), os objetos são limpos imediatamente, em vez de em alguma data posterior indeterminada.

Por exemplo, posso escrever código desleixado e não portátil em CPython, como

def parse_some_attrs(fname):
    return open(fname).read().split("~~~")[2:4]

e o descritor de arquivo daquele arquivo que abri será limpo imediatamente porque assim que a referência ao arquivo aberto desaparecer, o arquivo será coletado como lixo e o descritor de arquivo será liberado.É claro que, se eu executar Jython ou IronPython ou possivelmente PyPy, o coletor de lixo não será necessariamente executado até muito mais tarde;possivelmente ficarei sem descritores de arquivo primeiro e meu programa travará.

Então você DEVE escrever um código parecido com

def parse_some_attrs(fname):
    with open(fname) as f:
        return f.read().split("~~~")[2:4]

mas às vezes as pessoas gostam de confiar na contagem de referências para sempre liberar recursos, pois às vezes isso pode tornar seu código um pouco mais curto.

Eu diria que o melhor coletor de lixo é aquele com melhor desempenho, que atualmente parece ser o coletor de lixo geracional no estilo Java que pode ser executado em um thread separado e tem todas essas otimizações malucas, etc.As diferenças em como você escreve seu código devem ser insignificantes e, idealmente, inexistentes.

Acho que o artigo "Teoria e prática Java:Uma breve história da coleta de lixo" da IBM deve ajudar a explicar algumas das suas dúvidas.

A coleta de lixo é mais rápida (mais eficiente em termos de tempo) do que a contagem de referência, se você tiver memória suficiente.Por exemplo, um gc de cópia percorre os objetos "vivos" e os copia para um novo espaço, e pode recuperar todos os objetos "mortos" em uma única etapa, marcando toda uma região de memória.Isto é muito eficiente, se você tem memória suficiente.As coleções geracionais utilizam o conhecimento de que “a maioria dos objetos morre jovem”;frequentemente, apenas uma pequena porcentagem dos objetos precisa ser copiada.

[Esta também é a razão pela qual gc pode ser mais rápido que malloc/free]

A contagem de referências é muito mais eficiente em termos de espaço do que a coleta de lixo, pois recupera a memória no exato momento em que fica inacessível.Isso é bom quando você deseja anexar finalizadores a objetos (por exemplo,para fechar um arquivo quando o objeto Arquivo ficar inacessível).Um sistema de contagem de referência pode funcionar mesmo quando apenas uma pequena porcentagem da memória está livre.Mas o custo de gerenciamento de aumentar e diminuir os contadores a cada atribuição de ponteiro custa muito tempo, e ainda é necessário algum tipo de coleta de lixo para recuperar os ciclos.

Portanto, a compensação é clara:se você tiver que trabalhar em um ambiente com memória restrita ou se precisar de finalizadores precisos, use a contagem de referência.Se você tiver memória suficiente e precisar de velocidade, use a coleta de lixo.

Uma grande desvantagem do GC de rastreamento do Java é que, de tempos em tempos, ele "parará o mundo" e congelará o aplicativo por um tempo relativamente longo para executar um GC completo.Se o heap for grande e a árvore de objetos for complexa, ele irá congelar por alguns segundos.Além disso, cada GC completo visita toda a árvore de objetos repetidamente, algo que provavelmente é bastante ineficiente.Outra desvantagem da maneira como o Java faz o GC é que você precisa informar à jvm qual tamanho de heap deseja (se o padrão não for bom o suficiente);a JVM deriva desse valor vários limites que acionarão o processo de GC quando houver muito lixo acumulado no heap.

Presumo que esta seja realmente a principal causa da sensação de espasmo do Android (baseado em Java), mesmo nos celulares mais caros, em comparação com a suavidade do iOS (baseado em ObjectiveC e usando RC).

Eu adoraria ver uma opção jvm para ativar o gerenciamento de memória RC e talvez manter o GC apenas para ser executado como último recurso quando não houver mais memória.

O Sun Java VM mais recente possui vários algoritmos de GC que você pode ajustar.As especificações Java VM omitiram intencionalmente a especificação do comportamento real do GC para permitir diferentes (e múltiplos) algoritmos de GC para diferentes VMs.

Por exemplo, para todas as pessoas que não gostam da abordagem "pare o mundo" do comportamento padrão do Sun Java VM GC, existem VMs como WebSphere Real Time da IBM que permite que aplicativos em tempo real sejam executados em Java.

Como a especificação Java VM está disponível publicamente, não há (teoricamente) nada que impeça alguém de implementar uma Java VM que use o algoritmo GC do CPython.

A contagem de referências é particularmente difícil de ser feita de forma eficiente em um ambiente multithread.Não sei como você começaria a fazer isso sem entrar em transações assistidas por hardware ou instruções atômicas incomuns (atualmente) semelhantes.

A contagem de referência é fácil de implementar.As JVMs tiveram muito dinheiro investido em implementações concorrentes, portanto não deveria ser surpresa que elas implementassem soluções muito boas para problemas muito difíceis.No entanto, está se tornando cada vez mais fácil direcionar sua linguagem favorita para a JVM.

No final do jogo, mas acho que uma justificativa significativa para RC em python é sua simplicidade.Veja isso e-mail de Alex Martelli, por exemplo.

(Não consegui encontrar um link fora do cache do Google, a data do e-mail é de 13 de outubro de 2005 na lista python).

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top