Continuamos a cobertura da plataforma Java SE 7, atacando dessa vez as novas APIs e outras melhorias da JVM: aperfeiçoamentos do HotSpot, Java2D, suporte a 64 bits, entre outros.

Se, como dissemos no artigo passado, “a própria linguagem Java é a ferramenta mais crítica e fundamental”, o que dizer da JVM? Ainda mais crítica, inclusive para adeptos de um número crescente de linguagens como Scala, Groovy, JavaFX etc. A plataforma Java SE é o motor que move todas as aplicações desktop e também corporativas (Java EE, que é uma extensão do SE). O conhecimento aprofundado das capacidades e características da JVM é, muitas vezes, essencial para obter o máximo desempenho ou outras qualidades desejáveis de uma aplicação.

Se o plano seguir o rumo previsto, quando o leitor receber esta Edição os primeiros Release Candidates do JDK 7 já estarão aparecendo, apontando um release final ainda este ano. Mesmo quem não tiver planos de curto prazo de tirar proveito das novas sintaxes da linguagem Java que vimos no artigo anterior, ou de novas APIs que começaremos a comentar aqui, já deve ir se preparando: por exemplo, não custa nada fazer alguns testes dos seus sistemas com a nova versão da JVM para ver se ocorre alguma regressão, algum ganho de desempenho ou outra mudança digna de nota, positiva ou negativa. Este artigo serve tanto como uma introdução a todos os aspectos do Java SE 7 além da linguagem, quanto um guia para auxiliar na exploração do novo JDK.

Na Edição anterior iniciamos a cobertura do próximo release do Java, começando pelas novidades da linguagem, que já renderam bastante assunto e ainda renderão mais... mas o Java SE 7 não se resume à nova linguagem. O novo release da plataforma também inclui, como de costume, um lote de novidades e melhorias de API ou de infraestrutura. Neste artigo faremos um apanhado destas “outras” novidades.

Finalmente, temos Java SE 7

Este é o primeiro artigo que trata da plataforma Java SE 7 como um todo, e é o primeiro que pode nomear esta plataforma como algo certo e concreto. Até há pouco tempo não havia confirmação oficial sobre tal plataforma, só havia o projeto JDK 7. Mas o JDK é apenas uma implementação, no caso a da Oracle (e por consequência das contribuições ao OpenJDK, da comunidade open source). A plataforma Java SE é definida pelo JCP, mas a JSR necessária ainda não havia sido anunciada. Alguns críticos mais ácidos da Sun (e agora da Oracle), como o pessoal do Apache (devido à velha briga em torno do Harmony), chegaram a sugerir que poderíamos não ter nenhuma JSR e nenhuma plataforma oficial, ou seja que a Oracle iria abandonar a padronização do JCP e simplesmente finalizar o JDK 7 e impô-lo ao mercado sem padrões.

Bem, a dúvida finalmente se dissipou com o anúncio de uma palestra sobre “Java SE 7” de Mark Reinhold (líder do projeto JDK 7), na Devoxx 2010, que acontecerá em Novembro. Ainda não há JSR, mas esta provavelmente será lançada antes da JavaOne 2010, que este ano acontece nos EUA em 19-23 de setembro... e pela primeira vez, no Brasil em 7-9 de dezembro.

O que é que o Java SE 7 tem?

Já cobrimos as novidades da linguagem, o item mais importante desta atualização da plataforma. Este segundo artigo dá uma visão geral do “resto”. Mas não espere uma batelada de novas APIs. O último release da plataforma a acrescentar um volume significativo de APIs foi o J2SE 5.0, e mesmo neste, vários acréscimos foram mera inclusão de APIs que já existiam (como JMX e JAXP) ou alterações incrementais de APIs já da plataforma SE (como JDBC RowSets). No Java SE 6, o número de novas APIs já foi menor, mesmo contando tais casos. E no Java SE 7, é ainda menor. Isso é natural, pois após um período de crescimento explosivo de APIs entre as versões 1.2 e 1.4, a plataforma Java estabilizou, e é bom que seja assim. Muita gente já acha o JRE muito grande, inchado. Nem todas as novas APIs devem ser empilhadas no JRE, só as realmente fundamentais.

Por outro lado, a inovação em outros aspectos não para nunca, de fato até acelera na medida em que menos esforço é investido em novas APIs. Temos um fluxo contínuo de aperfeiçoamentos da própria VM, prometendo avanços na compilação JIT de código, gerenciamento de memória / GC, concorrência, etc. Também há um esforço contínuo na melhora da implementação de APIs, por exemplo as de GUI (AWT, Java2D e Swing), sempre explorando melhor os recursos do hardware gráfico e de toolkits nativos de GUI. Além das novidades de sintaxe, é nessas áreas que o Java SE 7 apresenta seus maiores destaques. Assim, é basicamente disso que vamos falar neste artigo.

Java SE 7 versus JDK 7

Uma consequência deste foco do Java SE 7 – em fatores de implementação, não necessariamente novas APIs – é que muitas dessas novidades são, de fato, novidades do Oracle JDK 7, não do Java SE 7. Por exemplo: a nova pipeline da Java2D para o XRender, o maior destaque desta versão para GUIs AWT/Swing, é um detalhe de implementação da Java2D. Você não encontrará nenhuma JSR falando disso. Os padrões e APIs públicas não dizem nada sobre a implementação da Java2D: se deve ser feita com a GDI, X11, OpenGL, DirectX, CoreGraphics ou qualquer outra tecnologia. Até por que a maioria dessas tecnologias não é portável, mas sim amarrada a cada sistema operacional.

No entanto, o Oracle JDK é um padrão de fato. Seu código-fonte é licenciado para outros distribuidores. Alguns JDKs “alternativos”, como o da HP para HP-UX, são meros portes para outra plataforma, 99% iguais à versão da Sun / Oracle. O Apple JDK é um pouco customizado, especialmente nas tecnologias de GUI. Já o JRockit e o IBM JDK possuem VMs independentes, mas reusam o grosso das APIs. Finalmente, no terreno do Java Livre, o OpenJDK também é basicamente uma cópia do código da Oracle, exceto por customizações pontuais, em geral focadas na substituição de alguns componentes não-livres do Oracle JDK.

Nem a OpenGL escapa totalmente disso, pois cada SO e GPU possui um nível diferente de suporte à OpenGL.

Isso significa que, embora este artigo cubra vários itens que são “detalhe de implementação”, na prática estas novidades, e o conhecimento delas, são válidos para diversas outras distribuições da plataforma Java SE 7. Por exemplo, o XRender acabará aparecendo na maioria dos JDKs para Linux e Unix baseados no código da Oracle. Já o novo coletor G1 deve aparecer em alguns (como OpenJDK, Apple JDK e HP-UX JDK), mas não necessariamente em todos (por exemplo o IBM JDK tem uma VM totalmente independente, e não irá incorporar o G1).

Para tornar as coisas mais claras, cada título de seção ou quadro do artigo terá um dos símbolos:

  • [Java SE]: Funcionalidade “pública”, padronizada pelo JCP. Faz parte da plataforma e será encontrada em qualquer implementação da plataforma;
  • [JDK]: Detalhe de implementação. Será encontrado no Oracle JDK e, possivelmente, em outras implementações que sejam baseadas no código da Oracle.

As seções de ambas as categorias estão misturadas, pois a ordem das seções privilegia outros fatores editoriais como as dependências entre os assuntos. Os itens [JDK] são cobertos em maior profundidade, pois esgotaremos o assunto neste artigo. A maioria dos itens [Java SE] só poderia ser bem coberta com um artigo dedicado – que esperamos ter no futuro.

Antes de entrar no tema deste artigo, porém, a próxima seção complementa a Edição anterior, que tratou das novas sintaxes da linguagem Java. Procurei manter esta seção acessível a quem não tiver lido o artigo anterior, e independente do artigo atual para poder ser pulada sem prejuízo do restante.

[Java SE] O Estado do Lambda

Quando finalizei o artigo da Edição 82, estava certo que todo o material coberto – as novas sintaxes da linguagem Java 7 – já era suficientemente estável. Até por que quase tudo já estava implementado, seja nos builds de desenvolvimento integrados do JDK 7 (que podem ser baixados de jdk7.dev.java.net), seja nos repositórios secundários de cada subprojeto (sem builds binários, mas permitindo aos mais pacientes compilar os fontes do compilador, VM e APIs atualizadas).

Infelizmente errei a aposta no caso dos lambdas. Pouco após o fechamento da Edição, em 6 de julho, a Oracle surpreendeu com o documento State of the Lambda:


  cr.openjdk.java.net/~briangoetz/lambda/lambda-state-2.html 

Seu título é um toque humorístico de Brian Goetz (imita “State of the Union”, título do tradicional discurso anual do Presidente dos EUA no congresso). A data também revela a veracidade dos estereótipos sobre geeks: há programa melhor para um feriadão (3-5 de Julho nos EUA) do que fazer uma nova versão de uma linguagem de programação? J

Este documento propõe uma série de mudanças nos lambdas, especialmente a sintaxe. Um lambda mais simples possível (sem parâmetros ou retorno), antes escrito assim:


  #() ( 42 ) 

...agora fica assim:


  { -> 42 } 

A diferença é maior para lambdas com parâmetros e/ou retorno. Usando o mesmo exemplo do meu artigo anterior, antes podíamos escrever algo como:


  #boolean(char c) val = #(char c) (Character.isDigit(c)); 

Agora devemos usar outra sintaxe:


  Validador val = { c -> Character.isDigit(c) } 

As diferenças vão além da superfície (como a estrutura #(a)(b) versus (a->b)). Por um lado, a nova sintaxe é bem mais enxuta, especialmente com parâmetros ou retornos.

Por outro lado, desaparecem os function types – tipos estruturais, como #boolean(char) significando “lambda com um parâmetro char e retorno boolean”. No lugar deles, seremos obrigados a usar somente SAM Types: interfaces ou classes abstratas que definem um único método abstrato – como no meu exemplo a interface Validador { boolean valida (char c); }; ou dúzias de APIs do Java SE como Runnable, Callable, Comparator, etc.

A proposta anterior já dava tratamento especial aos SAM Types, especificando a “conversão-lambda” que permite atribuir um lambda a um tipo compatível. Na nova proposta, a conversão-lambda é ainda mais importante, pois todo lambda possui como tipo nativo algum SAM Type. Isso tem algumas outras consequências, por exemplo:


  Runnable r = { -> System.out.println(this instanceof Runnable); };
  r.run();

…irá garantidamente imprimir true, pois o objeto que representa o lambda será alguma classe (gerada pelo compilador) que implementa a interface Runnable.

Um benefício importante da adoção mais radical dos SAM Types, é que a inferência de tipos ficou ainda mais poderosa. Além de não precisarmos (nem podermos) declarar o tipo de retorno do lambda, também não precisamos declarar o tipo dos parâmetros: no meu exemplo, escrevi { c -> Character.isDigit(c) }, sem o tipo de c. Isso pode ser feito: { char c -> Character.isDigit(c) }, mas é opcional. Como o SAM Type deste lambda é a interface Validador, o compilador já sabe que o tipo de c é char, que é o tipo do primeiro parâmetro do método valida() que corresponde ao lambda.

A nova especificação também suporta referências para métodos:


  class Pessoa { 
  …
    public static int comparaPorIdade (Person a, Person b) { ... }
    public static int comparaPorNome (Person a, Person b) { ... }
  }
   
  Pessoa[] povo = ...;
  Arrays.sort(povo, #Pessoa.comparaPorIdade); 

No exemplo, Arrays.sort() tem um argumento do SAM Type Comparator<T>. Podemos passar como parâmetro uma referência para um método de assinatura compatível. (Graças aos tipos genéricos, temos T=Person, então o SAM Type é Comparator<Person> que define o método boolean compareTo (Person, Person).) A referência para método produz um lambda compatível com o SAM Type, cujo corpo invoca o método referenciado. É bem melhor que a prática tradicional – que no exemplo, seria definir várias classes aninhadas de Pessoa, cada uma delas implementando Comparator para determinado critério de comparação.

Num “toque final”, o statement return é ilegal em lambdas, sendo substituído pelo yield que faz... a mesma coisa. A alteração tem justificativas “tecnocráticas”: o yield finaliza a execução do lambda (e opcionalmente produz seu valor), mas não finaliza a execução de nenhum método (um lambda não é um método). Brian Goetz prefere não misturar os conceitos. Mas a maioria dos participantes da lista lambda-dev não gostou do yield, que fede a mudança gratuita de sintaxe. Tanto Neal Gafter quanto Joshua Bloch, os maiores experts em design da linguagem Java que participam da lista (mas não estão mais na Oracle – respectivamente no Google e Microsoft), não aprovaram a mudança; eu (modestamente) também detestei, e espero que a gritaria convença a Oracle a reverter essa posição.

PRÓS:

  • A sintaxe é ainda mais enxuta que a proposta original, sem ser mais confusa;
  • Os lambdas ficaram mais próximos dos SAM Types e inner classes – a realidade das milhares de APIs e bilhões de linhas de código Java que já existem;
  • A transição será mais fácil. Não haverá divisão entre novas APIs que exigem lambdas (devido ao uso dos novos function types) e APIs legadas que exigem SAM Types (e poderiam ser usadas com lambdas, mas só graças a uma conversão implícita);
  • O aprendizado também deve ser mais fácil. Para quem já conhece Java, será trivial aprender a usar lambdas, pois para quase todos os efeitos práticos, estes podem ser considerados apenas uma sintaxe mais enxuta para inner classes.

Há um debate separado sobre o suporte a return transparente, no qual seria permitido ao lambda forçar o retorno do método que o contém – um acréscimo poderoso aos lambdas, mas não faz parte da proposta atual.

CONTRAS:

  • Lamento a perda dos function types, que seriam um acréscimo importante ao sistema de tipos do Java. A migração para novas APIs pedindo function types seria um pouco penosa, mas é uma troca por uma linguagem e APIs superiores no longo prazo;
  • Uma das principais justificativas para a inclusão de última hora dos lambdas no Java 7 era a API Parallel*Array, sendo considerada para inclusão na java.util.concurrent. Devido à explosão combinatória de tipos primitivos, esta API exige uma quantidade gigantesca de SAM Types, como Ops.LongAndDoubleToInt. Com function types, bastaria usar #int(long,double). Sem os function types, a Parallel*Array terá que ficar na sua atual (horripilante) forma... e ficará de fora deste release, talvez na esperança que os function types voltem no Java SE 8.

NEUTRO:

  • A nova sintaxe { parâmetros -> corpo } e as referências para método poderiam ter sido acrescentadas da mesma forma. Idem para a inferência de tipos de parâmetros, pelo menos em contextos de conversão-lambda.

Para finalizar, talvez o debate dos lambdas ainda não esteja completo; não cometerei novamente o erro de prometer ao leitor que a versão deste artigo já é definitiva.

Também podemos perguntar se essas revisões, não só de sintaxe, mas também de funcionalidades – saem function types, entra referência para método... – resultarão em novo atraso do Java SE 7. Até o fechamento desta edição a Oracle continua sustentando o cronograma anunciado na Devoxx’09, que promete o último milestone para meados de setembro, e o FCS presumivelmente ainda em 2010, pois a partir do último milestone só haveria Release Candidates com bugfixes. Mas a nova proposta de lambdas, ainda que tardia, pode evitar atrasos. Os function types teriam impacto no design de novas APIs, e provavelmente também exigiriam alterações no formato dos arquivos .class, nas APIs de reflection e outros componentes de baixo nível da plataforma. A nova proposta não exige nada disso, basta atualizar as ferramentas que processam fontes (javac, javadoc etc.).

[JDK] O coletor G1

Vou cobrir este item rapidamente, pois já falei muito dele em “A Memória do Java”, Edição 60. A antiguidade deste artigo, de há quase dois anos, é outro motivo para não falar muito: o G1 já deixou de ser novidade, antes mesmo do Java SE 7 ser lançado! É que o JDK 7 atrasou, e o G1 acabou sendo entregue – em status early access – no JDK 6 mesmo, a partir do Update 14.

Quem testou o G1 do JDK 6, ou dos primeiros milestones do JDK 7, pode ter se decepcionado com sua estabilidade e desempenho. Eu mesmo reportei alguns bugs à Sun. Mas estes releases estavam bastante imaturos, como foi possível conferir pelo enorme volume de bugfixes e melhorias do G1 que entraram em milestones posteriores do JDK 7. O ideal é esperar pelo primeiro release de produção (FCS) do JDK 7. Quem não puder migrar para o JDK 7 tão cedo também poderá começar usando o G1 no JDK 6, cujos updates de manutenção continuarão recebendo melhorias do G1. (No momento em que escrevo acaba de ser lançado o JDK 6u21, que traz outra grande atualização do HotSpot, inclusive o G1 atualizado para uma versão bem mais recente do JDK 7.)

O G1 é uma das principais novidades de runtime do JDK: um novo Garbage Collector com qualidades soft real-time, prometendo pausas de GC curtas mesmo com heaps enormes. O pedigree do G1 é generacional, paralelo, concorrente e incremental: reúne os principais truques dos algoritmos que o precederam. O G1 pretende substituir tanto o CMS (Concurrent Mark&Sweep – coletor concorrente da geração velha) quanto o ParallelGC (coletor paralelo da geração jovem).

Não se entusiasme muito com o G1 se o seu negócio é rodar applets, aplicações desktop ou games. O G1 é otimizado para o server side. Seu objetivo é permitir, por exemplo, que uma JVM de application server Java EE use um heap de 20Gb sem que isso resulte em pausas de GC de vários segundos. Ou então, que uma aplicação com um heap de tamanho mais normal (até poucos Gb) tenha pauses de GC excepcionalmente baixas (na casa das poucas dezenas de milissegundos), algo necessário em certas aplicações com exigência crítica de tempo de resposta.

O leitor mais curioso sobre novas tecnologias de GC pode conferir também www.managedruntime.org, projeto open source que traz para CPUs “comuns” a tecnologia de GC da Azul Systems (“Java à Moda do Freguês”, Edição 78).

[JDK] Compilador Tiered e suporte a 64 bits

Se o leitor já começou uma migração do seu desktop/laptop para plataformas de 64 bits, deve compartilhar a minha irritação. Embora todos os sistemas operacionais de desktop já tenham versão de 64 bits, inclusive com bom suporte de drivers, o mesmo não pode ser dito ainda de todas as aplicações. Para acessar a internet no Windows 7, por exemplo, a única boa opção de browser 64 bits é o... Internet Explorer 8, que é odiado por outros motivos. (Felizmente, o Firefox 4 vem logo aí incluindo suporte oficial ao Windows em 64 bits.) Mas de que adianta o browser se ainda não há versões de 64 bits de plug-ins importantes como Flash, Silverlight e Java?

Opa! Na verdade existe, é claro, versão de 64 bits do Java; mas isso ainda não era muito útil para aplicações desktop ou web (applets e JAWS). No JDK 6u12, a Sun liberou a primeira versão do JRE de 64 bits com o novo JavaPlugIn pós-6u10, mas isso ainda com muitas limitações: sem o instalador JavaKernel, o Java QuickStarter, o novo Deployment Toolkit, Patch In Place, Download e Update automáticos; e sem a VM HotSpot Client. Também havia problemas em sistemas com as duas versões do JRE (32 e 64 bits) instalados simultaneamente.

Estas limitações do JRE de 64 bits estão sendo resolvidas; só vou falar da última. Somos obrigados a usar o HotSpot Server, cujas características de desempenho não são ideais para aplicações cliente. Uma solução seria disponibilizar o HotSpot Client para 64 bits, mas isso jamais irá acontecer por que um caminho ligeiramente diferente foi escolhido.

A Oracle está investindo no Tiered Compiler, que podemos traduzir por “compilador estagiado”. Funciona assim: a JVM unifica os compiladores JIT Client e Server, alternando dinamicamente entre ambos. Os métodos começam executando em modo interpretado, como de costume, tendo sua execução monitorada (profiling). Quando a VM detecta métodos que estão consumindo muita CPU, estes são processados inicialmente pelo compilador Client, que é rápido e consome poucos recursos, mas tem qualidade de otimização média. No entanto, o código produzido em modo Client continua sendo monitorado; para métodos que ainda consumam muita CPU, a VM faz um “segundo round” de compilação com o otimizador Server.

Distribuição de código com a VM Tiered
Figura 1. Distribuição de código com a VM Tiered.

O resultado, teoricamente, é o melhor de dois mundos – ver Figura 1. O processo deve ter um desempenho de código quase tão bom quanto o do Server “puro”, pois os métodos mais críticos receberão otimização máxima. Mas ao mesmo tempo deve ter um tempo de inicialização e consumo de memória quase tão bons quanto o Client “puro”: métodos não-críticos (executados poucas vezes) permanecem interpretados, e métodos de importância mediana (executados com alguma frequência) são compilados apenas em modo Client.

Se eu fosse o responsável pelo JDK 7, faria este release com uma dedicatória “in memoriam” a Steve Goldman – vulgo FatCatAir – o engenheiro que tocava sozinho o projeto do Tiered Compiler. O projeto foi interrompido quando Steve faleceu em junho de 2008, e ficou um bom tempo no limbo até ser continuado no projeto JDK 7.

[JDK] CompressedOops

Ainda na migração para 64 bits, outro problema é o consumo de memória. Muita gente está migrando seus desktops para SOs de 64 bits para poder usar 4Gb ou mais de RAM, ou por outras vantagens como segurança – randomização de endereçamento (ALSR) mais eficaz, proibição de técnicas legadas inseguras (ver PatchGuard do Vista e Windows 7), etc.

SOs de 32 bits podem usar mais de 4Gb, por exemplo com a extensão PAE da Intel – mas isso só é suportado em motherboards e SOs servidores, e tem limitações (processos individuais continuam limitados a 4Gb).

Mas o desempenho não costuma ser uma vantagem; pelo contrário, o normal é haver alguma perda. Num processo de 64 bits, todos os ponteiros têm o dobro do tamanho do que teriam num processo de 32 bits. Tanto o código quanto os objetos do heap usam mais espaço; a mesma aplicação costuma precisar de um heap de 30% a 50% maior. Além do desperdício de RAM, há também um custo em desempenho devido à menor eficiência de caches da CPU.

A plataforma Java não se notabiliza pela eficiência máxima no uso de memória, comparada com linguagens tradicionais como C. Mas curiosamente, na transição dos 32 para 64 bits, isso aconteceu. O JDK possui uma otimização conhecida como CompressedOops – onde “oops” é um termo do HotSpot para ponteiro-para-objeto, ordinary object pointer – que permite usar ponteiros de 32 bits, mesmo num processo de 64 bits com heap maior que 4Gb.

[JDK] Alinhamento de Memória

CPUs não gostam de endereçar a memória em posições arbitrárias, só em endereços “redondos” (segundo vários divisores, conforme a memória: RAM, cache L1 / L2 / L3, buffers DMA, etc.). Algumas dessas restrições são mascaradas, mas a exigência mínima que as CPUs fazem é a de endereços redondos conforme o tipo de dado: por exemplo, uma instrução que lê um valor de 32 bits exige um endereço múltiplo de 32 bits.

Tentativas de leitura ou escrita em endereço desalinhado, em muitas CPUs, resultam numa exceção de hardware (que causa crash do processo). Em CPUs mais boazinhas como as x86, o custo é apenas em desempenho. Por exemplo, uma leitura de 32 bits no endereço 11 é transformada em duas leituras de 32 bits alinhadas: uma lê os bytes 8-11 (usando apenas o byte do endereço 11) e outra lê os bytes 12-15 (usando os bytes em 12-14). Isso leva pelo menos o dobro do tempo que uma leitura de 32 bits alinhada.

Para contornar o problema, todas as linguagens de programação alinham os objetos, variáveis, e rotinas de código segundo as exigências da CPU. Isso tem algum custo em memória, já que nem todos os objetos têm tamanho múltiplo do alinhamento. Para um alinhamento de 8 bytes, haverá um desperdício médio de 4 bytes após o final de cada objeto. Mas este custo é inevitável, com ou sem truques como o CompressedOops.

Distribuição de código com a VM Tiered
Figura 2. Compressão / descompressão de referências (para 35 bits / 32Gb).

Qualquer ponteiro do Java sempre tem os 3 bits menos significativos zerados, pois todos os objetos e rotinas de código são alinhados em endereços múltiplos de 8 (ver quadro “Alinhamento de Memória”). O HotSpot de 64 bits pode tirar proveito do alinhamento. A VM suporta ponteiros de 35 bits “virtuais”, permitindo heaps de até 32Gb. Mas estes ponteiros podem ser armazenados em 32 bits físicos. Como os 3 bits menos significativos são sempre zero, basta fazer um shift de três bits para a direita para “comprimir” o ponteiro. Para “descomprimir”, basta carregar o ponteiro num registrador de 64 bits e fazer o shift oposto para a esquerda; ver Figura 2. Esses shifts adicionais teriam um custo de desempenho, mas a maior parte desse custo é anulada por otimizações que eliminam a maior parte dos shifts ou os posicionam fora de regiões críticas como loops.

Esta otimização, habilitada por –XX:+UseCompressedOops, já era disponível desde o JDK 6u14, para heaps de até 32Gb. Agora com o JDK 7, além de mais eficiente, existe também a capacidade de suporte a heaps de até 64Gb, o que é feito aumentando o alinhamento para 16 bytes: assim temos ponteiros de 36 bits virtuais, também comprimidos para 32 bits. Esse alinhamento extra tem um custo (aumenta o desperdício de memória para uma média de 8 bytes por objeto), mas esse custo ainda é menor do que aquele causado por ponteiros de 64 bits.

Se o leitor acha que essa conversa de heaps de até 32Gb ou 64Gb é coisa de maluco, lembre-se das palavras de Bill Gates em 1981: “640Kb devem ser suficientes para todo mundo”. J Mesmo para heaps pequenos, a otimização CompressedOops ainda é útil, pois reduz o consumo de RAM.

Se o limite máximo do heap for inferior a 4Gb, aqueles shifts de 3 ou 4 bits para lá e para cá nem são necessários: o HotSpot simplesmente ignora os 32 bits mais significativos dos ponteiros e só armazena os 32 bits inferiores (tomando o cuidado de alocar o heap num endereço virtual igual a 0). Assim, o desempenho será ainda melhor.

[JDK] XRender no Unix e Linux

Quando se trata de GUIs, os sistemas Linux e Unix são o patinho feio, e não dá para jogar toda a culpa em terceiros – embora estes tenham alguma culpa, por exemplo não fornecendo bons drivers de vídeo. Mesmo deixando de lado a maioria da família Unix (AIX, Solaris, HP-UX, *BSD etc.) e focando só no Linux, que tem de longe o melhor desktop entre seus pares, suas tecnologias de GUI estão muito atrás do Windows e Mac OS X. Um dos principais obstáculos me parece ser o X11, o padrão de GUI “velho de guerra” do Unix, projetado no passado remoto, para hardwares e cenários de uso radicalmente diferentes dos atuais. É interessante observar que o único grande sucesso comercial de Linux com GUI é a plataforma Android... que não usa o X11.

Nota para os fãs do Linux: não estou julgando usabilidade ou estética, ou disponibilidade de boas aplicações. Essas coisas são um tanto subjetivas, e bons programadores podem tirar “leite de pedra” e contornar limitações da plataforma. Estou tratando apenas da tecnologia – todo o stack incluindo drivers de vídeo / som / mídia, modelo de drivers do kernel, gerenciador de janelas, composição de desktop com aceleração de GPU, APIs de gráficos e GUI, codecs, fontes (da rasterização ao typesetting), tecnologias recentes como GPGPU, etc.

Uma das soluções é a pipeline da Java2D para OpenGL. Mas esta pipeline tem desvantagens, como exigir bons drivers OpenGL (nem sempre disponíveis mesmo no Windows), e não se integrar bem a alguns recursos de desktops baseados no X11. E mesmo quando funciona, a OpenGL não garante o melhor desempenho possível, pois é uma API complexa e que não dá suficiente grau de controle sobre certas operações (em especial antialiasing; por isso, a pipeline Java2D/OpenGL é obrigada a fazer todo o antialiasing em software.)

Mas nem tudo está perdido: o X11 possui a extensão XRender, disponível desde 2000, que suporta um conjunto mais moderno de primitivas de renderização de gráficos e texto, e torna mais fácil e eficiente o acesso à funcionalidade das GPUs modernas. O XRender permite acelerar praticamente todos os recursos usados pela Java2D, com uma exigência muito menor sobre os drivers de vídeo. Ao mesmo tempo, mantém compatibilidade total com o X11, inclusive em conexões de desktop remotas via X Protocol (outro cenário problemático para a OpenGL). A nova pipeline da Java2D integra-se também com o Cairo, uma biblioteca open source de gráficos 2D que suporta o XRender.

Desempenho da Java2D/XRender, em comparação com a
pipeline tradicional para X11, com testes para várias GPUs da Intel, Nvidia e
ATI
Figura 3. Desempenho da Java2D/XRender, em comparação com a pipeline tradicional para X11, com testes para várias GPUs da Intel, Nvidia e ATI.
Vantagem
relativa da Java2D/XRender, para sessão desktop via rede (X Protocol)
Figura 4. Vantagem relativa da Java2D/XRender, para sessão desktop via rede (X Protocol).

Os benchmarks de openjdk.java.net/projects/xrender/benchmarks.html, que copio nas Figuras 3 e 4, mostram a grande vantagem da nova pipeline. A diferença é especialmente notável no teste de conexão remota, que com o XRender, executa praticamente com o mesmo desempenho de uma sessão local. XAA e EXA são arquiteturas de antialiasing do X11; o EXA é mais moderno, dedicado ao XRender e tem desempenho superior, mas ainda está amadurecendo.

Em resumo, a nova pipeline XRender promete colocar o Linux e Unix em pé de igualdade com o Windows e OS X, no desempenho e qualidade de GUIs Java. Será um passo definitivo para resolver a “portabilidade de fato” das GUIs feitas em Java, que até então, sofriam desvantagens importantes de desempenho nas plataformas baseadas em X11.

Note que isso tudo vale só para GUIs baseadas em AWT/Java2D/Swing, e talvez para a JavaFX com seu toolkit Swing. A JavaFX com o Prism tem uma arquitetura diferente: em sessões locais não precisa do XRender, e em sessões remotas não deve ser possível de acelerar (o Prism é muito “envenenado” para acesso direto à GPU). Não influi na SWT do Eclipse, que também não usa a Java2D, mas a SWT já suporta o XRender e Cairo.

[Java SE] JSR-292

A JSR-292 já foi bastante comentada aqui (ver Edição 74), tentarei então ser breve e apresentar uma visão diferente. Esta JSR dá continuidade à “abertura” da plataforma para outras linguagens, iniciada no Java SE 6 com a JSR-223 (javax.script). Mas não se trata apenas de suporte a linguagens de scripting ou dinamicamente tipadas. A JSR-292 é uma poderosa revisão das capacidades da JVM, que dá suporte a conceitos importantes para várias técnicas de programação, como:

  • Invocações dinamicamente tipadas: bytecode invokedynamic;
  • Referências para métodos: MethodHandle: úteis nas invocações dinâmicas, mas também em outros cenários, como lambdas;
  • Técnicas de dispatch customizado: por exemplo para implementar o MOP (Meta Object Protocol) de linguagens meta-programadas como Ruby, Groovy, Smalltalk etc.;
  • Injeção de Interface: uma classe já carregada e instanciada, pode ser dinamicamente modificada de forma a implementar uma nova interface;
  • Classes Anônimas: não é o mesmo que as inner classes, que só são anônimas no código-fonte mas viram classes normais e completas no bytecode;
  • Liberdade Simbólica: nomes de classe, método etc., mais independentes da linguagem Java.

Os itens acima farão parte do Java SE 7, a maior parte já especificados na JSR-292. Há ainda outros itens sendo investigados e prototipados no projeto MLVM: Métodos Autônomos, Continuations e Introspecção do Stack, Tail Calls e Tail Recursion, Fixnums, Tuplas... ideias que mais provavelmente aparecerão em builds do OpenJDK, e poderiam ser padronizadas no Java SE 8.


Para que serve tudo isso? São novas capacidades fundamentais da JVM, úteis basicamente para melhorar o desempenho de técnicas que até então exigiam código complexo e ineficiente. Mesmo quem não usa uma linguagem dinâmica como Groovy deve usar, por exemplo, frameworks como o Hibernate (tradicional ou JPA), que faz uso intenso de geração dinâmica de bytecode. Os recursos da JSR-292 permitirão que essa geração de código seja mais rápida, consuma menos memória, e tenha menos problemas como leaks de código (quando classes geradas dinamicamente ficam entupindo o heap, pois são “seguradas” por classloaders: as novas Classes Anônimas, além de ultra-leves, não são associadas a nenhum classloader).

Os recursos da JSR-292 não serão tipicamente utilizados de forma direta em código de aplicações. Serão usados por compiladores, frameworks avançados, middlewares e tecnologias de runtime em geral, produzindo benefícios para as aplicações que rodam sobre os mesmos.

[Java SE] Melhorias de ClassLoaders

Este item compreende um único bugfix, e um único novo método na API. Por que é tão importante para ser destaque do JDK 7? É que o bug é o 4670071, cujo fix torna o carregamento de classes mais flexível e confiável, permitindo por exemplo que vários threads carreguem classes em paralelo, através de classloaders distintos, sem risco de deadlock. E a nova API, URLClassLoader.close(), é crítica para permitir a algum container que gerencie classloaders (por exemplo, o Java PlugIn ou um servidor Java EE) libere recursos, como arquivos jar e conexões remotas, tão logo estes deixem de ser necessários.

No caso do bug 4670071, que não exige nenhuma nova API pública, esta correção/melhoria já foi despachada para o JDK 6 a partir do seu Update 18.

O resultado de ambas as melhorias será processos Java mais eficientes e “bem comportados”, com menor tempo de inicialização, e menos problemas como retenção desnecessária de arquivos abertos. Estas vantagens serão maiores para aplicações que carregam muitas classes, ou têm uma hierarquia de classloaders complexa – basicamente, servidores Java EE com muitos EARs/WARs instalados.

[Java SE] JLayer

Em meio às reclamações dos fãs da Swing, que acusam a Sun/Oracle de ter abandonado as APIs de GUI tradicionais do Java SE em favor da JavaFX, é interessante destacar que a maioria das demandas não atendidas poderiam facilmente ser implementadas por bibliotecas externas: Binding, Application Framework, componente para seleção de datas, etc. Esses projetos de terceiros já existem, e o maior problema do status não-oficial é que a Oracle não custeia seu desenvolvimento... mas se a comunidade da Swing, inclusive empresas que vendem ferramentas e componentes, não tem massa crítica ou organização para assumir o esforço de desenvolvimento de algumas bibliotecas, talvez a Oracle esteja mesmo certa em abandonar a Swing.

Eu até poderia acrescentar (se fosse maldoso...) que a principal iniciativa de terceiros para criar uma plataforma RIA para Java, o Apache Pivot, também abandona a Swing, ao invés de melhorá-la.

O que podemos cobrar da Oracle é que dê continuidade ao trabalho que não pode ser feito de fora, como correções de bugs das APIs do núcleo (AWT, Java2D e Swing), e novas funcionalidades críticas que exigem (ou se beneficiariam muito) de código nativo, suporte da VM, ou acesso privilegiado ao núcleo do JRE. É o caso do XRender, que já vimos, e também do JLayer.

O JLayer é descrito pelos javadocs como um “decorador universal para a Swing, que permite implementar vários efeitos avançados de pintura, bem como receber todos os eventos da AWT”. Um JLayer pode ser usado para customizar a pintura de algum outro componente (por exemplo um JPanel ou JButton), sem ter que alterar o código do próprio componente, e de forma muito mais simples do que seria previamente possível com outras técnicas.

Demo do JLayer, com um efeito especial de animação da pintura de componentes da
Swing
Figura 5. Demo do JLayer, com um efeito especial de animação da pintura de componentes da Swing.

A Figura 5 é um demo simples do JLayer (ou mais propriamente do JXLayer, projeto da java.net que migrou para o JDK 7 transformando-se no JLayer). Todos os componentes do programa têm sua pintura customizada de forma a animar todas as regiões repintadas, variando sua cor a partir do preto, em resposta a eventos. Para obter esta figura, corri com o mouse rapidamente sobre os controles centrais, que ficaram em diferentes estágios dessa animação.

Em resumo, o JLayer permite decorar componentes da Swing com muitos tipos de “efeitos especiais”, de forma simples e eficiente... pelo menos, para os padrões da Swing. Não espere nada tão simples (ou tão bem acelerado pela GPU) quanto os efeitos da JavaFX. A Swing pode ser melhorada, mas até certo ponto; milagre não existe. ;-)

[Java SE] Atualizações do stack XML e web services

As APIs de XML e web services foram atualizadas:

  • JAXP 1.4 (incluindo StAX 1.2). São atualizações puramente de manutenção (bugfixes e desempenho), sem alterações de API. Também incluídas no Java EE 6 e no JDK 6u18;
  • JAXB e JAX-WS 2.2. Incluem pequenas atualizações de API, e foram também incluídos no Java EE 6, mas não foram nem serão incluídos no Java SE 6 (o JDK 6 continua na versão 2.1).

Pouca novidade em termos de APIs; todos os itens já foram atualizados no Java EE 6, e alguns até no Java SE 6. Mesmo as atualizações 2.2 da JAXB / JAX-WS foram modestas. Assim, o aspecto mais interessante do Java SE 7 é o fato de ficar 100% alinhado com a versão mais atual do Java EE, para todas as APIs de XML e web services. Esse alinhamento é importante, por exemplo, ao se criar clientes Java SE que se comunicam com servidores Java EE através de web services que usem recursos avançados ou exijam os últimos fixes de compatibilidade com padrões.

[JDK] SDP e SCTP

http://java.sun.com/developer/technicalArticles/javase/jdk7-sctp/index.html

Estes itens vão na conta do suporte a aplicações peso-pesadas. E com isso quero dizer, aplicações que rodam em clusters ou mainframes com links Infiniband (entre outros padrões similares) que chegam até 300Gbit/s. Os protocolos de transporte Internet que todos conhecemos, TCP e UDP, são inadequados para esses tipos de conexões, que além da alta velocidade também exigem baixíssimas latências. Precisamos então de novos protocolos, como a dupla SDP (Sockets Direct Protocol) e SCTP (Stream Control Transmission Protocol).

Dificilmente algum programador de aplicações Java terá que conhecer estes protocolos ou as suas novas APIs, pelo mesmo motivo pelo qual pouca gente programa sockets TCP/UDP hoje em dia: o desenvolvimento de aplicações distribuídas já foi elevado para um nível mais alto, e quase sempre, programamos numa camada superior de abstração, com middlewares como EJB (RMI/IIOP), HTTP, web services REST ou SOAP, JMS, JDBC, caches distribuídos, etc. Só quem implementa esses componentes terá que usar coisas como SCTP. Se for o seu caso, o link acima introduz o assunto e a API. Note que você não encontrará nenhuma nova API no package java.net, já que pela natureza destes protocolos, só faz sentido usar o modelo “acelerado” da New I/O.

Para ser exato, a API está no package não-padrão com.sun.nio, e deverá ficar assim no Java SE 7. A API será promovida para o package padrão java.nio num release futuro (presumivelmente Java SE 8), pois é tecnologia ainda muito recente. O SCTP foi definido inicialmente no ano 2000, mas o padrão atual (RFC 4960) é de 2007, e ainda não é universalmente suportado – por exemplo, não há suporte nativo no Windows (há algumas implementações de terceiros).

Outra restrição: este suporte inicial será exclusivo para a plataforma Solaris 10. Mas não tenho dúvida que a turma do OpenJDK irá fazer o porte necessário para que a API preliminar funcione no Linux e *BSD, os quais também contam com suporte para SDP/SCTP.

[Java SE] Unicode 5.2

O Java SE 6 suporta o padrão Unicode na versão 4.1 (com 97.720 caracteres); no Java SE 7, este suporte é atualizado para a versão 5.2 do padrão, a mais recente (com 107.361 caracteres). Além do acréscimo contínuo de novos caracteres, essas atualizações sempre possuem erratas (bugfixes). O padrão Unicode não é uma mera tabela de caracteres, inclui regras de normalização de codificações e outras encrencas, que precisam ser suportadas pelos algoritmos do Java SE em APIs como Character, String, java.text, java.util.regex, javax.xml, etc. Raramente as atualizações de Unicode implicam em novas APIs, somente em melhoria de comportamento dessas APIs já existentes, que passam a funcionar corretamente com inputs contendo novos caracteres.

A internacionalização é importante para um número crescente de projetos, e a plataforma Java se esmera em manter-se atualizada com o padrão Unicode – nunca se sabe quando aquele seu sistema terá que ser vertido para Birmanês ou Aramaico Imperial... ou sendo realista, talvez Japonês.

Aproveitando a deixa, para nós no Brasil é ainda mais importante reparar que o Oracle JRE, já a partir da versão 6u21, acrescenta localização do próprio JRE – em especial seus componentes mais visíveis pelo usuário final, como o WebStart – para o Português Brasileiro (pt_BR).

[Java SE] JSR-308: Anotações em Tipos

O suporte do J2SE 5.0 para anotações (JSR-175) revolucionou o design de APIs e frameworks para as plataformas Java SE e EE. As anotações substituíram descritores XML (e outras abordagens) com vantagens, permitindo a adoção do paradigma de configuração-por-exceção e dando suporte a frameworks mais dinâmicos e fáceis de usar. A plataforma J2EE 5.0, a primeira a adotar anotações em larga escala e de forma integrada em várias APIs, teve sucesso amplamente reconhecido em restaurar a sanidade do desenvolvimento J2EE, que havia se tornado sinônimo de complicação e de programação verbosa e burocrática.

Mas as anotações tinham algumas limitações, que inibiam certos cenários interessantes de uso. Estas limitações foram basicamente resolvidas agora, no Java SE 7, com a JSR-308. Resumindo a diferença: o Java 5 permitia o uso de anotações em praticamente qualquer declaração de tipo; por exemplo, uma declaração de classe, método ou atributo. Mas no Java 7, poderemos usar anotações em qualquer uso de um tipo. Somando tudo: onde há algum tipo, pode haver uma anotação.

Listagem 1. Exemplo de anotações da JSR-308.


  @DefaultQualifier(@NonNull)
  class DAG {
   
    Set<Edge> edges;          
   
    // ...
   
    List<Vertex> getNeighbors(@Interned @Readonly Vertex v) @Readonly { 
      List<Vertex> neighbors = new LinkedList<Vertex>();
      for (Edge e: edges)                
        if (e.from() == v)              
          neighbors.add(e.to());      
      return neighbors;
    }
  } 

Vamos usar o exemplo da própria especificação. Na Listagem 1, vemos algumas anotações em lugares que não eram anteriormente permitidos:

  • @Interned indica que o parâmetro v tem igualdade por identidade: nunca há instâncias separadas representando o mesmo objeto, portanto getNeighbors() pode usar comparação por referência (e.from() == v), ao invés de igualdade (e.from().equals(v)) que seria mais custosa;
  • @Readonly garante que getNeighbors() não irá modificar o objeto referenciado pelo parâmetro v;
  • Um segundo @Readonly, após o ‘)’ da declaração do método, é uma anotação aplicada sobre o parâmetro oculto this – o receiver. No caso, isso garante que uma chamada x.getNeighbors() também não irá modificar o objeto x, sobre o qual o método foi invocado;
  • O @DefaultAnnotation(@NonNull) é uma anotação em declaração, mas é uma nova facilidade introduzida pela JSR-308. No exemplo, isso determina que a anotação @NonNull será aplicada por default para todas as declarações da classe (que suportem esta anotação). Atualmente é possível criar anotações com essa propagação de valores default, mas isso tem que ser feito de forma ad-hoc para cada tipo de anotação criada.

A JSR-308 tem outras novidades que estão sendo discutidas, não havendo certeza no momento em que escrevo se serão incluídas no Java SE 7: repetições do mesmo tipo de anotação no mesmo elemento (por exemplo várias anotações @Resource – sem ter que agrupá-las num array dentro de um @Resources); herança entre anotações; e anotações potencialmente recursivas (isso permitiria construir estruturas hierárquicas de anotações tão complexas quanto um documento XML).

Veja o quadro “JSR-305 e/ou Checker Framework: Anotações para Qualidade de Software” para o primeiro exemplo concreto da utilidade das novas capacidades de annotations.

JSR-305 e/ou Checker Framework: Anotações para Qualidade de Software

Este item não leva no título nem [JDK], nem [Java SE], pois seu status ainda é pouco definido no momento em que escrevo. Há uma JSR para o assunto, mas o planejamento do Java SE 7 não a lista como um componente confirmado; também não pude encontrar nenhuma informação pública e atual sobre o andamento da JSR, que parece ter sido abandonada. E o motivo talvez seja o aparecimento de outra iniciativa mais recente que tomou o seu lugar, o Checker Framework – este sim, referenciado pelos documentos JSR-308, portanto parece tratar-se de uma ferramenta que será integrada ao JDK 7 (mas ainda não se pode saber se, também, ao padrão Java SE 7).

A ideia de ambos é definir anotações que complementam o sistema de tipos do Java, permitindo ao programador acrescentar informações que auxiliam o javac a fazer várias validações adicionais sobre o código. Pense em ferramentas como FindBugs ou PMD, só que muito melhores: fortemente integradas ao compilador (e possivelmente IDEs), com anotações padronizadas para “dicas” do programador, e bem mais inteligentes.


  public class Teste {
    String x () { return "*"; }
    String y () { return null; }
    void teste (boolean a) {
      @NonNull String ref = a ? x() : y();
    }
  } 

Explicando com o exemplo simples acima: na parte óbvia, a anotação @NonNull declara que a variável ref não pode jamais ser nula. Mas se você passar esse código pelo javac, nada diferente acontecerá: a anotação será ignorada, não tem influência na linguagem.

Podemos, no entanto, fazer uma execução especial do javac que utiliza “plug-ins” do Checker Framework. Estes plug-ins executam validações de acordo com as anotações do mesmo framework. Um dos plug-ins valida a anotação @NotNull, e isso funciona assim: o código é analisado como se essa anotação fizesse parte dos tipos. Por exemplo, o tipo da variável ref não é apenas “referência para java.lang.String”, e sim “referência não-nula para java.lang.String”. Esta modificação do sistema de tipos não é normalmente permitida (as annotations são explicitamente proibidas de alterar a semântica da linguagem Java), é por isso que precisamos dos plug-ins externos, cujo acionamento não ocorre por default, exige um parâmetro -processor que carrega o plug-in. Se fizermos esta validação no nosso exemplo, teremos um resultado como:


  >c:\Java\Checkers\binary\javac
  -processor checkers.nullness.NullnessChecker Teste.java
   
  Teste.java:5: incompatible types.
    String y () { return null; }
                         ^
    found   : null
    required: @NonNull String
  1 error 

Essa mensagem de erro mostra como o plugin do Checker Framework realmente estende o sistema de tipos reconhecido pelo javac, gerando um erro parecido com o que teríamos, por exemplo, ao tentar atribuir um int para uma String.

Note que o problema não é trivial de detectar. Começa no método teste(), onde temos uma atribuição ref = a ? x() : y(). Devido ao operador condicional ?:, esta atribuição só é válida se tanto x() quanto b() tiverem o tipo desejado. A chamada a x() produz um objeto não-nulo, então está OK. Já o método y() retorna null, é neste ponto que o erro de tipagem é localizado e reportado. Esse comportamento do framework me deixou bastante impressionado, pois sei que não é fácil fazer isso. Modificando y() para retornar um valor não-nulo, nenhum erro é reportado. Mas se anotarmos o retorno de y() com a notação oposta @Nullable, o erro será novamente identificado, mas num lugar diferente:


  >c:\Java\Checkers\binary\javac
  -processor checkers.nullness.NullnessChecker Teste.java
   
  Teste.java:8: incompatible types.
      @NonNull String ref = a ? x() : y();
                                             ^
    found   : @Nullable String
    required: @NonNull String
  1 error 

Agora o validador acusa o resultado do operador ?:, o ponto onde o erro de tipagem acontece. Vemos que o Checker Framework não só é capaz de detectar esses erros, mas consegue fazê-lo de forma inteligente, apontando sempre a “raiz do problema”. Também parece exigir um mínimo de anotações; no exemplo, só precisamos colocar o @NonNull na variável ref, não precisamos de nenhuma anotação nos métodos x() e y().

A versão atual do framework funciona com o Java SE 6, mas exige executar uma versão modificada do javac, como podemos ver pela linha de comando do exemplo. Também é preciso incluir jars adicionais (que contêm as anotações do framework) ao classpath de compilação dos seus fontes, mesmo quando nenhum validador estiver sendo executado. Estas restrições serão provavelmente removidas no Java SE 7 (ou pelo menos no JDK 7), restando apenas a necessidade de chamar o javac com o parâmetro –processor (e esta parte, certamente, poderá ser automatizada por IDEs atualizados).

Os avanços que começaram com a JSR-305, e culminam com o Checker Framework, são derivados de projetos mais antigos de validação de código Java, como o FindBugs (cujo autor, Bill Pugh, foi também o líder da JSR-305). Imagino que o Checker Framework irá tornar algumas funcionalidades destas ferramentas obsoletas; as restantes provavelmente serão retrabalhadas para encaixarem-se no framework, na forma de validadores adicionais que também poderão ser integrados ao seu build.

[Java SE] JSR 166y

Continuando a tradição iniciada pela JSR-166, original do J2SE 5.0, temos mais um lote de APIs de concorrência (java.util.concurrent). A JSR continua sendo a mesma; o sufixo “x” foi utilizado para as atualizações feitas para o Java SE 6, e o sufixo “y” para o Java SE 7. (Como o alfabeto só vai até o “z”, imagino que será preciso inventar codinomes diferentes a partir do Java SE 9.).

A JSR-166y traz duas novidades principais:

  • ForkJoin Framework (FJ): Uma sub-API para computação paralela, para problemas CPU-intensivos e do tipo “divisão-e-conquista”. Por exemplo, a renderização de uma imagem de 2000 x 2000 pixels poderia ser dividida em 16 subtarefas, cada uma delas renderizando um bloco de 500x500 pixels, de forma que a computação total será até 16 vezes mais rápida se tivermos uma máquina com 16 núcleos/CPUs. As tarefas também podem ser decompostas recursivamente;
  • Parallel*Array (PA): Estende o FJ, modelando um array que suporta operações paralelas. Por exemplo, existe um método ParallelArray.binarySearch() similar a Arrays.binarySearch(), com a diferença que a versão de PA é capaz de usar vários cores/CPUs – até centenas ou milhares, se você quiser. Além de operações “básicas” como pesquisa, max(), sort() etc., há também métodos mais avançados suportando o paradigma “map/reduce”, por exemplo.

Destes dois itens, no momento em que escrevo somente o ForkJoin Framework está confirmado para inclusão no Java SE 7. O Parallel*Array ainda está na corda bamba, em parte devido à especificação de lambdas (ver seção “O Estado do Lambda”). Mas neste caso, será disponibilizado como uma biblioteca, e poderá ser incluído num release futuro do Java SE.

Ganho de desempenho do ForkJoin Framework para diversos benchmarks
Figura 6. Ganho de desempenho do ForkJoin Framework para diversos benchmarks.

A Figura 6, de gee.cs.oswego.edu/dl/papers/fj.pdf, mostra a capacidade de paralelização de vários benchmarks com o FJ. No melhor resultado, o teste Integ (um algoritmo de integração numérica) ficou 28,2 vezes mais rápido com 30 threads. (Isso exige, é claro, ter pelo menos 30 cores/CPUs.)

[Java SE] JSR-203: New I/O 2.0

A java.nio foi originalmente introduzida no J2SE 1.4 (JSR-51), com a missão de preencher as lacunas da java.io, oferecendo um modelo mais avançado e eficiente de I/O, em especial com suporte a operações não-bloqueantes / polling. A adoção desta API foi lenta por que sua implementação inicial deixava bastante a desejar, mas já no Java SE 6 ficou bastante robusta, e passou a ser amplamente utilizada (principalmente por middlewares e componentes de infraestrutura em geral). Então o interesse no avanço das capacidades de I/O foi renovado, e o resultado é esta nova JSR que desenvolve ainda mais funcionalidades:

  • Nova API de Filesystem: permite acesso a todos os atributos de arquivos, suporta notificações de mudança, escapes para APIs específicas a cada filesystem;
  • Operações de I/O Assíncronas: Para arquivos e sockets;
  • Funcionalidade completa para SocketChannel: Complementa a JSR-51, que havia definido esta API mas deixou algumas lacunas.

Os dois últimos itens, provavelmente, serão também de uso quase exclusivo de desenvolvedores de middleware – containers Java EE, drivers, protocolos, SGBDs, etc. Já a API de Filesystem, no novo package java.nio.file, tem apelo mais direto para código de aplicações. Se você sempre reclamou das APIs simplórias do Java para arquivos, seus problemas acabaram! A nova API permite lidar com links, atributos estendidos e vários ouros detalhes; tem métodos bem mais simples, poderosos e eficientes para lidar com paths (resolução, normalização, relativização etc.), ou para operações comuns como move/rename; permite “monitorar” arquivos de forma que a aplicação recebe uma notificação quando alguma mudança acontece, entre várias outras funcionalidades.

Esta nova interface de filesystem é uma solicitação tão antiga da comunidade Java, que o bug correspondente (bugs.sun.com/view_bug.do?bug_id=4313887), cadastrado em fevereiro de 2000, chega a ser cômico: seu campo “Reported Against” lista as versões “1.1, 1.2, 1.3, 1.4, b04, b05, 1.0.1, 1.0.2, 1.1.1, 1.1.2, 1.1.3, 1.1.4, 1.1.5, 1.1.6, 1.1.7, 1.2.2, 1.3.1, 1.4.1, 1.4.2, tiger, 1.2beta2, 1.2beta3, 1.2beta4, 1.4.2_05, merlin-beta, tiger-beta2, merlin-beta2, merlin-beta3, kest-linux-fcs”, e há em torno de 60 “Related Bugs”. Bem... antes tarde do que nunca!

JSR-294 e Jigsaw

Tratamos destes itens em “JVM Language Summit” (Edição 74), há um ano – no momento em que escrevo a edição 2010 já está acontecendo... mas sem nenhuma nova apresentação sobre o Jigsaw. O problema mais importante é que o destino do projeto parece estar suspenso neste momento (final de julho). Toda atividade visível – na lista jigsaw-dev e no repositório Mercurial do OpenJDK – foi completamente interrompida há dois meses, no comecinho de junho, embora o projeto parecesse já estar próximo do fim. Por outro lado, em 23 de julho o build 102 do JDK 7 incluiu uma pequena alteração originária do Jigsaw, mas foi algo ainda muito pequeno para podermos concluir que o Jigsaw começou a “aterrissar” no JDK 7.

Para evitar qualquer risco de uma nova situação como aquela dos lambdas, preferi deixar o assunto em aberto. Se o Jigsaw for confirmado, terá o tratamento necessário num artigo futuro. Aliás, é daqueles assuntos que certamente exigiriam um artigo dedicado.

Nota: Acho precipitado concluir que o Jigsaw foi cancelado. Nos últimos meses, tenho aprendido que a Oracle é uma empresa muito diferente da Sun. Nas palavras de um engenheiro de outro time (JavaFX) que acompanho no Twitter, “A Oracle não anuncia coisas com antecedência (...) às vezes é preciso ter alguma paciência.”

Criptografia de Curva Elíptica (ECC)

Pra terminar com um item realmente obscuro, o Java SE 7 irá incluir novos, modernos algoritmos de criptografia ECC. Emprestando o bordão de Silvio Santos, “eu não vi esse filme, mas minha esposa viu e diz que é ótimo!” – não entendo quase nada de algoritmos de criptografia, mas os entendidos dizem que ECC é melhor que pão quentinho, especialmente por que permite usar chaves de criptografia muito menores que outros algoritmos para obter um mesmo grau de segurança. Enfim: uma arma a mais, no arsenal de segurança do Java SE.

Conclusões

O Java SE 7 tem poucas APIs novas, mas as poucas novidades de API são bastante críticas (em especial as de I/O), e o volume de melhorias que não envolvem APIs – os “detalhes de implementação” – é enorme. É mais um salto qualitativo da plataforma fundamental do Java, oferecendo novas capacidades e tornando outras possíveis de implementar.

Vários itens cobertos aqui – new I/O, ForkJoin, JSR-292... – precisam de um artigo inteiro (ou mais) para uma cobertura completa, até por isso não tentamos ensaiar explicações mais detalhadas ou exemplos de codificação. Provavelmente aguardarei um pouco (pelo menos até saírem os primeiros Release Candidates), para que estes artigos, que serão mais práticos, alcancem o leitor num momento em que serão imediatamente aplicáveis, com o release de produção do JDK 7 já disponível e pronto para ser adotado.