Devemos evitar a criação de objetos em Java?

230

Foi-me dito por um colega que na criação de objetos Java é a operação mais cara que você poderia realizar. Então, só posso concluir para criar o menor número de objetos possível.

Isso parece de certa forma frustrar o propósito da programação orientada a objetos. Se não estamos criando objetos, estamos apenas escrevendo um estilo de classe C para otimização?

    
por Slamice 22.05.2012 / 04:06
fonte

14 respostas

458

Seu colega não faz ideia do que está falando.

Sua operação mais cara seria ouvi-los . Eles perderam seu tempo direcionando você para informações com mais de uma década desatualizadas (a partir da data original em que essa resposta foi postada) e você terá que gastar tempo postando aqui e pesquisando na Internet pela verdade.

Espero que eles estejam apenas ignorando algo que ouviram ou leram há mais de uma década e não sabem nada melhor. Eu tomaria qualquer outra coisa que eles dissessem como suspeita também, isso deveria ser uma falácia bem conhecida por qualquer um que se mantenha atualizado de qualquer forma.

Tudo é um objeto (exceto primitives )

Tudo diferente de primitivos ( int, long, double , etc) são Objetos em Java. Não há como evitar a criação de objetos em Java.

A criação de objetos em Java devido a suas estratégias de alocação de memória é mais rápida que C ++ na maioria dos casos e para todos os propósitos práticos comparado a tudo o mais na JVM pode ser considerado "livre" .

No início, no início dos anos 90, as implementações de JVM no início dos anos 2000 tiveram alguma sobrecarga de desempenho na alocação real de Objetos. Este não tem sido o caso desde pelo menos 2005.

Se você ajustar -Xms para suportar toda a memória necessária para que seu aplicativo seja executado corretamente, o GC talvez nunca precise executar e varrer a maior parte do lixo nas implementações modernas de GC, programas de curta duração nunca .

Ele não tenta maximizar o espaço livre, que é um arenque vermelho, maximiza o desempenho do tempo de execução. Se isso significa que o heap da JVM é quase 100% alocado o tempo todo, que assim seja. A memória de heap livre da JVM não oferece nada apenas sentado lá de qualquer maneira.

Existe um equívoco de que o GC irá liberar memória de volta para o resto do sistema de uma maneira útil, isso é completamente falso!

O heap da JVM não aumenta e diminui, de forma que o resto do sistema é afetado positivamente pela memória livre na Pilha da JVM . -Xms aloca TODO o que é especificado na inicialização e sua heurística é nunca realmente liberar qualquer parte dessa memória de volta ao sistema operacional para ser compartilhada com qualquer outro processo do sistema operacional até que essa instância da JVM seja encerrada completamente. -Xms=1GB -Xmx=1GB aloca 1 GB de RAM, independentemente de quantos objetos são realmente criados a qualquer momento. Existem algumas configurações que permitem que porcentagens da memória heap sejam liberadas, mas para todos os propósitos práticos a JVM nunca é capaz de liberar memória suficiente para que isso aconteça , portanto, nenhum outro processo pode recuperar essa memória, então o resto do sistema não se beneficiará do heap da JVM estar livre . Um RFE para isso foi "aceito" 29-NOV-2006, mas nada foi feito sobre isso. Este comportamento não é considerado uma preocupação por alguém de autoridade.

Existe um conceito errôneo de que a criação de muitos objetos curtos e curtos faz com que a JVM pause por longos períodos de tempo. Isso também é falso

Algoritmos de GC atuais são realmente otimizados para criar muitos objetos pequenos que são de curta duração, que são basicamente a heurística de 99% para objetos Java em cada programa. Tentativas no Object Pooling farão a JVM piorar na maioria dos casos.

Os únicos Objetos que precisam de pool atualmente são Objetos que se referem a recursos finitos que são externos para a JVM; Sockets, arquivos, conexões de banco de dados, etc e podem ser reutilizados. Objetos regulares não podem ser agrupados no mesmo sentido que em idiomas que permitem acesso direto a locais da memória. Object caching é um conceito diferente e pode ou não ser o que algumas pessoas chamam ingenuamente pooling , os dois conceitos não são a mesma coisa e não devem ser confundidos.

Os algoritmos modernos de GC não têm este problema porque eles não são desalocados em um horário, eles desalocam quando a memória livre é necessária em uma determinada geração. Se o heap for grande o suficiente, nenhuma desalocação ocorrerá o tempo suficiente para causar pausas.

Linguagens dinâmicas orientadas a objeto estão batendo C até hoje em dia em computação sensível testes.

    
por 22.05.2012 / 04:33
fonte
89

Linha de fundo: Não comprometa seu design para usar atalhos para criar objetos. Evite criar objetos desnecessariamente. Se for sensato, projete para evitar operações redundantes (de qualquer tipo).

Ao contrário da maioria das respostas - sim, a alocação de objetos tem um custo associado. É um custo baixo, mas você deve evitar criar objetos desnecessários. O mesmo que você deve evitar qualquer coisa desnecessária no seu código. Gráficos de objetos grandes tornam o GC mais lento, implicando em tempos de execução mais longos, pois você provavelmente terá mais chamadas de método, acionará mais erros de cache da CPU e aumentará a probabilidade de seu processo ser trocado para disco em casos com pouca memória RAM.

Antes que alguém reclamar sobre o fato de ser um caso extremo - eu criei perfis de aplicativos que, antes de otimizar, criavam 20 MB + de objetos para processar ~ 50 linhas de dados. Isso é bom nos testes, até que você atinja até cem solicitações por minuto e, de repente, você está criando 2 GB de dados por minuto. Se você quiser fazer 20 reqs / seg você está criando 400MB de objetos e depois jogando fora. 20 reqs / seg é pequeno para um servidor decente.

    
por 22.05.2012 / 04:41
fonte
60

Na verdade, devido às estratégias de gerenciamento de memória que a linguagem Java (ou qualquer outra linguagem gerenciada) torna possível, a criação de objetos é pouco mais do que incrementar um ponteiro em um bloco de memória chamado geração jovem. É muito mais rápido que C, onde uma busca por memória livre tem que ser feita.

A outra parte do custo é a destruição de objetos, mas é difícil comparar com C. O custo de uma coleção é baseado na quantidade de objetos salvos a longo prazo, mas a frequência de coleções é baseada na quantidade de objetos criados. .. no final, ainda é muito mais rápido que o gerenciamento de memória no estilo C.

    
por 22.05.2012 / 04:20
fonte
38

Outros pôsteres apontaram corretamente que a criação de objetos é extremamente rápida em Java e que você não deve se preocupar com isso em qualquer aplicativo Java normal.

Existem algumas situações muito especiais em que é uma boa ideia evitar a criação de objetos.

  • Quando você está escrevendo um aplicativo sensível à latência e deseja evitar pausas no GC. Quanto mais objetos você produzir, mais GC ocorrerá e maior a chance de pausas. Essa pode ser uma consideração válida para jogos, alguns aplicativos de mídia, controle robótico, negociação de alta frequência, etc. A solução é pré-alocar todos os objetos / matrizes que você precisa antes e reutilizá-los. Existem bibliotecas especializadas no fornecimento desse tipo de recurso, por exemplo, Javolution . Mas, sem dúvida, se você realmente se importa com baixa latência, deve usar C / C ++ / assembler em vez de Java ou C #: -)
  • Evitar (Double, Integer etc.) pode ser uma micro-otimização muito benéfica em determinadas circunstâncias. Como as primitivas não encaixadas (double, int etc.) evitam a sobrecarga por objeto, elas são muito mais rápidas com trabalho intensivo da CPU, como processamento numérico, etc. Normalmente, as matrizes primitivas têm um desempenho muito bom em Java, portanto você deseja usá-las para quaisquer outros tipos de objetos.
  • Em situações de memória restrita , você deseja minimizar o número de objetos (ativos) criados, uma vez que cada objeto carrega uma pequena sobrecarga (normalmente 8-16 bytes, dependendo da implementação da JVM). Em tais circunstâncias, você deve preferir um pequeno número de objetos ou matrizes grandes para armazenar seus dados em vez de um grande número de pequenos objetos.
por 22.05.2012 / 13:17
fonte
17

Há um núcleo de verdade no que seu colega está dizendo. Eu respeitosamente sugiro que o problema com o objeto criação é na verdade garbage collection . Em C ++, o programador pode controlar precisamente como a memória é desalocada. O programa pode acumular crud por tanto tempo ou tão curto quanto quiser. Além disso, o programa C ++ pode descartar o crud usando um thread diferente daquele que o criou. Assim, o encadeamento atualmente em funcionamento nunca precisa parar para limpar.

Por outro lado, a Java Virtual Machine (JVM) interrompe periodicamente seu código para recuperar a memória não utilizada. A maioria dos desenvolvedores de Java nunca percebe essa pausa, pois geralmente é pouco frequente e muito curta. Quanto mais bruto você acumular ou quanto mais restrita for sua JVM, mais frequentes serão essas pausas. Você pode usar ferramentas como VisualVM para visualizar esse processo.

Em versões recentes do Java, o algoritmo de coleta de lixo (GC) pode ser ajustado . Como regra geral, quanto mais curto você quiser fazer as pausas, mais onerosa a sobrecarga na máquina virtual (ou seja, a CPU e a memória gastas coordenando o processo de GC).

Quando isso pode importar? Sempre que você se preocupa com taxas de resposta consistentes abaixo de milissegundos, você se importará com o GC. Sistemas automatizados de negociação escritos em Java ajustam a JVM para minimizar as pausas. As empresas que, de outra forma, escrevem o Java se voltam para o C ++ em situações em que os sistemas precisam ser altamente responsivos o tempo todo.

Para o registro, eu não tolero a evitação de objetos em geral! Padrão para programação orientada a objetos. Ajuste essa abordagem somente se o GC ficar no seu caminho e, depois, apenas após tentar ajustar a JVM para pausar por menos tempo. Um bom livro sobre o ajuste do desempenho em Java é Java Performance de Charlie Hunt e Binu John.

    
por 22.05.2012 / 05:34
fonte
11

Há um caso em que você pode ser desencorajado de criar muitos objetos em Java por causa da sobrecarga - projetando para desempenho na plataforma Android

Além disso, as respostas acima são verdadeiras.

    
por 22.05.2012 / 10:30
fonte
8

o GC é ajustado para muitos objetos de curta duração

Dito isto, se você pode reduzir trivialmente a alocação de objetos, você deve

um exemplo seria construir uma string em um loop, o caminho ingênuo seria

String str = "";
while(someCondition){
    //...
    str+= appendingString;
}

que cria um novo objeto String em cada operação de += (mais StringBuilder e a nova matriz de caracteres subjacentes)

você pode facilmente reescrever isso em:

StringBuilder strB = new StringBuilder();
while(someCondition){
    //...
    strB.append(appendingString);
}
String str = strB.toString();

esse padrão (resultado imutável e um valor intermediário mutável local) também pode ser aplicado a outras coisas

mas diferente disso você deve usar um profiler para encontrar o verdadeiro gargalo em vez de perseguir fantasmas

    
por 22.05.2012 / 10:43
fonte
5

Isso realmente depende do aplicativo específico, por isso é realmente difícil dizer em geral. No entanto, eu ficaria muito surpreso se a criação de objetos fosse realmente um gargalo de desempenho em um aplicativo. Mesmo que sejam lentos, o benefício do estilo de código provavelmente superará o desempenho (a menos que seja realmente perceptível para o usuário)

Em qualquer caso, você não deve nem começar a se preocupar com essas coisas até ter perfilado seu código para determinar os gargalos reais de desempenho em vez de adivinhar. Até lá, você deve fazer o que for melhor para a legibilidade do código, não para o desempenho.

    
por 22.05.2012 / 04:08
fonte
5

Joshua Bloch (um dos criadores da plataforma Java) escreveu em seu livro Java efetivo em 2001:

Avoiding object creation by maintaining your own object pool is a bad idea unless the objects in the pool are extremely heavyweight. A prototypical example of an object that does justify an object pool is a database connection. The cost of establishing the connection is sufficiently high that it makes sense to reuse these objects. Generally speaking, however, maintaining your own object pools clutters up your code, increases memory footprint, and harms performance. Modern JVM implementations have highly optimized garbage collectors that easily outperform such object pools on lightweight objects.

    
por 03.10.2014 / 19:23
fonte
3

Acho que seu colega deve ter dito a partir da perspectiva da criação desnecessária de objetos. Quer dizer, se você está criando o mesmo objeto com frequência, é melhor compartilhar esse objeto. Mesmo nos casos em que a criação de objetos é complexa e ocupa mais memória, convém clonar esse objeto e evitar a criação desse processo complexo de criação de objetos (mas isso depende da sua necessidade). Acho que a afirmação "Criação de objetos é cara" deve ser levada em conta.

No que se refere aos requisitos de memória da JVM, aguarde o Java 8, nem precisa especificar -Xmx, as configurações do espaço meta cuidarão da necessidade de memória da JVM e ela crescerá automaticamente.

    
por 01.08.2013 / 23:48
fonte
1

Há mais para criar uma classe do que alocar memória. Há também inicialização, não sei por que essa parte não é coberta por todas as respostas. As classes normais contêm algumas variáveis e fazem alguma forma de inicialização, e isso não é gratuito. Dependendo do que a classe é, ela pode ler arquivos ou fazer qualquer outro número de operações lentas.

Portanto, considere o que o construtor de classe faz antes de descobrir se é gratuito ou não.

    
por 22.05.2012 / 23:59
fonte
1

O GC do Java é realmente muito otimizado em termos de criação de muitos objetos rapidamente de uma maneira "explodida". Pelo que entendi, eles usam um alocador seqüencial (o mais rápido e simples O (1) alocador para pedidos de tamanho variável) para esse tipo de "ciclo de estouro" em um espaço de memória que eles chamam de "espaço Éden" e somente se os objetos persistirem depois de um ciclo de GC, eles são movidos para um local onde o GC pode coletá-los um a um.

Com isso dito, se suas necessidades de desempenho se tornarem críticas o suficiente (como medidas com requisitos reais de usuário final), os objetos carregam uma sobrecarga, mas eu não pensaria tanto em termos de criação / alocação. Tem mais a ver com a localidade de referência e o tamanho adicional de todos os objetos em Java conforme necessário para suportar conceitos como reflexão e despacho dinâmico ( Float é maior que float , geralmente algo como 4 vezes maior em 64 bits com seus requisitos de alinhamento, e uma matriz de Float não é necessariamente garantida para ser armazenada contígua do que eu entendo).

Uma das coisas mais atraentes que eu já vi desenvolvido em Java que me fez considerar um contendor pesado na minha área (VFX) foi um rastreador de caminho padrão multithread interativo (não usando cache de irradiância ou BDPT ou MLS ou qualquer coisa mais) na CPU, fornecendo pré-visualizações em tempo real que convergiram rapidamente para uma imagem sem ruído. Eu trabalhei com profissionais em C ++ dedicando suas carreiras a essas coisas com criadores de perfil de fantasia na mão que tinham dificuldade em fazer isso.

Mas eu observei o código-fonte e enquanto ele usava muitos objetos a um custo trivial, as partes mais críticas do path tracer (BVH e triângulos e materiais) evitavam muito claramente e deliberadamente os objetos em favor de grandes arrays de tipos primitivos (principalmente float[] e int[] ), que faziam com que usassem muito menos memória e garantissem a localização espacial para passar de um float na matriz para a próxima. Eu não acho que seja muito especulativo pensar que, se o autor usasse tipos de caixas que gostassem de Float , isso teria um grande custo para o seu desempenho. Mas estamos falando da parte mais crítica desse mecanismo, e tenho certeza, dado que o desenvolvedor o otimizou habilmente, que ele mediu isso e aplicou essa otimização muito, muito judiciosamente, já que ele usou objetos em qualquer lugar com custo trivial para seu impressionante rastreador de caminhos em tempo real.

I was told by a colleague that in Java object creation is the most expensive operation you could perform. So I can only conclude to create as few objects as possible.

Mesmo em um campo tão crítico quanto o meu, você não vai escrever produtos eficientes se estiver se traindo em lugares que não importam. Eu até alegaria que os campos mais críticos para o desempenho podem exigir ainda mais a produtividade, já que precisamos de todo o tempo extra para sintonizar esses pontos críticos que realmente importam por não perder tempo com coisas que não são importantes. . Como com o exemplo do path tracer de cima, o autor habilmente e judiciosamente aplicou tais otimizações apenas nos lugares que realmente importavam, e provavelmente em retrospectiva após a medição, e ainda usava alegremente objetos em qualquer outro lugar.

    
por 20.12.2018 / 23:39
fonte
0

Como as pessoas disseram que a criação de objetos não é um grande custo em Java (mas eu aposto maior do que a maioria das operações simples como adicionar, etc) e você não deve evitar muito.

Isso ainda é um custo, e às vezes você pode tentar se desfazer do maior número de objetos possível. Mas somente após o perfil mostrou que isso é um problema.

Aqui está uma ótima apresentação sobre o tema: link

    
por 24.05.2012 / 04:56
fonte
0

Eu fiz um rápido microbenchmark sobre isso e eu forneci fontes completas no github. Minha conclusão é que, se a criação de objetos é cara ou não, não é problema, mas criar continuamente objetos com a noção de que o GC cuidará das coisas para você fará com que seu aplicativo acione o processo do GC mais cedo. O GC é um processo muito caro e é melhor evitá-lo sempre que possível e não tentar forçá-lo a entrar.

    
por 25.01.2015 / 02:24
fonte