JRuby + Web: Por que usar Java com JRuby on Rails?

Propaganda
capa
JRuby + Web: Por que usar
Java com JRuby on Rails?
Entenda as vantagens de se usar Java para rodar código
Ruby, inclusive aplicações JRuby on Rails.
Fabio Kung
([email protected]):
é
engenheiro
da
Computação pela Escola Politécnica da USP e possui
experiência na Alemanha, onde trabalhou com Java para
a Web. Palestrante em alguns eventos de tecnologia como
o Conexão Java, JustJava, Falando em Java, WebMobile
TechWeek, RejectConf e Rails Summit Latin America.
Possui as certificações SCJP, SCBCD 5, SCEA 5 e Ruby
Certified Programmer Silver. Também participa de alguns
projetos opensource como o VRaptor, Waffle, GUJ, Caelum
Stella e JettyRails, além de estar bastante envolvido com a
comunidade de Ruby no Brasil. Trabalha na Caelum como
instrutor, desenvolvedor e consultor.
52 www.mundoj.com.br
JRuby tem ganhado muita populari
popularidade nos últimos tempos, por ser
a única alternativa viável à impleme
implementação oficial da linguagem Ruby,
conhecida como MRI. É considerada a implementação mais rápida da
linguagem Ruby, chegando a ter performance
p
média quatro vezes
superior. Além disso, JRuby é o únic
único que já implementa quase todas
as mudanças introduzidas na versão 1.9 da linguagem Ruby, além da
implementação oficial, a YARV.
Como pode uma implementação que roda sobre a JVM (camada extra)
ser a mais veloz? Quais são as vantagens
vanta
de executar código Ruby na
JVM e como podemos tirar proveito disso? Como e por que executar
aplicações Web feitas com o Ruby o
on Rails usando tecnologia Java?
Este artigo busca
busc
sca mostrar alguns detalhes
det
de funcionamento do JRuby
e porque ele est
stá
st
á se tornando tão po
está
popular.
$"1"t+3VCZ8FC1PSRVFVTBS+BWBDPN+3VCZPO3BJMT
D
esde o início, Yukihiro Matsumoto (Matz) teve como objetivo
criar uma linguagem o mais legível e expressiva possível. Praticamente, nenhuma das decisões no projeto de Ruby favorece possíveis
implementações da linguagem: interpretadores, parsers, compiladores e
máquinas virtuais. Por causa disso, muitos consideram Ruby uma linguagem difícil de implementar de forma eficiente. Não foi projetada para
ser rápida, mas sim legível; portanto estaria condenada a ser uma das
linguagens mais lentas. Performance não é o foco, e para grande parte
das aplicações ela é simplesmente suficiente.
Mesmo assim, os implementadores da linguagem têm feito um trabalho
excepcional. Hoje existem diversas implementações da linguagem como
o IronRuby, MacRuby, Rubinius e MagLev. A maioria destas implementações ainda tem como foco a compatibilidade, ou seja, conseguir rodar
as aplicações e código Ruby existentes; inclusive as que usam o Rails. O
GBNPTP GSBNFXPSL QBSB EFTFOWPMWJNFOUP8&# o 3VCZ PO 3BJMT o GVOciona como um grande divisor de águas para as implementações da
linguagem Ruby: suportar aplicações escritas usando o Ruby on Rails é o
grande indicativo de compatibilidade.
Entre todas as implementações, a que tem mais destaque é o JRuby,
a única que pode se “dar ao luxo “de não se preocupar mais tanto com
compatibilidade.
Já há mais de um ano que os seus desenvolvedores, liderados por
Charles Nutter, têm trabalhado em melhorar a performance. O JRuby é
atualmente a única implementação já compatível com a versão 1.8.6 da
linguagem Ruby e o suporte à versão 1.9.1 está a todo vapor e quase
completo. JRuby tem puxado a qualidade de todas as outras implementações para cima, estimulando a competitividade entre elas.
Os problemas do interpretador MRI
A implementação oficial do Ruby 1.8 é conhecida como Matz Ruby Implementation/Interpreter (MRI) ou CRuby, já que é escrita em C. Alguns
também propõem que a sigla seja lida como Matz Reference Implementation. Como o foco do Matz sempre foi a legibilidade e expressividade
da linguagem, nunca favorecendo implementação, o interpretador é
bem simples em diversos aspectos.
Puro interpretador
O interpretador MRI opera na forma mais lenta para execução de código
hoje disponível: a pura interpretação. Isso significa que o interpretador
vai andando pelo código e executando o código de máquina associado.
As melhores implementações, para a maioria das linguagens de última
geração, costumam ser baseadas em máquinas virtuais. Um bom motivo
(discutiremos outros) é pensar na portabilidade: o interpretador precisa
ter grande parte do seu código reescrito para cada nova plataforma que
deseja ter uma implementação de Ruby, o que dificulta a difusão da
linguagem em diversas plataformas.
Threads, ou threads em espaço de usuário (user space), já que o próprio
processo do interpretador as ativa e desativa (escalonamento), e não o
Sistema Operacional (kernel space). A principal vantagem é que green
threads são mais rápidas de serem criadas e destruídas do que threads
nativas, gerenciadas pelo Sistema Operacional. Além disso, costumam
consumir menos recursos da máquina (cpu, memória); ou seja, são mais
leves do que as threads nativas.
Por estarem confinadas dentro de um processo, green threads também
estão confinadas a apenas uma CPU. Não conseguem se beneficiar de
vários núcleos e/ou processadores disponíveis. Já threads nativas são gerenciadas pelo Sistema Operacional, que pode jogar cada uma delas em
uma CPU diferente. Threads nativas aproveitam melhor a realidade atual
do hardware, com cada vez mais núcleos e CPUs na mesma máquina.
Por fim, outro grave problema no uso de green threads é que existem algumas operações chamadas “bloqueantes”, que causam o travamento do
processo "chamador" até a operação terminar. Como exemplos podem
ser citadas algumas chamadas de IO e algumas syscalls (chamadas ao
Sistema Operacional), que são “bloqueantes”.
Como é o processo do MRI que gerencia as threads, caso qualquer uma
delas faça alguma chamada “bloqueante” ao Sistema Operacional, o
processo inteiro é travado e consequentemente todas as threads ficam
esperando a chamada “bloqueante” terminar, não apenas a thread que
fez a chamada. Por este motivo, processos de background em aplicações
Rails não costumam usar threads, mas sim processos separados, geralmente com o uso da gem BackgroundDRb.
Threads nativas são mais pesadas, mas resolvem este problema. Caso
uma thread nativa execute alguma operação bloqueante, o Sistema
Operacional trava apenas a thread que fez a chamada.
Gerenciamento de memória
0VUSBHSBOEFEFmDJÐODJBEP.3*ÏPTFVDPMFUPSEFMJYP($o(BSCBHF
Collector), que é extremamente simples e ineficiente. De tempos em
tempos, o coletor de lixo varre a memória (heap) de objetos à procura de
objetos que não estão mais sendo referenciados.
Sempre que passa, o coletor de lixo varre a memória toda! Quanto tempo
a aplicação deve ficar travada esperando o coletor terminar de varrer 3Gb
de memória? O interpretador MRI usa um algoritmo conhecido como “Stop
UIF8PSME(BSCBHF$PMMFDUJPOwRVFÏVNEPTNBJTTJNQMFTRVFFYJTUFN
Outro problema é que depois de coletar alguns objetos, o coletor de lixo
não compacta a memória. O resultado são alguns espaços vazios (buracos) na memória de objetos, como ilustra a figura 1.
Green threads
Por decisão de projeto, as threads da aplicação Ruby são gerenciadas
pelo próprio interpretador MRI. Esta estratégia é conhecida como Green
Figura 1. Espaços vazios não recuperáveis
na memória de objetos.
53
$"1"t+3VCZ8FC1PSRVFVTBS+BWBDPN+3VCZPO3BJMT
O espaço só é preenchido quando um novo objeto que caiba neste
espaço vazio for criado. Existe uma chance de novos objetos ocuparem
apenas uma parte do buraco na memória, fazendo com que sobre um
pedaço de memória tão pequeno que nenhum objeto poderá mais ocupá-lo. Veja na figura 1 que os espaços vazios existentes não comportam
mais objetos, por serem muito pequenos.
Como o tempo, aparecem cada vez mais desses pequenos buracos na
memória. Este é o efeito conhecido como Fragmentação da Memória.
Em outras palavras, podemos dizer que o coletor de lixo do MRI, por ser
muito simples, não desfragmenta a memória. As aplicações escritas em
Ruby, que rodem no MRI, naturalmente vazam memória. Mesmo sem ter
nada de errado.
Este é um dos motivos pelos quais muitos dizem que Ruby não é uma
boa linguagem para processos que rodam por muito tempo (long running processes), já que o uso de memória cresce sem parar. Mesmo em
aplicações Rails, a solução é reiniciar os processos do MRI de tempos em
tempos. Às vezes feito pelo próprio servidor, como no caso do Phusion
Passenger, às vezes feito por um processo de monitoramento extra,
como o Monit e o God.
Este até foi um dos episódios da recente história polêmica do Twitter, que
trocou o sistema de mensageria Starling, feito em Ruby e rodando no
MRI, pelo Kestrel, feito em Scala e rodando sobre a Java Virtual Machine.
Um dos argumentos mais fortes é que eles tinham problemas com os
processos de mensageria rodando por muito tempo.
Felizmente, o MRI não é a única implementação de Ruby existente.
"TPMVÎÍPKÈFTUÈQPSBÓ
t
t
t
memória etc.;
.BH-FWCBTFBEBOB(FN4UPOF4NÈRVJOBWJSUVBMQBSB4NBMM5BML
#MVF3VCZQSPKFUPEB4"1JNQMFNFOUBÎÍPEF3VCZTPCSFBNÈRVJna virtual ABAP;
.BD3VCZ93VCZ3VCZ/&5FOUSFPVUSPT
Criar uma máquina virtual do zero é um trabalho imenso. Quantos anos
de trabalho e melhorias existem sobre o gerenciamento de memória da
máquina virtual Java? Quanto já foi otimizado nesse tempo todo? Quanto tempo demoraríamos para ter um bom coletor de lixo (geracional,
adaptativo, compactador), no nível do que existe na JVM da Sun?
Aproveitar as plataformas de execução de código que já existem, como
a plataforma Java, a plataforma .Net, LLVM e PyPy, é aproveitar o bom
trabalho que já tem sido feito sobre estas plataformas. É poder se beneficiar de compiladores JIT adaptativos bastante avançados e focar no que
realmente importa ao implementar uma nova linguagem, com grandes
chances de sair uma implementação eficiente.
Dado que cada linguagem tem as suas vantagens e desvantagens sobre
outras, há bastante gente que acredita em um futuro onde não haverá
mais polarização de linguagens (Java vs C#) e sim polarização de plataformas: Java vs .Net?
"QPMBSJ[BÎÍPEFQMBUBGPSNBT
A ideia de poder escrever os programas na linguagem mais
adequada para cada caso e rodar na plataforma de preferência
é bastante tentadora. A técnica tem sido chamada de Programação Poliglota (Polyglot Programming) e trata sobre usar a
ferramenta certa para cada tarefa.
Avi Bryant, um famoso rubista, deu uma importante palestra na RubyConf
de 2007, onde disse:
“Eu vim do futuro, sei como esta história termina. Todas as pessoas dizendo que você não pode implementar Ruby em uma máquina virtual
veloz estão erradas. Esta implementação já existe, é conhecida como
Gemstone e poderia ser facilmente adaptada para Ruby. (...)”
Ele se referia a máquinas virtuais para a linguagem SmallTalk, que é a
grande inspiração do Matz e muito parecida com Ruby. Desta palestra
surgiram as ideias para outra implementação de Ruby, que viria a ser
anunciada na RailsConf de 2008, poucos meses depois. MagLev, da
empresa GemStone, é a implementação de Ruby baseada no produto
GemStone/S, que é uma máquina virtual para SmallTalk madura e com
muitos anos no mercado, servindo grandes aplicações. Na época, o
JRuby ainda almejava compatibilidade e era pelo menos duas vezes mais
lento que o MRI.
Hoje, porém, a frase do Avi Bryant também serve para grande parte das
outras implementações. Boa parte do fator de sucesso de muitas delas
vem do fato de serem baseadas em outros projetos, que já resolvem
muito dos problemas envolvidos na implementação de uma linguagem.
Alguns exemplos:
t +3VCZTPCSFB.ÈRVJOB7JSUVBM+BWB
t *SPO3VCZTPCSFP$-3FRVJWBMFOUFË+7.NBTEBQMBUBGPSNB/FU
t 3VCJOJVTBUVBMNFOUFTPCSFPQSPKFUP--7.-PX-FWFM7JSUVBM.Bchine), que serve como base para construção de máquinas virtuais
e contêm algoritmos prontos para coleta de lixo, gerenciamento de
54 www.mundoj.com.br
O Java como plataforma
JRuby é a implementação da linguagem Ruby em Java. Começou como
um simples interpretador escrito em Java, que ia percorrendo o código
Ruby e chamando métodos Java associados a cada expressão. Hoje, é
muito mais do que isso, incluindo até um completo compilador capaz
de transformar código Ruby em bytecodes Java válidos; arquivos .class
prontos para serem lidos pela JVM.
A capacidade de rodar código Ruby sobre a plataforma Java, faz com que
Ruby esteja automaticamente disponível em todas as plataformas onde
Java é suportado. Quase todas as existentes! De gigantescos mainframes
a celulares e televisores. Diversos tipos diferentes de hardware e opções
de sistemas operacionais.
Além disso, JRuby é considerado como a única alternativa completa ao
MRI, para a versão 1.8.6 da linguagem Ruby. Também é a mais rápida,
superando na média o MRI em quatro vezes [1]. Mas como uma camada
a mais de interpretação (a máquina virtual Java, figura 2) pode tornar a
execução de código Ruby mais eficiente?
Compilador JIT
Não raramente, código escrito em Java consegue ser mais rápido do que
a alternativa escrita em linguagens estaticamente compiladas, como C.
$"1"t+3VCZ8FC1PSRVFVTBS+BWBDPN+3VCZPO3BJMT
código que permitam o compilador AOT a cometer quase nenhum erro e
produzir código muito próximo do melhor possível e código que em sua
maioria apenas faz chamadas nativas, como IO, renderização de vídeo
etc. O que podemos dizer é que geralmente um bom compilador JIT
ultrapassa o código de compiladores AOT.
Saber mais
'JHVSB +7. Ï VNB DBNBEB FYUSB OB
execução do código.
A tendência de tornar a plataforma Java melhor para
outras linguagens é tão forte que a Sun criou o projeto
MLVM (Multi Language Virtual Machine) ou Da Vinci
Machine, no qual são testadas melhorias à JVM da Sun
(Hotspot), que a façam executar melhor linguagens
dinâmicas.
http://openjdk.java.net/projects/mlvm/
As melhorias que fazem mais sentido são propostas ao
JCP através da JSR-292, para entrar em futuras versões
da plataforma Java. Algumas como a nova instrução
invokedynamic já estão até agendadas para fazer parte
do Java SE 7.
Espero aprofundar o assunto em um próximo artigo.
Apesar de parecer um absurdo (como o bytecode, sendo interpretado,
pode superar o código assembly nativo para o processador?), o responsável é o grande herói das máquinas virtuais: o compilador JIT.
É o caso das máquinas virtuais Java mais famosas (Sun Hotspot, Oracle
JRockit, IBM J9 etc.), que possuem alguns dos melhores compiladores JIT
existentes no mercado. Ao rodar código Ruby sobre a plataforma Java,
aproveitamos alguns dos melhores (se não os melhores) compiladores
JIT existentes. Esse é um dos principais motivos que tornam JRuby a
implementação mais rápida. Isto é enxergar Java como plataforma, não
como linguagem. O compilador JIT da Sun Hotspot é adaptativo e pode
até decidir que um código já compilado pode ser compilado de uma
maneira melhor. Neste caso, pode até decidir jogar fora o código antigo
e compilar de novo.
Os compiladores JIT das principais máquinas virtuais Java costumam
procurar por “pontos quentes” da aplicação para serem compilados durante a execução. Por exemplo, se um método já foi chamado 20 vezes,
pode ser considerado como um ponto quente e, portanto, vale a pena
gastar um tempinho a mais compilando o código para assembly nativo
do processador. Na próxima chamada do método, o código nativo é
executado diretamente.
Por isso, o código executado pelas máquinas virtuais costuma ser mais
lento no começo, se comparado a alternativas compiladas estaticamente
(AOT), ou até puramente interpretadas, pois estas que não sofrem do custo
inicial de carregamento da máquina virtual, como mostra a figura 3.
Existem dois tipos principais de compilação:
t "IFBE 0G5JNF $PNQJMBUJPO "05
RVBOEP P QSPHSBNB EFWF TFS
compilado antes de ser executado;
t +VTU*O5JNF$PNQJMBUJPO+*5
RVBOEPPQSPHSBNBWBJTFOEPDPNpilado durante a execução.
Dado um programa em uma linguagem qualquer, é possível provar que
existem infinitas formas de compilá-lo em linguagem nativa dos processadores comuns. Se existem infinitas maneiras, como um compilador
AOT (por exemplo, o gcc para a linguagem C) escolhe uma delas?
O compilador chuta, portanto pode errar. A diferença entre compiladores
AOT bons e compiladores AOT ruins é que os bons dão um chute inteligente, usando, por exemplo, heurísticas e análise estática do códigofonte. Por isso erram menos, mas mesmo assim ainda podem errar.
Um compilador JIT pode amenizar bastante esse problema, porque vai
compilando o código durante a execução. Portanto, não precisa chutar
nada, caso colete informações de como o código está executando. Na
hora de compilar um pedaço de código (por exemplo, um método que
já foi chamado dez vezes), o compilador JIT usa a informação de como o
código é executado no momento, e gera o melhor código de máquina
possível para esse caso; sem precisar de chutes.
Desta forma, o código de máquina produzido por um compilador JIT
costuma ser mais eficiente do que a contrapartida produzida por compiladores AOT. Claro que isso ainda vai depender de muitos fatores, como
a qualidade e sofisticação dos compiladores usados, características do
'JHVSB5FNQPEFSFTQPTUBQBSBJOUFSQSFUBÎÍPWTDPNQJMBEPS+*5
Na figura 3 vemos um gráfico comparativo entre código sendo executado em uma máquina virtual com compilador JIT e código produzido
por um compilador AOT, ou puramente interpretado. O eixo vertical (Y)
representa o tempo de resposta da aplicação, já o eixo horizontal (X) o
tempo de execução. Para a interpretação pura ou compilação AOT não
importa quanto tempo a aplicação esteja sendo executada, cada requisição sempre executa o mesmo código e, portanto, leva o mesmo tempo.
No caso da máquina virtual com JIT, o programa começa muito mais
lento, pois além do tempo inicial de carregamento da máquina virtual,
o programa é puramente interpretado no início. Depois de algum tempo
de execução, o compilador JIT começa a compilar os pontos quentes e
gerar código mais eficiente. Como já visto, o JIT usa informação de como
a aplicação é utilizada para gerar código de máquina melhor.
55
$"1"t+3VCZ8FC1PSRVFVTBS+BWBDPN+3VCZPO3BJMT
No gráfico é possível perceber que máquinas virtuais valem a pena
para aplicações que rodem por muito tempo, como as que rodam em
servidores. Quanto tempo ficamos sem reiniciar o servidor? Basta dar
tempo suficiente ao JIT para otimizar o código; tempo conhecido como
“esquentamento” (warm-up) da máquina virtual.
Gerenciamento de memória
Você ficaria surpreso se eu disser que as máquinas virtuais Java possuem algumas das melhores implementações de Garbage Collection? Uma das grandes
sacadas para fugir de coletores “stop-the-world”, que travam a aplicação por
tempos grandes é a hipótese das gerações (generational hypothesis).
Observações em diversos sistemas orientados a objetos concluíram que
a maioria dos objetos tem vida curta, ou seja, se tornam inúteis (nãoreferenciados) pouco tempo depois de sua criação. Em outras palavras,
diz-se que em um típico sistema orientado a objetos, a mortalidade
infantil é alta. Quantas vezes você já viu um código como o seguinte, no
qual um objeto é criado dentro de um método, mas assim que o método
termina perdemos todas as referências a esse objeto?
public void operacaoQualquer() {
Cachorro cachorro = new Cachorro();
// ...
// no fim deste método, todas as referências ao
// objeto cachorro são perdidas
sabemos que a maioria dos objetos tem vida curta, portanto mesmo em
uma passada rápida, o coletor já leva quase todos os objetos embora.
Aqueles que sobrevivem são promovidos (copiados) à memória adulta
(tenured). Já que sobreviveram a uma (e, em algumas situações, a algumas) passada(s) do coletor de lixo, provavelmente estes objetos não
fazem parte do conjunto que tem vida curta. Não vale a pena gastar
tempo analisando-os novamente na próxima coleta de memória jovem;
provavelmente vão sobreviver. Por isso são promovidos.
A simples ideia de dividir a memória em vários pedaços, a partir da mortalidade infantil dos sistemas orientados a objeto, faz com que a coleta
de lixo seja extremamente eficiente e bloqueie muito pouco a aplicação.
Cada parte da memória pode ser vista como uma geração de objetos
(jovem, adulta). Por isso, o algoritmo é formalmente conhecido como
generational copying.
Mais uma enorme vantagem de usar a máquina virtual Java para rodar
código Ruby: todos os problemas de gerenciamento de memória anteriormente vistos no MRI já foram bem resolvidos nas máquinas virtuais
Java mais famosas. Isso é enxergar o Java como plataforma, e não como
linguagem. Aproveitar tudo aquilo de bom que é construído há anos na
máquina virtual.
Existem outros diversos algoritmos para coleta de lixo disponíveis nas
várias opções de máquinas virtuais Java. Nas últimas versões da Hotspot,
máquina virtual Java da Sun, está disponível o novo coletor G1 (Garbage
'JSTUoWFKBPCPY
1SPNFTTBEB4VOIÈNVJUPUFNQPDPNPPDPMFUPSEF
lixo mais avançado já existente.
}
Usando este fato, poderíamos tentar otimizar o gerenciamento de memória, criando novos algoritmos de coleção de lixo. A ideia é dividir a
memória de objetos em “setores”, como mostra a figura 4.
Por fim, a maioria dos coletores de lixo disponíveis na plataforma Java
são compactadores e/ou desfragmentadores. O que significa que a memória de objetos não fragmenta com o tempo, como no MRI. Por isso,
as aplicações que rodam no JRuby não vazam memória naturalmente.
Se bem escritas, não precisam de restarts esporádicos e processos de
monitoramento extras como o God e o Monit.
Instalando o JRuby
O JRuby pode ser baixado no site oficial: http://jruby.codehaus.org e é
necessário ter alguma máquina virtual Java instalada. A última versão,
na data de fechamento deste artigo é a jruby-bin-1.3.1 (cuidado para
CBJYBSPQBDPUFCJOÈSJPoCJOOÍPPTGPOUFToTSD
"QØTGB[FSPEPXOMPad, basta descompactar em um diretório de sua preferência e colocar o
subdiretório ‘jruby/bin/’ na variável de ambiente PATH. Como é feito em
Java, funciona em qualquer plataforma e sistema operacional com bom
suporte a Java.
Figura 4. Divisão da memória de objetos em gerações.
Todos os objetos criados vão para a primeira parte da memória, conhecida como memória jovem. Essa memória costuma ter um tamanho bem
pequeno em relação ao total de memória heap. Tipicamente inicia com
2.2Mb [3] na máquina virtual da Sun, podendo variar bastante já que a
máquina virtual adapta o tamanho para a aplicação corrente. O tamanho
da memória jovem também pode ser adaptativo.
Quando a memória jovem vai ficando cheia, o coletor de lixo é ativado.
Porém, ele passa apenas na memória jovem, que é muito pequena. Desta
forma, a aplicação fica travada por um tempo bem pequeno. Além disso,
56 www.mundoj.com.br
Para prosseguir, o comando jruby deve estar disponível no seu terminal
PV QSPNQU OP DBTP EP 8JOEPXT
5PEP P DØEJHP RVF TFSÈ NPTUSBEP
pode ser salvo em um arquivo com extensão .rb e executado com o
comando jruby nome-do-arquivo.rb. De preferência, no começo dos
estudos, é interessante digitar o código ruby diretamente no jirb (JRuby
Interactive Ruby) para ir vendo o resultado intermediário do código.
Basta usar o comando jirb no terminal, que está disponível logo após a
instalação do JRuby.
Além disso, é assumido neste artigo que o leitor já esteja familiarizado
com a linguagem Ruby. Para uma boa introdução, leia o artigo “Dinamis-
$"1"t+3VCZ8FC1PSRVFVTBS+BWBDPN+3VCZPO3BJMT
(BSCBHF'JSTU
A nova geração de coletores de lixo da Sun, conhecida como Garbage First e apelidada de G1, leva o conceito de gerações ao extremo.
O número de gerações é praticamente infinito e no G1 são chamadas
de regiões. O coletor vai criando dinamicamente novas regiões, a
medida do necessário.
Mais detalhes de funcionamento do G1 sai do escopo deste artigo,
mo e Elegância na Parceria Java e Ruby”, do Alexandre Gazola e do Alex
Campos, nesta mesma edição.
6TBOEPCJCMJPUFDBTFTDSJUBTFN+BWBEFOUSP
de código Ruby
De todas as vantagens de usar o Java como plataforma, na minha sincera opinião, a maior é justamente poder usar a quantidade imensa de
código e bibliotecas já escritas para a plataforma Java. Uma das maiores
belezas do projeto JRuby é prover ao código Ruby executado sobre ele,
a capacidade de chamar qualquer código Java (bytecodes) que esteja no
LOAD_PATH (mesma ideia do classpath, já conhecido de programadores
Java). Tudo isso de uma maneira extremamente simples.
O primeiro passo é carregar o suporte a Java dentro de código Ruby:
require ‘java’. A partir daí, todas as classes que estiverem em diretórios do
LOAD_PATH do interpretador podem ser carregadas. Para descobrir qual
é o LOAD_PATH, basta imprimir a constante LOAD_PATH, que é um array
de diretórios a serem varridos. O código abaixo mostra como verificar e
adicionar diretórios a esta constante:
puts $LOAD_PATH
# a constante $: é um alias para a $LOAD_PATH
puts $:
# adicionando um novo diretório ao $LOAD_PATH
$LOAD_PATH << ‘/lib/java/classes’
O diretório corrente está por padrão no LOAD_PATH, portanto,
todas as classes que estiverem no diretório onde o script Ruby for
executado, ou onde o jirb tenha sido iniciado, estão disponíveis. Ao
chamar o método require ‘java’, fica disponível o módulo Java para
ser usado dentro do código Ruby. Todas as classes Java que estiverem no LOAD_PATH podem ser acessadas dentro deste módulo, na
convenção CamelCase. Ou seja, para usar a classe java.util.ArrayList,
usamos o código Ruby: Java::JavaUtil::ArrayList. Alguns exemplos:
t KBWBVUJM)BTI4FUWJSB+BWB+BWB6UJM)BTI4FU
t CSDPNDBFMVNTUFMMB$1'7BMJEBUPS WJSB +BWB#S$PN$BFMVN4UF
lla::CPFValidator;
t FTUJMP BMUFSOBUJWP KBWBYTXJOH+'SBNF WJSB +BWBKBWBYTXJOH
JFrame. Aqui o JRuby usa alguns métodos Ruby para se aproximar mais da sintaxe de pacotes Java;
t QBSBDMBTTFTEFOUSPEFKBWBKBWBYFPSHÏQPTTÓWFMPNJUJS
o módulo Java e usar direto: java.util.List, javax.swing.JLabel
porém outras informações podem ser encontradas em um paper,
publicado no portal da ACM (requer acesso ao portal):
http://portal.acm.org/citation.cfm?id=1029879
Mais detalhes sobre as tecnologias envolvidas na máquina virtual
Java da Sun (Hotspot) podem ser encontrados no white paper, no
site da Sun:
http://java.sun.com/products/hotspot/whitepaper.html
ou org.w3c.dom.Document. Mesma técnica de antes, criar métodos Ruby que imitam a estrutura de pacotes Java.
Alguns exemplos de uso das classes do Java SE podem ser vistos
na Listagem 1. Todas as classes e objetos Java se tornam objetos
Ruby normais dentro de um programa rodando com o JRuby. Isto
significa que o poder das linguagens dinâmicas pode ser usado com
bibliotecas Java existentes!
O JRuby faz ainda algumas coisas muito interessantes para melhorar o uso dos recursos da linguagem sobre bibliotecas Java
existentes. Um ótimo exemplo é a adição dos métodos do módulo
Enumerable (each, map, select, find, sort, entre outros) em todas
as coleções Java. Listas e mapas de Java também ganham métodos
como o << (append), que os tornam parecidos com listas e mapas
Ruby. Algumas conversões como objetos Fixnum para números Java
(int, long, wrappers etc.), objetos String Ruby para java.lang.String,
Arrays Ruby para java.util.ArrayList, objetos Hash Ruby para java.
util.HashMap e vice-versa são feitas automaticamente pelo JRuby.
Outra característica interessante é que, ao chamar métodos em classes
Java, o JRuby também permite usar a convenção de código Ruby com
tudo em minúsculo separado por underscore ‘_’. CamelCase só para
constantes. Isso ajuda bastante a deixar bibliotecas Java sendo utilizadas
com mais “cara de Ruby”. A convenção de propriedades em objetos Ruby
também pode ser usada para invocar getters e setters. Para executar o
exemplo da Listagem 3, primeiro compile a classe Java da Listagem 2 no
mesmo diretório onde o código Ruby será executado.
Todas as classes Java usadas dentro de código Ruby são abertas, como
qualquer outra classe Ruby. Isso significa que podemos abrir e alterar
qualquer classe Java; mas cuidado, as alterações só valerão para o código
Ruby usando esta classe alterada, não para o resto do código Java comum que usa a classe. Além disso, classes Ruby podem herdar de classes
Java normalmente. Para implementar alguma interface Java, basta tratar
a interface como se fosse um módulo Ruby e incluí-la. Veja os exemplos
na Listagem 4.
Por fim, às vezes pode ser meio inconveniente ter que usar sempre o
módulo Java para usar alguma classe Java, como em Java::Banco; ou usar
o nome completo para classes dos pacotes java.*, javax.* e org.*. Existem
duas formas de amenizar este problema (veja exemplos na Listagem 5):
t VTBOEPPNÏUPEPJNQPSUÏQPTTÓWFMVTBSPOPNFTJNQMFTEBDMBTTF
Exemplo: import “Banco”, faz com que possamos usar diretamente
Banco.new ao invés de Java::Banco.new. Qualquer classe Java que
57
$"1"t+3VCZ8FC1PSRVFVTBS+BWBDPN+3VCZPO3BJMT
Listagem 1. Usando classes do Java SE.
Listagem 4. Herdando e implementando interfaces.
require ‘java’
# herdando de uma classe Java
class SuperArrayList < Java::JavaUtil::ArrayList
lista = Java::JavaUtil::ArrayList.new
def size()
puts “sobrescrevendo o metodo herdado”
end
lista.add(“um texto”)
lista.add([:um, :array, :com, :objetos, :ruby])
end
lista << :metodo_append_em_java_collection
lista[2] = ‘estilo Ruby para Collections’
lista = SuperArrayList.new
lista << “um item”
puts lista.size
# coleções Java ganham os métodos do módulo Enumerable
# each, map, find, select, filter, sort, ...
# implementando uma interface
class Executavel
# qualquer interface Java pode ser inclusa como módulo
include java.lang.Runnable
lista.each do |item|
puts item
end
# todos os métodos da interface devem ser implementados
# não é obrigatório, mas ocorre um erro caso seja chamado e
# não esteja implementado
def run()
puts “executando metodo da interface Runnable”
25.times { |n| puts n }
end
java.lang.System.out.println(“Strings Ruby viram java.lang.String quando
necessário”)
informacoes = java.util.HashMap.new # estilo simplificado para java.*
informacoes.put :nome, “Joao”
# parenteses são opcionais em Ruby
informacoes[:idade] = 31
# estilo Ruby para java.util.Map
puts informacoes[:nome]
# alternativa ao map.get(“chave”)
end
primeira = java.lang.Thread.new(Executavel.new)
segunda = java.lang.Thread.new(Executavel.new)
Listagem 2. Banco.java.
public class Banco {
# caso esteja no jirb, inicie as duas na mesma linha para comecar ao mesmo
tempo
primeira.start; segunda.start
private String nome;
private Long codigo;
public void transferePara(String origem, String destino, Double valor) {
System.out.println(“executando transferencia...”);
}
t
// getters e setters
}
Listagem 3. Convenções Ruby ao chamar código Java.
banco = Java::Banco.new
# getters e setters Java
banco.setCodigo(12345)
puts banco.getCodigo()
# usando convencao Ruby para propriedades
banco.nome = “Banco do Povo”
puts banco.nome
# chamando o método do “jeito Java”
banco.transferePara(“conta do Jose”, “conta da Maria”, 300)
# usando convencoes de nomes Ruby (parenteses sao opcionais)
banco.transfere_para “conta do Jose”, “conta da Maria”, 300
58 www.mundoj.com.br
esteja no LOAD_PATH pode ser importada dessa maneira, bastando
para isso passar o nome completo dela para o método import. Fica
parecido com o import do Java;
DSJBOEP VN NØEVMP 3VCZ QBSB BCSJHBS DMBTTFT F QBDPUFT DPN
os métodos include_class e include_package. Assim, ao invés
de fazer Java::JavaMath::BigDecimal, podemos fazer algo como
Numbers::BigDecimal. Alguns exemplos estão na Listagem 5.
Existem ainda outros detalhes sobre o uso de código Java dentro de Ruby,
como callbacks usando blocos e mais conversões de tipos Ruby para Java.
Uma ótima fonte de consulta é o wiki oficial do projeto JRuby [4]:
http://wiki.jruby.org/wiki/Calling_Java_from_JRuby
O exemplo de uso do JRuby mais comum em tutoriais e palestras é a
criação de interfaces gráficas com Swing. Que tal praticar um pouco e
usar as classes do pacote javax.swing.* com código Ruby para criar uma
pequena janela?
Importando jars
É bem simples usar código de bibliotecas java na forma de jars. No MRI,
quando carregamos alguma biblioteca com require ‘nome_da_biblio-
$"1"t+3VCZ8FC1PSRVFVTBS+BWBDPN+3VCZPO3BJMT
Listagem 5. Simplificando o uso de classes Java.
import “Banco”
import “java.math.BigDecimal”
# agora as classes podem ser usadas pelo nome simples
banco = Banco.new
banco.nome = “Super Banco”
numero = BigDecimal.new “45.66”
# podemos também usar módulos para importar classes e pacotes inteiros
module Lib
include_class “java.lang.System”
include_package “java.util”
include_package “java.math”
end
Lib::System.out.println “classe incluida no modulo!”
itens = Lib::HashSet.new
n = Lib::BigDecimal.new “10.45”
teca’, o interpretador procura em todos os diretórios do LOAD_PATH o
arquivo ‘nome_da_biblioteca.rb’. Como o MRI suporta código nativo
escrito em C, caso não ache o .rb vai atrás do ‘nome_da_biblioteca.dll’, ou
.so, ou .bundle dependendo do sistema operacional.
O comportamento no JRuby é parecido, porém ele tenta carregar o .jar
antes de tentar carregar código nativo. Quando chamamos require ‘log4j’
em uma aplicação JRuby, primeiro será procurado o arquivo log4j.rb, depois o log4j.jar e por último o log4j.dll/so (nativo), em todos os diretórios
do LOAD_PATH.
Uma vez que o jar tenha sido carregado, qualquer classe que ele contenha
pode ser usada normalmente, como visto nas seções anteriores. O código a
TFHVJSNPTUSBDPNPVTBSPGBNPTPGSBNFXPSLEFMPHHJOHo-PH+EFOUSP
de código Ruby. Para o exemplo funcionar, o log4j.jar deve estar no mesmo
diretório do script Ruby (ou em qualquer diretório do LOAD_PATH).
Além do log4j.jar, é necessário ter o arquivo de configuração do Log4J,
log4j.properties para que o log seja corretamente exibido (caso contrário o
Log4J solta um warning dizendo que não encontrou nenhum appender):
require ‘java’
require ‘log4j’
# log4j.jar deve estar no
mesmo diretório deste arquivo
# outra forma de não precisar usar o nome completo
sempre
Logger = org.apache.log4j.Logger
logger = Logger.getLogger(“heyy”)
logger.info "Usando o Log4J em codigo Ruby"
# log4j.properties
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.
ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n
log4j.rootLogger=debug, stdout
JRuby on Rails
JRuby é a única implementação da linguagem Ruby que suporta o Ruby
on Rails completamente, ou seja, é a única implementação capaz de rodar completamente aplicações Rails. A única exceção fica por conta das
aplicações que usam gems com código nativo, específico do MRI, como
discutido anteriormente. Neste artigo, vamos nos limitar ao uso do JRuby
para criar e rodar aplicações Rails; não iremos a fundo no Ruby on Rails.
Mais detalhes sobre o framework ficarão para um próximo artigo.
Código Nativo no JRuby
Todo código Java usado em programas que rodam no JRuby é considerado código nativo. Porém, o carregamento de código assembly de máquina (normalmente escrito em C) também é suportado.
1BSBUBMP+3VCZVTBBFYDFMFOUFCJCJPUFDB+/"o+BWB/BUJWF"DDFTT
que permite o carregamento de código nativo (não bytecodes) pela
máquina virtual Java, de uma forma mais simples que o seu princiQBMDPODPSSFOUFP+/*o+BWB/BUJWF*OUFSGBDF
Apesar de suportar código nativo escrito em C, uma das grandes
barreiras à adoção do JRuby são algumas bibliotecas (gems) com
extensões nativas escritas em C, específicas para o MRI. Essas gems
não são suportadas pelo JRuby por serem específicas ao MRI, não
por terem código escrito em C, como muitos confundem.
Existe uma iniciativa para especificar a interface de extensões nativas em C conhecida como Ruby FFI (Foreign Function Interface). O
projeto foi iniciado pelos desenvolvedores do JRuby e do Rubinius,
e toda extensão nativa escrita em C que for compatível com o Ruby
FFI funcionará em qualquer implementação.
Infelizmente, nem todas as gems com extensões nativas migraram
ainda para serem compatíveis com o Ruby FFI. Até que isso aconteça, o grande problema do JRuby são gems com código nativo para o
MRI. Nestes casos, ou alguém porta o pedaço de código nativo para
Java, ou é necessário usar alguma biblioteca alternativa, em Java. É
o caso da manipulação de gráficos, em que muitos têm substituído
a gem image_magick por Java2D e/ou JFreeChart.
59
$"1"t+3VCZ8FC1PSRVFVTBS+BWBDPN+3VCZPO3BJMT
Para começar no mundo JRuby on Rails, o primeiro passo é adicionar o
framework à sua instalação do JRuby, com a ajuda do RubyGems:
<% jdbc = defined?(JRUBY_VERSION) ? “jdbc” : “” %>
development:
jruby -S gem install rails
O comando acima instala a última versão do Rails (2.3.2, nesta data) e as
suas dependências. É recomendado deixar explícito que o comando gem
a ser executado é o do JRuby, com o prefixo jruby -S, que significa: “o que
vier a frente deve ser executado de dentro do diretório bin/ da instalação do JRuby”. Isso garante que estamos executando o comando gem
do JRuby e não do MRI. É muito comum ter diversas implementações
diferentes de Ruby na mesma máquina.
Precisamos agora instalar o suporte ao banco de dados a ser usado. Para
o nosso exemplo, usaremos o MySQL, que deve estar instalado antes de
prosseguirmos. Com o MySQL funcionando, precisamos do seu driver
JDBC, que também será instalado via RubyGems:
jruby -S gem install activerecord-jdbcmysql-adapter
O projeto ActiveRecord-JDBC provê suporte à maioria dos bancos suportados pelo JDBC. A documentação oficial com instruções para outros
bancos pode ser encontrada em:
http://jruby-extras.rubyforge.org/activerecord-jdbc-adapter/
Já temos o mínimo necessário! Para criar o projeto, basta executar o
comando rails que acabamos de instalar no JRuby:
jruby -S rails blog -d mysql
O primeiro passo é configurar o banco de dados a ser utilizado. Edite o arquivo config/database.yml e acerte as configurações de usuário e senha
para o seu banco. Caso tenha problemas de conexão no banco, ou esteja
com o arquivo .sock do MySQL em outra pasta, também pode trocar a
linha socket: <arquivo>.sock para host: IP_DO_BANCO caso ela exista. O
mais importante é trocar o adapter: mysql dos três modos de execução
(development, test e production) para:
adapter: jdbcmysql
Desta forma, instruímos o Rails a usar o adaptador ActiveRecord-JDBC que
acabamos de instalar. Caso queira que o projeto Rails funcione ao mesmo
tempo no JRuby e no MRI, o truque é usar um pouco de código Ruby
embutido no arquivo de configuração database.yml, para que quando o
JRuby for usado o adapter seja jdbcmysql, caso contrário só mysql:
adapter: <%= jdbc %>mysql
...
test:
adapter: <%= jdbc %>mysql
...
production:
adapter: <%= jdbc %>mysql
...
O projeto já está pronto para ser executado pelo JRuby. Criemos agora as
bases no MySQL, um CRUD simples de posts para o Blog. Por fim, basta
mandar o Rails criar as tabelas no banco e iniciar o servidor:
jruby -S rake db:create:all
jruby script/generate scaffold post
title:string author:string content:text
jruby -S rake db:migrate
jruby script/server
O comando rake e todos que estão na pasta bin/ do JRuby devem ser
executados com jruby -S antes. Scripts Ruby como o script/generate e o
script/server são executados como quaisquer outros scripts Ruby. Com
isso, já deve ser possível acessar o sistema para ser testado na url:
http://localhost:3000/posts
Parabéns e bem-vindo(a) ao universo JRuby on Rails!
Rodando a aplicação em Servlet Conteiners Java
$PNBBKVEBEPQSPKFUP8BSCMFS<>QPEFNPTHFSBSVNBSRVJWPXBSEP
projeto pronto para ser implantado em qualquer servlet contêiner ou
servidor de aplicação Java com suporte à especificação Servlet 2.4. Os
NBJTGBNPTPTTÍPTVQPSUBEPT5PNDBU+FUUZ+#PTT8FC4QIFSF8FC-Pgic, entre outros. Receitas para os contêiners mais famosos podem ser
encontradas no wiki do JRuby [4].
jruby -S gem install warbler
Após a instalação do warbler, é importante criar o arquivo de configuração do warbler na pasta config. Podemos gerar este arquivo usando o
comando warble, dentro do diretório da aplicação Rails:
jruby -S warble config
Abra o arquivo de configuração gerado em config/warble.rb, onde
precisamos adicionar todas as gems que serão inclusas no arquivo .war
gerado. Como usamos o adapter ActiveRecord-JDBC, adicione dentro
EPCMPDP8BSCMFS$POmHEFQSFGFSÐODJBQSØYJNPEBTMJOIBTDPOmHHFN
comentadas):
60 www.mundoj.com.br
$"1"t+3VCZ8FC1PSRVFVTBS+BWBDPN+3VCZPO3BJMT
config.gems << "activerecord-jdbcmysql-adapter"
Além disso, o warbler criará um arquivo .war para rodar em modo production por padrão. Isto pode ser alterado no arquivo de configuração,
mas no nosso caso apenas criaremos as tabelas no banco de produção
(assegure-se de que ele já esteja criado!):
jruby -S rake db:migrate RAILS_ENV=production
Já podemos criar o arquivo .war, novamente com o comando warble, no
diretório do projeto Rails:
jruby -S warble
Basta agora instalar o arquivo .war gerado em qualquer contêiner suportado.
Outras formas de executar a aplicação Rails
Durante o desenvolvimento de uma aplicação com JRuby on Rails, o grande problema que a equipe do projeto enfrentava era a falta de feedback
instantâneo durante o desenvolvimento. Qualquer mudança, por menor
RVFGPTTFFYJHJBBHFSBÎÍPEPXBSOPWBNFOUFDPNBBKVEBEP8BSCMFS
deploy no servidor de aplicação e o tempo de restart do contexto.
Feedback instantâneo é muito importante durante o desenvolvimento
para garantir a produtividade. O ideal é que para ver as modificações bastaria salvar o arquivo e recarregar a página no navegador. Se a cada pequena mudança é preciso regerar o .war e esperar o contexto recarregar,
a produtividade vai lá para baixo e a equipe começa a acumular muitas
mudanças antes de testar (ou ficar muito tempo no Orkut enquanto o
servidor reinicia).
Um dos grandes apelos do desenvolvimento com Ruby on Rails é esse
feedback instantâneo, que perdemos ao usar JRuby e fazer deploy em um
servidor de aplicações Java. Por isso, muitas equipes preferem usar o MRI
durante o desenvolvimento e só usar JRuby nos ambientes de produção,
qualidade e pré-produção.
Resolver estes problemas foge do escopo deste artigo, mas fica a dica de
dois projetos que podem ajudar na tarefa. JettyRails e Glassfish Gem são
dois projetos que visam prover uma forma simples de executar aplicações JRuby on Rails em servidores de aplicação Java (Jetty [7] e Glassfish
[8]), em desenvolvimento e em produção. Mais detalhes em:
http://jetty-rails.rubyforge.org/
http://glassfishgem.rubyforge.org/ (requer Java 6.0)
Considerações finais
Tudo o que foi apresentado neste artigo mostra como Java é uma excelente plataforma para executar código Ruby, além de ser a principal
alternativa à implementação oficial, a MRI.
do qual espero escrever mais em futuras edições, muitas melhorias têm
sido propostas para tornar as próximas versões da plataforma Java ainda
melhores para essas outras linguagens.
Isso não vale apenas para código Ruby, já que existe uma tendência forte
de Java ser uma boa plataforma para outras linguagens. Projetos como o
+ZUIPOoQBSBFYFDVUBSDØEJHP1ZUIPOOB+7.3IJOPoQBSB+BWB4DSJQU
(SPPWZ4DBMBF$MPKVSFoVNEJBMFUPEF-*41oBQFOBTDPNQSPWBNFTUB
tendência. Existem ainda diversas outras implementações de linguagens
sobre a JVM e através do projeto MLVM (Multi Language Virtual Machine),
JRuby é uma excelente maneira de introduzir a linguagem Ruby e a
enorme produtividade do seu mais famoso framework, Ruby on Rails, em
ambientes corporativos que já têm a plataforma Java homologada. Muitas empresas já têm infraestrutura montada com diversos servidores de
aplicação Java; muitas vezes sem nem saber, já estão prontas para rodar
aplicações Ruby on Rails!
Referências
<>5IF(SFBU3VCZ4IPPUPVU"OUÙOJP$BOHHJBOP
<>#BDLHSPVOE%3CoIUUQCBDLHSPVOESCSVCZGPSHFPSH
<> 5VOJOH (BSCBHF $PMMFDUJPO XJUI UIF +BWB<UN> 7JSUVBM .BDIJOF o IUUQKBWBTVO
com/docs/hotspot/gc5.0/gc_tuning_5.html
<>+3VCZ8JLJoIUUQXJLJKSVCZPSH
<>8BSCMFSoIUUQDBMEFSTQIFSFSVCZGPSHFPSHXBSCMFS
<>.VMUJ-BOHVBHF7JSUVBM.BDIJOF5IF%B7JODJ.BDIJOF1SPKFDU
oIUUQPQFOKELKBWB
net/projects/mlvm/
<>+FUUZ3BJMToIUUQKFUUZSBJMTSVCZGPSHFPSH
<>(MBTTmTI(FNoIUUQHMBTTmTIHFNSVCZGPSHFPSH
61
Download