As 5 tecnologias baseadas em Java para aprender em 2008

Assim, quando você usa Integers o autoboxing/autounboxing acontece com chamadas a Integer.valueOf( int ) que fazem pool dos integers que já foram criados. Deve ser melhor do que você mesmo fazer o seu pool, não?

Onde é que você está tendo essa criação de objetos xulos que são rapidamente coletados? Você anda criando integers chamando “new Integer(int)”?
[/quote]

Imagina um loop, que é executado milhares de vezes por segundo. Isso é um SELECTOR do NIO. Agora imagina que em cada loop vc precisa pegar o FileDescriptor (inteiro) e associar a um SelectionKey (objeto). As collections de Java não aceitam primitivos, certo? Então vc tem que usar Map<Integer, Object>. Só que a cada interação vc coloca e remove milhares de entradas no seu map, e o FD (FileDescriptor) é um inteiro primitivo. Então vc estará criando milhares de objetos inteiros e sobrecarregando o GC. Não tem jeito a não ser que vc faça algum pool de objetos ou que use uma collection de primitivos. Outra opção seria criar um pool interno de inteiros, mais ou menos, como o pool de strings do Java. Mas são milhares de inteiros, mas precisamente 2^32. Outra opção seria criar um array de objetos (SelectionKeys), mas o nosso array seria todo esburacado porque o FD é algo assim 2342342342. Para iterar esse array seria um parto. Precisamos de um collection que suporte inteiro primitivo como chave ou temos que fazer um pool de objetos inteiros. Em termos de performance, a primeira opção é bem melhor do que a segunda, então primitivos podem não ser tão ruim assim. Pelo menos nesse caso extremo tiveram o seu uso!

Claro que esse sobrecarregamento é de alguns poucos milisegundos, ou seja, na maioria dos casos vc vai cagar pra ele. Infelizmente (ou felizmente) não no meu caso.

Isso sem falar na implementação do Selector (sun.nio.ch.SelectorImpl), que leak iterator a torto e a direito, mesmo dentro de um block synchronized da lista! :s Seria mais interessante reutilizar o mesmo iterator sempre.

Acho que já dá para voltar para o tópico. Só quiz falar que quando um cara falar que uma folha é azul, ou que Java é 100% OO, ou que ele precisa de primitivos, etc e tal, não façam como o CV e digam que ele tem deficiencia mental e não façam como o louds e digam que ele só faz MRD.

O nome dado para isso é Inteligência Emocional.

Mas é exatamente isso, já existe um pool de inteiros dentro da própria JVM, assim como o de Strings, é só você usar autoboxing/autounboxing ou criar os seus inteiros usando Integer.valueOf( int ).

Eu realmente não tinha me tocado que outboxing vai te dar um pool de wrappers automaticamente. Faz todo sentido mesmo. Boa observação Maurício, e obrigado por ter feito isso sem precisar chamar ninguém de debil mental.

Infelizmente parece que o pessoal do NIO esqueceu de atualizar o código depois de autoboxing, pois no openjdk ainda está assim:

          int nextFD = pollWrapper.getDescriptor(i);
 
          SelectionKeyImpl ski = (SelectionKeyImpl) fdToKey.get(new Integer(nextFD));

Quando ao iterator não tem escapatória. Está leakando mesmo, pois por padrão os Maps criam um novo iterator a cada chamada para iterator().

Todas as coleções do java fazem isso sempre (ou pelo menos deveriam), não é um caso específico de Map não.

Quando é um duelo de forças onde as pessoas apenas discutem educadamente apresentado seus argumentos acho que todos lucram, os que estão assistindo e principalmente quem está discutindo. Acabei de aprender que autoboxing te dá de presente pool de wrappers. Não sabia disso…

Agora quando alguem coloca uma resposta com palavras do tipo deficiencia mental, imbecil, M**D*, aberração, etc. isso prejudica bastante o debate e a credibilidade do GUJ.

Minha opinião, se me permitem, ok? Obrigado…

Todas as coleções do java fazem isso sempre (ou pelo menos deveriam), não é um caso específico de Map não.[/quote]

Fazem isso para garantir que seja thread-safe, mas no caso do selector ele está sincronizando a lista, ou seja, pode tranquilamente e sem medo reutilizar o iterator, principalmente dentro de um loop que está sendo executado milhares de vezes por segundo.

sun.nio.ch.SelectorImpl.java

        synchronized (cks) {
            Iterator i = cks.iterator();
            
             //..
          }

Cara, sei desse tipo de coisa por dois motivos, por já ter feito muito tunning de GC e por hoje trabalhar com isso. Voltando a questão do GC. A HotSpot possui apenas coletores generacionais e em todos os casos a minor collections sempre pausa quando vai fazer Sweep-Compact ou Copy-Compact. Ela suporta coleta paralela da old gen com geração de lixo flutuante. Porém no caso de minor collections, a Sun decidiu por sempre fazer compactação, coisa que não existe implementação paralela sem uso de read-barriers, que é um recurso exclusivo das RTVMs pois torna o código compilado significativamente mais lento.

Vocês ai precisam de alguém que saiba pelo menos ler direito a documentação da Sun. Só isso já seria um bom começo.

Quanto a questão de que todo lookup com autoboxing em um HashMap cria um objeto inútil, bom, isso não necessariamente acontece pois ela é capaz de eliminar a alocação via inlining e escape analysis.Você consegue ver isso com um build de debug do OpenJDK.

Quando eu falo as coisas eu geralmente coloco o link e o conteúdo. Como eu disse antes, o GC sempre PAUSA as threads, mas SE E APENAS SE houver coisas para limpar. Se não houver nenhum objeto para limpar então essa pausa é praticamente zero porque a checagem é feita em paralello com as outras threads. Se vc achar que não é isso, por favor poste onde na documentação está escrito que o GC introduz latência mesmo quando não há nada para ele limpar.

Bom, uma coisa é certa, gerando um monte de inteiros e iterators a cada iteração do selector vc vai realmente gerar um belo trabalho para o GC e as coisas com certeza vão pausar bem mais do que poderiam caso vc reutilizasse esses objetos.

Basta ligar o debug do GC para constatar isso…

Apenas uma consideração de quem acabou de descobrir que autoboxing te dá de presente pool de Integers. Pensando melhor aqui, isso pode se tornar um presente de grego. :slight_smile:

Strings são bastante limitadas, ou seja, num programa vc não vai gerar strings dinamicamente e internalizá-las (cacheá-las) no pool de strings da VM.

Acho que esse pool é para apenas as strings que estão hardcodes no seu código, apesar de que vc pode usar intern() para cachear qualquer objeto string.

Minha dúvida é que se vc usar autoboxing dentro de um selector ou de qualquer outro loop que vai rodar milhares de vezes por segundo e durante milhares de segundos, gerando milhares de FD (FileDescriptors) diferentes, vc pode terminar com um pool imenso de Integers em memória. Não sei se isso é um big deal ou apenas uma simples questão de memória, mas como temos mais de 4 bilhões de inteiros, 4 bilhões de objetos integers em memória pode não ser uma boa idéia… De repente é melhor usar um pool de objetos Integers mutáveis mesmo…

Sérgio, não existe “pausa praticamente zero” quando se fala em GC. Mesmo quando não há nada para ser coletado, independente de você usar algoritmos “geracionais” ou incrementais, você vai ter pausas para avaliação do estado da heap, mesmo que seja para o GC descobrir que não existe nada a ser coletado.

Como você mesmo quer links, eis um que pode ser útil:

[quote=saoj][quote]
Vocês ai precisam de alguém que saiba pelo menos ler direito a documentação da Sun. Só isso já seria um bom começo.
[/quote]

Obrigado, Louds, mas o pessoal aqui sabe ler sim. Quando eu falo as coisas eu geralmente coloco o link e o conteúdo. Como eu disse antes, o GC sempre PAUSA as threads, mas SE E APENAS SE houver coisas para limpar. Se não houver nenhum objeto para limpar então essa pausa é praticamente zero. Se vc achar que não é isso, por favor poste aonde na documentação está escrito que o GC introduz latência mesmo quando não há nada para ele limpar.[/quote]

Primeiro, na implementação do HotSpot uma minor collection sempre causa pausas e estas envolvem todas as threads. Eu mostrei a conta do tempo que leva para pausar todas threads, independente do trabalho feito pelo GC. Se o GC simplesmente pausar e logo em seguida resumir todas threads, isso leva 8millis num box com 4 cores linux - e não fica melhor se tiver mais 60. Esse praticamente zero seu são 8millis, que é 8x mais que a necessidade de 1milli de precisão que você tanto enfatizou.

Segundo, minor collections são implementadas usando um CopyCollector, cujo tempo de execução é um fator do número de objetos visíveis na partição ativa. Um CC não processa os objetos "mortos".

Terceiro, você falou antes "…ele só precisa PAUSAR as demais threads da aplicação SE e ONLY SE ele tiver alguma memória para liberar. Caso contrário isso é feito em paralelo". Tem alguns problemas aqui. Paralelo significa usar vários processadores ao mesmo tempo. Sim, isso é perfeitamente possível com os HotSpot; porém não significa que é possível realizar uma minor collection concorrente com a execução de código gerenciado. Paralelo e Concorrente significam duas coisas quando se fala de garbage collection, taí sua confusão, avisei que precisava aprender a ler a documentação. A opção -UseParNewGC não torna o GC concorrente para minor collections.

Quarto, é obvio que um GC não irá realizar uma coleta de lixo se não existe necessidade de liberar memória. Porém escrever um programa em Java que não aloca memória é quase impossível e sem razão. Ou seja, mesmo otimizando seu código ao máximo só está adiando o inevitavel.

Links? Bom, a documentação em lugar algum fala que que é possível fazer coleta concorrente do Eden Space*. Uma busca no google por "concurrent eden space" só retorna resultados relativos ao mature space. Hmm, tem um artigo muito legal do David Bacon sobre real time garbage collection, sobre o metronome, no qual eles conseguem uma latência de 6millisegundos em um ambiente com 1 processador e 50% de CPU em uso.

*Eden Space é o nome da geração mais nova do GC do Hot Spot, onde a maioria dos objetos novos são alocados.

Sérgio, porém para substanciar minhas palavras vou exemplificá-las com código. O código pode ser meu, mas não irá soar tão prepotente quanto seu autor. :wink:

Vamos lá, quero provar que uma thread produzindo lixo atrapalha a vida de outras mesmo na presença de vários cores. Para isso vou deixar uma calculando a diferença entre duas chamadas de System.nanoTime(), que não é mais que alguns poucos micro-segundos; e vou deixar uma segunda thread alocando objetos adoidado. Claro que isso não acontece no mundo real, mas serve para acelerar o experimento e mostrar que o efeito existe mesmo com uma mortalidade de quase 100% - tornando a minor collection super rápida.

O código é o seguinte:

public class Driver {
	public static void main(String[] args) throws Exception {
		new Thread(new Runnable() {
			public void run () {
				while (true) { new Object(); }
			}
		}).start();

		while(true) {
			//new Object();
			long a = System.nanoTime();
			long b = (System.nanoTime() - a) / 1000l;
			if (b &gt; 900) //0.9 mili
				System.out.println (b);
		}
	}
}

Ele vai imprimir no console o delta em micro segundos sempre que levar mais de 0.9milli entre duas chamadas System.nanoTime().

Executando o código em uma máquina com 4 cores, o que significa nenhum thread switch, vemos valores próximos de 10milli, apesar da maioria ficar na faixa do 1-4 milli. O que é compreensível já que não tem mais nada rodando e somente 2 threads precisam de sinalização. Bem abaixo daqueles 8millis que eu alardei, porém aqui temos o melhor cenário possível. Se rodarmos com -verbose:gc dá para ver que cada minor collection leva em média 0.5millis e o resto todo do tempo é o overhead de pausar/resumir todas threads.

Sérgio, essa é minha última resposta sobre o assunto, pois realmente parece que vocês precisam contratar um especialista no assunto e eu não quero dar consultoria de graça. :stuck_out_tongue:

Beleza, Rodrigo. Vc escreveu demais. Se eu for ler tudo isso e tentar responder vai dar sono em todo mundo aqui. Se vc tivesse respondido nesse nível antes, teria sido mais proveitoso. O seu problema não é falta de conhecimento mais sim falta de humildade para debater sem dizer que o outro é um ser inferior ou que só faz M**D*.

Tudo que eu tenho a dizer é:

  1. Vc usou -XX:+UseParallelGC e sua máquina tem mais do que um processador?

  2. O que o DQO falou está certo, se tiver GC haverá pausa, não importa o tipo de GC.

  3. É plenamente possível fazer um programa em Java que não libere nenhum objeto para o GC, ou seja, que faça pool de tudo. O exemplo mais simples está abaixo:

import java.util.*;

public class Test {
    
    public static void main(String[] args) throws Exception {
        
        long x = 0;
        
        List&lt;String&gt; list = new LinkedList&lt;String&gt;();
        
        while(true) {
            
            Thread.sleep(10);
            
            if (list.size() &lt; 100000) { // avoid OutOfMemory...
                
                list.add(new String(String.valueOf(x++)));
            }
        }
    }
}
  1. Se não há nada para ser coletado o GC simplesmente não dá as caras! Isso mesmo… Sem GC e sem pausa!

Rode o programa acima assim:

java -XX:+UseParallelGC -verbose:gc Test

Já está rodando aqui a 20 minutos sem printar nada no console…

Experimente agora meter um if (x % 500 == 0) System.gc(); no meio do loop e vc terá um exemplo de um GC que não limpa nada.

Conclusões:

  1. Se não quer pagar o preço do GC, evite ao máximo criar objetos a toa. Use pool para tudo ou primitivos quando possível. Caso contrário vc vai perder alguns milesegundo mesmo.

  2. Por favor, sejam mais tolerantes e mais humildes com as pessoas (eu incluso) que de uma maneira ou de outra falam coisas que vcs não concordam. Se o cara está falando besteira, argumentem educadamente e não xingem o cara de Débil Mental, Imbecil, etc. Eu falei uma coisa errada (que não existia um pool de Integers) e não vi o Maurício me xingando de nada.

[quote=saoj]Beleza, Rodrigo. Vc escreveu demais. Se eu for ler tudo isso e tentar responder vai dar sono em todo mundo aqui. Se vc tivesse respondido nesse nível antes, teria sido mais proveitoso. O seu problema não é falta de conhecimento mais sim falta de humildade para debater sem dizer que o outro é um ser inferior ou que só faz M**D*.

Tudo que eu tenho a dizer é:

  1. Vc usou -XX:+UseParallelGC e sua máquina tem mais do que um processador?
    [/quote]

Não usei -XX:+UseParallelGC pois no meu exemplo o tempo aumenta em muito devido a custo de sincronização, com picos de 45 millis. Quanto a máquina que eu rodei, você não leu direito o que eu escreví, não é? A máquina tem 4 cores, um xeon com 4 cores.

Logo, você concorda comigo que ter um serviço que depende de resolução de 1 milli segundo rodando em uma linguagem com GC é muito questionável uma vez que não é possível entregar esse tempo de resposta.

Vamos lá, usar pools para tudo torna o programa mais difícil de escrever para coisas elementares e um inferno para coisas um pouco além do simples. Não pode usar java.lang.String nem as classes de collection, já que essas alocam memória do heap. Não dá para usar absolutamente quase nada que vem na J2SE. Ou seja, é quase como não programar em Java.

Porém existe um problema com esse tipo de técnica, primeiro que ela não é muito escalável, pois ou gera muita contenção ao manipular o pool ou desperdiça memória demais por usar per-thread pools. Além disso, tem o problema de que a taxa de mortalidade dos objetos é pequena, o que gera muita cópia durante as minor collections; infla muito o remembered set devido a muitas referencias entre mature e eden space. Isso significa que o programa em geral fica mais lento, especialmente as minor collections. Pooling tem um throughput mensuravelmente inferior a alocação direta.

Enfim, só o esforço de escrever uma aplicação inteira usando pools já não justifica a técnica. Tenta reescrever o demo do Mentawai para fazer isso. Lembrando que isso deve ser seguido por todas bibliotecas usadas - framework mvc, driver jdbc, collections, etc.

Não tem como não pagar o preço do GC. Evitando alocar objetos você só mitiga o problema, troca GCs frequentes por GC mais lentos.

Sérgio, você é pessoa que mais abusa de Argumentum ad hominem, como pode ser visto em todas suas respostas dirigidas a mim. Foram suas idéias as atacadas e isso é uma coisa muito diferente, questioná-las e criticá-las em momento algum diz respeito a você, apenas a elas.

Sérgio, o código acima não vai coletar nada porque não há nenhum objeto elegível para ser coletado. Todos os objetos que você está criando continuam sendo referenciados por “alguém” (no seu caso, a lista onde você está armazenando seus objetos), logo o GC sequer vai trabalhar.

Logo, isso é bem diferente do código que o Rodrigo apresentou porque os objetos que ele cria dentro do loop deixam de ser referenciados ao final de cada iteração, forçando o GC a trabalhar com uma certa freqüência.

O Maurício ainda tem esperanças em ir para o céu quando ele morrer, por isso ele não costuma dar patadas :smiley:

É plenamente possível. Vc pode mitigar e evitar o GC. E tb é plenamente possível fazer um programa que não cria objetos para serem coletados e usa pool e coleção de primitivos pra tudo. Dá um trabalho grande mesmo, mas é plenamente possível. Basta vc ter suas estruturas de dados (incluindo aí o seu pool de objetos que nada mais é que um linked list) e ter em mente que é proibido usar o GC. Simples assim e isso não tem nada haver com uma aplicação web. (O Mentawai por sinal cria uma instancia de action para cada requisição e um bando de outros objetos por requisição)

Tb vale notar que fica mais simples e possível ainda quando se está num ambiente single-threaded. Como eu sei que vc conhece NIO eu sei que vc vai entender isso.

E o GC não vai rodar a cada minuto se vc estiver usando o ParallelGC e um heap suficientemente grande. Pra vc ter uma idéia, com aqueles leaks do Selector, ele rodava aqui de 20 em 20 minutos, isso com uma carga absurda em cima. Dá para colocar ordens em algumas bolsas em menos de 1 ms. O bottleneck está do lado da bolsa.

Vc já está dando consultoria de graça e esse assunto já deu o que tinha que dar. Um dia a gente pode se encontrar pessoalmente e trocar experiencia sobre isso.

Fui! :thumbup:

Ok, entao faca como o louds e o dqo e embase seus argumentos, ao inves de dizer que “basta vc ter suas estruturas de dados (incluindo aí o seu pool de objetos que nada mais é que um linked list) e ter em mente que é proibido usar o GC” - que tal um exemplo menos trivial? Eis aqui um probleminha bem simples que vc pode resolver usando a sua maravilhosa tecnica:

http://codekata.pragprog.com/2007/01/kata_four_data_.html

Errrm, uma coisa que eu não consegui entender é a que ponto você quer chegar, Sérgio? Porque você quer tanto evitar GC assim?

Olá pessoal… o André Santi me chamou a atenção para este thread. Por sinal, é ótimo ver debates deste nível técnico por aqui; mesmo em sites de escopo mais global, 99% da turma só fala bobagem ou superficialidades sobre assuntos como GC.

Por acaso há poucos dias li um paper bem interessante, que esclarecer alguns assuntos discutidos aqui:

Effective Prefetch for Mark-Sweep Garbage Collection

O interessante desse paper não é nem tanto a técnica apresentada para otimizações de prefetching, e sim alguns detalhes que os autores discutem de GC em geral, como a coleta “lazy” de regiões livres do heap. É mais um fator que indica que a maior parte do custo de GC está na fase inicial de localização dos objetos vivos (mark). Concordo com quem disse que o custo de GC é relativamente alto, mesmo quando não há muito o que limpar. (Claro que nesse cenário, salvo bug ou System.gc() explícito, o GC nem deveria acordar para começo de conversa.)

Os GCs paralelos/concorrentes são criaturas complexas. Têm limitações. O HotSpot não faz tudo o que poderia (embora faça muita coisa!) para eliminar pausas de GC; nem deve, pois não é uma JVM RT. Como tb já disseram aqui, RealTime tem seus custos. Também li os artigos sobre o Metronome da IBM, que é muito interessante. Infelizmente ainda não achei papers ou documentos técnicos decentes sobre o RTSJ da Sun. Creio que a JVM RT da Sun deve ser mais dependente das facilidades RT do SO, por isso que essa JVM só é disponível para o Solaris, o qual possui “out of the box” uma capacidade como SO RT que dá uma surra no Windows, Linux e outros SOs comuns (pelo menos nas suas versões convencionais, bem suportadas pelos fornecedores do SO e das principais aplicações…). [DISCLAIMER: Não tenho experiência real com RT, mas acredito no que li na nova edição do Solaris Internals :wink: ]

Uma dica para quem quer acompanhar de perto essas tecnologias: assinem a lista da JSR-166, onde o pessoal discute a próxima versão da java.util.concurrent. Embora o assunto principal seja concorrência, j.u.c., JMM e afins, volta e meia rola algum comentário sobre otimizações de JVM em geral. E são comentários feitos por gente que mexe no código do HotSpot, que sabe o que está falando. Ainda há poucos dias andei metendo meu bedelho (humildemente - o nível da lista é uma ordem de magnitude acima do meu) sobre a questão dos wrappers de tipos primitivos em collections. O Doug Lea é dos meus: sempre reclama que a plataforma Java dá um suporte de segunda classe a “tipos escalares”; mesmo com otimizações como o cache de wrappers do Java 5+ (que por sinal é limitado a uma estreita faixa de valores!), o custo dos wrappers, graças à collections “apagadas” onde após a compilação tudo acaba virando Object, é pavoroso. O Doug cita cenários de aplicações forkJoin que ficaram 16 vezes mais lentas (!), só devido ao overhead de wrappers como Integer, em comparação com uma versão usando classes especializadas nos tipos primitivos. Basicamente, a alocação desses wrappers estúpidos - mesmo com alocação e GC “quase grátis” - esculhamba com a eficiência dos caches e tb inúmeras otimizações do JIT, sendo que o efeito é ainda pior em arquiteturas multicore & SMP. E a otimização de Escape analysis, que espero estar mais avançada (com stack allocation) no Java 7, não resolve muita coisa… só serve para problemas triviais, como objetos Iterator usados em loops. Para problemas mais complexos, por exemplo um algoritmo que processa um int[] que em determinado momento tem que ser todo enfiado num Object[] de alguma collection, a tecnologia de escape analysis que existe hoje (mesmo em teoria) não dá pro gasto.

Lazy sweeping não é difícil de se implementar, já que opera completamente com memória fora do alcance do mutator step. O problema é implementar compactação ou cópia de forma concorrente ou lazy, que é o motivo pelo qual essa etapa ser stop-the-world no HotSpot.