SlideShare une entreprise Scribd logo
1  sur  54
Télécharger pour lire hors ligne
Imergindo na JVM
Otávio Santana
Imergindo na JVM
de Otávio Gonçalves de Santana
Texto copyright © 2013 Otávio Gonçalves de Santana
Este trabalho está licenciado sob a Licença Atribuição-NãoComercial-CompartilhaIgual 3.0
Brasil da Creative Commons. Para ver uma cópia desta licença, visite
http://creativecommons.org/licenses/by-nc-sa/3.0/br/ ou envie uma carta para Creative Commons, 444
Castro Street, Suite 900, Mountain View, California, 94041, USA.
Você tem a liberdade de:
•Compartilhar — copiar, distribuir e transmitir a obra.
•Remixar — criar obras derivadas.
Sob as seguintes condições:
Atribuição — Você deve creditar a obra da forma especificada pelo autor ou licenciante (mas não
de maneira que sugira que estes concedem qualquer aval a você ou ao seu uso da obra).
Uso não comercial — Você não pode usar esta obra para fins comerciais.
Compartilhamento pela mesma licença— Se você alterar, transformar ou criar em cima desta
obra, você poderá distribuir a obra resultante apenas sob a mesma licença, ou sob uma licença
similar à presente.
Ficando claro que:
Renúncia — Qualquer das condições acima pode ser renunciada se você obtiver permissão do titular
dos direitos autorais.
Domínio Público — Onde a obra ou qualquer de seus elementos estiver em domínio público sob o
direito aplicável, esta condição não é, de maneira alguma, afetada pela licença.
Outros Direitos — Os seguintes direitos não são, de maneira alguma, afetados pela licença:
• Limitações e exceções aos direitos autorais ou quaisquer usos livres aplicáveis;
• Os direitos morais do autor;
• Direitos que outras pessoas podem ter sobre a obra ou sobre a utilização da obra, tais como
direitos de imagem ou privacidade.
Prefácio
Certamente o Java é atualmente uma das linguagens mais usadas e uma das mais populares
no mundo, sendo que os seus maiores diferenciais não estão na linguagem e sim na JVM (máquina
virtual Java). A JVM vem sendo alvo de muitos estudos e pesquisas, afinal conhecer bem a JVM vai
garantir ao desenvolvedor Java, maneiras para tirar o melhor proveito da linguagem e da plataforma
além de programar de maneira mais eficiente levando em consideração tudo o que a JVM pode fazer
por ele e ajudando a mesma a te ajudar. Com esse objetivo estou criando esse pequeno texto, estar
ajudando a comunidade brasileira a conhecer profundamente o software que é alicerce de inúmeros
outros.
Esse material é fruto dos meus trabalhos junto com o OpenJDK, a JVM open source e a partir
da versão 7 se tornou a implementação de referência. Ao contrário do que muitas pessoas imaginam,
existem milhões de máquinas virtuais, dentre as mais populares está a HotSpot (também conhecida
como a JVM da Oracle). O OpenJDK é um projeto é vivo com ajuda de algumas empresas (Oracle,
Intel, RedHat, AMD, Google, etc.), mas principalmente com a grande ajuda da comunidade, que
dentre as diversas frentes de trabalho que existem podemos destacar o Adote o OpenJDK que visa a
evolução da plataforma e do Java Livre (ajudando na refatoração, evangelizando sobre o OpenJDK,
identificando e corrigindo bugs).
Para facilitar o entendimento do leitor esse trabalho foi dividido em seis partes:
A primeira falará um pouco sobre o Java, fazendo a devida separação entre linguagem,
plataforma e máquina virtual além de falar um pouco sobre a história do Java e da sua evolução junto
com as versões.
Em seguida se falará sobre o funcionamento básico da JVM, fará a distinção básica entre o
Java linguagem e da máquina virtual, já que a última precisa ser compilada para que a linguagem
seja multiplataforma, o ciclo de vida da JVM e dos processos em paralelo que nascem com uma
aplicação Java.
Saber aonde fica cada informação dentro da JVM e o nome dos seus respectivos
registradores, será o alvo dessa terceira parte do trabalho. Se saberá quais registradores serão
compartilhados por todas as Threads e aqueles que são específicos de cada, assim nascem e
morrem com a sua respectiva Thread.
O bytecode, a linguagem da Máquina virtual, pouco é explicado sobre ela, mas são graças
aos seus opcodes que a JVM é multiplataforma. Nessa parte se verá quão diferente é o seu código
em Java e do produto gerado, o bytecode, além da estrutura que a classe adquire após o processo
de compilação.
A JVM consiste em um processo básico de pegar a informação da classe, gerar stream para
dentro da JVM (Naturalmente na memória principal) e executá-lo o tornando em código nativo, esse
processo de carregar uma classe é feita em tempo de execução, ou seja, só é carregada a classe X
no momento em que ela for chamada, não basta estar apenas no import, caso essa classe tenha um
pai ou implemente interfaces elas serão carregadas antes dessa classe X. Toda classe possui um
ciclo de vida e conheça um pouco mais sobre este ciclo na parte número cinco.
Um grande diferencial da JVM de algumas linguagens é o recurso de gerenciamento
automático da memória, esse processo consistem em matar e recuperar memória de objetos que não
estão mais sendo utilizados, esse é o papel do Garbage Collector. Conheça um pouco mais sobre as
implementações e em quais situações elas são mais aconselhadas.
Para finalizar será demonstrada uma visão prática do JNI e do projeto OpenJDK além dos
conceitos de compilar a JVM.
Sobre o Autor
Otávio Gonçalves de Santana: Desenvolvedor entusiasta do mundo Open Source. Praticante da
filosofia ágil e do desenvolvimento poliglota na Bahia, JUG Leader do JavaBahia, membro do
SouJava, um dos fomentadores do grupo LinguÁgil. Presente nos maiores eventos Java e ágil em
nível nacional, contribui para o projeto OpenJDK e a plataforma JSE, criador do Apache
Easy-Cassandra, presente na comunidade Java mundial, membro ativo e/ou moderador dos maiores
fóruns de língua portuguesa do mundo além de escrever artigos para DevMedia, revista espírito livre
e java.net.
http://about.me/otaviojava
Arte da capa do Ebook
Raul Libório é do openSUSE Project, onde atua na área de wiki, artwork, criação de artigos e
tradução. Tem as motos como paixão e Muay Thai como esporte.
Sumário
1 – Introdução da JVM
1.1 - Histórico da JVM
1.1.1 - JDK Alpha and Beta (1995)
1.1.2 - JDK 1.0 (23 de janeiro de 1996)
1.1.3 - JDK 1.1 (19 de fevereiro de 1997)
1.1.4 - J2SE 1.2 (8 de dezembro de 1998)
1.1.5 - J2SE 1.3 (8 de maio de 2000)
1.1.6 - J2SE 1.4 (6 de fevereiro de 2002)
1.1.7 - J2SE 5.0 (30 de setembro de 2004)
1.1.8 - Java SE 6 (11 de dezembro de 2006)
1.1.9 - Java SE 7 (28 de julho de 2011)
2 – Funcionamento básico da JVM
3 – Registradores da JVM
3.1 - Program Counter
3.2 - Java Stack (Java virtual machine stack)
3.3 - Stack Frame
3.3.1 - Stack variables
3.3.2 – Stack Operand
3.3.3 - Frame Data
3.4 - Native Method Stacks
3.5 - Method Area
3.6 - Heap Space
3.7 – Cache de código
3.7.1 - Just In Time (JIT) Compilation
4 - ByteCodes
5 – Ciclo de vida de uma classe
6 – Garbage Collector
6.1 – Implementação Serial
6.2 – Implementação Paralelo
6.3 – Implementação Concurrent
6.4 – Implementação Incremental Concurrent
6.4 – Implementação Garbage First
6.5 – Comandos para tunning da JVM
7 – Interface Nativa Java
8 – O projeto OpenJDK
9 – Conclusão
1 – Introdução da JVM
JVM, java virtual machine ou máquina virtual java, tem sua história inciada em 1992 com o
Green Project na época a linguagem era denominada de oak. Com o passar do tempo a máquina
virtual foi evoluindo e ficando cada vez mais complexa. A linguagem java possui uma sintaxe similar
ao C++, ele pode ser usado de várias maneiras e é orientado a objetos e se tornou popular em
conjunto com a web. A JVM funciona como o alicerce da plataforma java ficando responsável por
tratar todas as plataformas e SO de modo independente para a linguagem. A JVM não conhece
absolutamente nada da linguagem Java, apenas o seu bytecode, que vem no formato .class, que são
as instruções da JVM (daí a possibilidade de portar outras linguagens para a JVM já que ele não roda
Java e sim o bytecode). Esse class é o código compilado em java e representa uma classe ou
interface em java.
Do seu inicio até a presente data o Java teve diversas entre as suas versões. Essas
modificações são gerenciadas pelo JCP, Java Community Process (o comitê que rege as mudanças
da plataforma java com cerca de 30 empresas), a partir de JSRs, Java Specification Requests,
especificações que fornecem tais modificações e melhorias. Essas mudanças são documentadas no
JSL, Java Language Specification.
1.1 - Histórico da JVM
Antes de se falar dos aspectos do Java, como linguagem, plataforma e máquina virtual é
interessante conhecer um pouco sobre a evolução que o Java vem sofrendo. O projeto nasceu em
1995 e a partir desta data veio softendo constante evolução com ajuda de empresas e da
comunidade.
1.1.1 - JDK Alpha and Beta (1995)
Nas versões alfas e betas se tiveram uma máquina instável.
1.1.2 - JDK 1.0 (23 de janeiro de 1996)
Com o código nome Oak, que também foi o primeiro nome da linguagem. Na versão 1.0.2 foi
lançado a primeira versão estável.
1.1.3 - JDK 1.1 (19 de fevereiro de 1997)
• Grande melhorias e refatorações nos modelos de evento do AWT
• Inner classes adicionado a linguagem
• JavaBeans
• JDBC
• RMI
• Reflection que suportava apenas introspecção apenas, nenhuma modificação em tempo real
era possível
1.1.4 - J2SE 1.2 (8 de dezembro de 1998)
Com o codinome Plaground. Essa e as outras versões foram denominadas de Java 2, J2SE
Java 2 Platform, Standard Edition, Houve modificações significantes nessa versão triplicando o
código para 1520 classes em 59 pacotes incluindo:
• palavra-chave strictfp
• A api do Swing foram integrados ao Core
• Adicionando o JIT compilador
• Java Plug-in
• Java IDL, uma implementação CORBA IDL para interoperabilidade
• Collections framework
1.1.5 - J2SE 1.3 (8 de maio de 2000)
Com o codinome Kestrel, as modificações mais importantes foram:
HotSpot JVM incluído (a JVM HotSpot foi lançado em abril de 1999 para os 1,2 J2SE JVM)
• RMI foi modificado para suportar a compatibilidade opcional com CORBA
• JavaSound
• Java Naming and Directory Interface (JNDI) incluído em bibliotecas centrais
• Java Platform Debugger Architecture (ACDP)
• Sintéticos classes de proxy
1.1.6 - J2SE 1.4 (6 de fevereiro de 2002)
Com o codinome Merlin, foi a primeira versão para a plataforma desenvolvida pelo JCP como
a JSR 59:
• Alterações na linguagem
• a palavra-chave assert( JSR 41)
• melhorias nas bibliotecas
• Expressões regulares
• encadeamento de exceção permite uma exceção para encapsular exceção de menor nível
original
• Suporte ao Protocolo de internet versão 6 (IPv6)
• Chamadas de IO (chamado de NIO) novos Input/Output (JSR 51)
• API de loggin (JSR 47)
• API para ler e escrever imagens in formatos como JPED e PNG
• Integração com o XML e XSLT (JAXP) na JSR 63
• Nova integrações com extensões de segurança e criptografia (JCE, JSSE, JAAS)
• Java Web Start incluído (JSR 56)
• API de preferências (java.util.prefs)
1.1.7 - J2SE 5.0 (30 de setembro de 2004)
Com o codinome Tiger, foi desenvolvida na JSR 176, teve seu final de vida em 8 de abril de
2008 e o encerramento do suporte dia 3 de novembro de 2009. O Tiger adicionou um número
significantes de melhorias para a linguagem:
• Generics: (JSR14).
• Annotations: (JSR 175)
• Autoboxing/unboxing: Conversão automática entre os tipos primitivos e as classes
encapsuladas (JSR 201).
• Enumerations: (JSR 201.)
• Varargs:
• for each loop
• Correções para o Java Memory Model(Que define como Threads interagem através da
memória).
• Static imports
• Geração automática do stub para objetos RMI
• Novo look and feel para o Swing chamado synth
• Um pacote utilitário de concorrência ( java.util.concurrent)
• A classe Scanner
• Classe Scanner para analisar dados de input streams e buffers
1.1.8 - Java SE 6 (11 de dezembro de 2006)
Com o codinome Mustangue, nessa versão a Sun substitui o nome “J2SE” e removeu o “.0”
do número da versão e foi desenvolvida na JSR 270.
• Suporte a linguagem de script JSR 223): Uma API Genérica para integração com linguagens
scripts e foi embutido a integração com o Mozilla JavaScript Rhino.
• Suporte a Web Service através do JAX-WS (JSR 224)
• JDBC 4.0 (JSR 221).
• Java Compiler API (JSR 199): uma API para permitir chamar compilação programando
• JAXB 2.0:
• melhorias no Annotations (JSR 269)
• Melhorias no GUI, como SwingWorker, tabela filtrada e ordenada
• Melhorias na JVM incluindo: sincronização e otimizações do compilador, melhorias no
algorismo do coletor de lixo.
1.1.9 - Java SE 7 (28 de julho de 2011)
Com o codinome Dolphin possui o maior número de atualização no Java. Foi lançado no dia 7
de Julho e foi disponibilizado no dia 28 de julho do mesmo ano.
• Da Vinci Machine: Suporte para linguagens dinâmicas
• Projeto Coin
• Strings in switch
• Automatic resource management in try-statement
• Diamond
• Simplified varargs
• Binary integer literals
• numeric literals
• mult-try
• Novo pacote utilitário de concorrência: JSR 166
• NIO2: novos biblioteca para IO
Nesse primeiro capítulo houve uma pequena introdução sobre o interior da JVM e da sua
importância para a plataforma além do aspecto histórico das versões do Java e de sua evolução tanto
como plataforma, linguagem e máquina virtual.
2 – Funcionamento básico da JVM
Nesse capítulo será falado um pouco do funcionamento básico da JVM além das variáveis.
Falará um pouco do coração da linguagem Java, a sua JVM. Ela faz a independência entre as
plataformas e roda basicamente dois tipos de processos: O escrito em java e são gerados bytecodes
e os nativos que são realizados em linguagens como o CC++ que são linkadas dinâmicamentes para
uma plataforma específica.
Os métodos nativos são muito interessantes para obter informações do SO sendo utilizado
além de usar recursos da máquina e é em função disso que apesar de a linguagem ser
RunAnyWhere a JVM não, ou seja, se precisa usar a máquina virtual específica para aquela
plataforma. Isso acontece, por exemplo, para usar recursos específicas de máquina, por exemplo,
existem chamadas especificas para cada diretório e arquivos.
Figura 1: A JVM precisa ser compilada para uma plataforma específica.
O único e principal motivo da JVM é rodar o aplicativo, quando se iniciar uma execução a
JVM nasce e quando a aplicação termina ela morre. É criado uma JVM para cada aplicação, ou seja,
se executar três vezes o mesmo código em uma mesma máquina serão criadas 3 JVMs. Para rodar
uma aplicação basta que sua classe possua um método público e estático com o nome main e tenha
como parâmetro um vetor de String.
Ao iniciar uma JVM existem alguns processos que rodam em paralelos e em backgrouns e
executam diversas operações e processos para manter a JVM sempre disponível:
• Os Timers que são responsáveis pelos eventos que acontecem periodicamente, por exemplo,
interrupções, eles são usados para organizar os processos que acontecem continuamente.
• Os processos do Garbage Collector é responsável por executar as atividades do coletor de
lixo da JVM.
• Compiladores que são responsáveis por transformar byte code em código nativo.
• Os ouvintes, que recebem sinais (informações) e tem como principal objetivo enviar essas
informações para o processo correto dentro da JVM.
Falando um pouco mais sobre esses processos paralelos ou Thread, a JVM permite que
múltiplos processos execute concorrentemente, essa rotina em Java está diretamente relacionada
com uma Thread nativa. Tão logo um processo paralelo em Java nasça, os seus primeiros passos
são alocação de memória, sincronização dos objetos, criação dos registradores específicos para a
mesma e a alocação da Thread nativa. Quando essa rotina gera uma exceção a parte nativa envia
essa informação para a JVM que a encerra. Quando a Thread termina todos os recursos específicos,
tanto para em Java quanto a parte nativa, são entregues para JVM.
Como na linguagem a JVM opera em dois tipos de dados: Os primitivos e os valores de
referência. A máquina espera que toda a verificação quanto ao tipo tenha sido feito no momento da
execução, sendo que os tipos primitivos não precisão de tal verificação ou inspeção já que eles
operam com um tipo específico de instrução( por exemplo: iadd, ladd, fadd, e dadd para inteiro, long,
float e double respectivamente).
A JVM tem suporte para objetos que são ou instância de uma classe alocada dinamicamente
ou um array, esses valores são do tipo reference, o seu funcionamento é semelhante de linguagens
como C.
Os tipos primitivos existentes na JVM são: numéricos, booleano e returnAdress, sendo que os
tipos numéricos são os valores inteiros e flutuantes.
Nome Tamanho variação Valor padrão Tipo
byte 8-bit -2⁷ até 2⁷ 0 inteiro
short 16-bits -2¹ até 2¹⁵ ⁵ 0 inteiro
integer 32-bits -2³² até 2³¹ 0 inteiro
long 64-bits -2 ³ até 2 ³⁶ ⁶ 0 inteiro
char 16-bits UFT-8 'u0000' inteiro
Float 32-bits 0 flutuante
Double 64-bits 0 flutuante
boolean inteiro false booleano
returnAddress nulo ponteiro
Tabela 1: relação dos tipos primitivos e os seus respectivos tamanho
Os formatos de ponto flutuante são o float com precisão simples o double com dupla precisão
no formato IEE 754 os valores e as operações como especificado no padrão IEEE para aritmética de
ponto flutuante binário (ANSI / IEEE. 754-1985, Nova York). Esse padrão não inclui apenas valores
positivos e negativos, mas zero, positivo e negativo infinito e não um número ( abreviado como Nan é
utilizado para representar valores inválidos como divisão por zero). Por padrão as JVM suportam
esse formato, mas também podem suportar versões estendidas de double e float.
O returnAdress são usadas pela JVM, não possui representação na linguagem, tem seu
funcionamento similar a ponteiros e diferentes dos tipos primitivos não podem ser modificados em
tempo de execução.
Na JVM o tipo booleano possui um suporte bem limitado, não existem instruções para
booleano, na verdade eles são compilados para usar os tipos de instruções do int e o array de
booleano são manipulados como array de byte. Os valores são representados com 1 para verdadeiro
e 0 para falso.
Falando um pouco sobre o tipo de referência, existem três tipos: classes, array e interfaces
todas são instanciadas dinamicamente. Um array é uma matriz simples ou vetor cujo o seu tamanho
não é definido pelo seu tipo.
O Valor de referência é iniciado como nulo, o nulo não é um tipo definido, mas pode ser feito
cast para qualquer tipo de referência.
Recapitulando, existem basicamente dois tipos de dados:
• Primitivos e Referência.
• As referências tem os seus subtipos os de classe, interface e array.
• Os primitivos possuem returnAdress, booleano, flutuantes (float e double de dupla e simples
precisão respectivamente), inteiros (short, byte, int, long, char). Conforme mostra a figura1:
Figura 2: Tipos de variáveis existentes dentro da JVM
Com isso se apresentou os tipos de dados que são armazenados na JVM além do seu
tamanho, o seu ciclo de vida além dos métodos nativos e escritos em java. O Java possui
características multiplataformas, pelo fato que a JVM realiza essa abstração e para isso ela precisa
ser compilada para a plataforma destino, vale salientar, que a JVM é composta por diversas
linguagens além do Java (C, C++, shell script, Objective C, dentre outras) e essas necessitam dessa
compilação para usar recursos específicos da plataforma e da máquina que a JVM rodarão.
3 – Registradores da JVM
Falado um pouco sobre os tipos de dados que são armazenados na JVM e o seu tamanho é
necessário também que se tenha informações de onde são armazenadas tais informações. A JVM
usa registradores para armazenar várias coisas sendo que para todo tipo de dado existe um local
específico. Nessa parte será falado um pouco os registradores da JVM. Durante a execução de um
programa existem registrados que são compartilhados entre toda a JVM e outros que tem a
visibilidade da Thread corrente, esses registradores serão falados abaixo:
Figura 3: Os registradores da JVM, Method Area e o Heap são compartilhados por toda a JVM e o PC
Counter, Pilha Java e a Pilha Nativa cada Thread possui a sua.
3.1 - Program Counter
O registrador PC, Program counter, é criado tão logo uma Thread é criado, ou seja, cada
Thread possui o seu. Ele pode armazenar dois tipos de dados: que são os ponteiros nativos e o
returnAdress, esses dados possuem informações quanto o local aonde se encontra as instruções que
estão sendo executadas pela Thread. Se o método executado não for nativo o PC conterá o endereço
da instrução, returnAdress, caso seja um método nativo será um ponteiro e não tem o seu valor
definido.
3.2 - Java Stack (Java virtual machine stack)
Assim como o PC é um registrador privado para cada Thread, esse registrador armazena
frames ( que será visto a frente). Seu funcionamento é similar a linguagens clássicas como o C, ele
serve para armazenar variáveis locais e resultados parciais, invocações e resultados dos métodos.
Ele não modifica as variáveis diretamente somente inserindo e removendo frames do registrador. Tão
logo a corrente Thread chama um método um novo frame é inserindo contado informações como
parâmetros, variáveis locais, etc. Assim quando o método termina de uma maneira normal, quando
acaba o método, ou por interrupção, quando ocorre uma exceção dentro do método, esse frame é
descartado. O Java Stack pode ter tamanho de fixo ou determinado dinamicamente.
3.3 - Stack Frame
O frame é a unidade do Java Stack ele é criado tão logo se cria um método e é destruído
quando o método é encerrado (normalmente ou ininterrompido por uma exceção). Cada frame possui
uma lista das variáveis locais, pilha de operações além da referência da classe corrente e do método
corrente. Esse frame é dividido em três partes:
3.3.1 - Stack variables
Figura 4: Representação da pilha Java, composta por dois
sub-registradores: Pilha de operações e a pilha de variáveis.
Cada frame contém um vetor para armazenar variáveis locais e os parâmetros e esse
tamanho é definido em tempo de execução. Nesse vetor as variáveis double e long ocupam dois
elementos do vetor e são armazenados consequentemente. Variáveis do tipo int e returnAdress
ocupam um elemento desse vetor (byte, short e char são convertidos para int). Caso o método seja
da instância, não seja estático, o primeiro elemento desse vetor será ocupado pela instância que está
executando esse método e em seguida os parâmetros, na ordem que foram passados. Caso o
método seja da classe, o método seja estático, Não haverá referência da instância que chama o
método, assim o primeiro elemento será os parâmetros.
3.3.2 – Stack Operand
Figura 5: As pilhas de variáveis são compostas por índices de 32 bits, assim as
variáveis do tipo double e long ocupam dois espaços contínuos e as outras variáveis
apenas um.
Como o nome indica esse registrador serve para armazenar as instruções que ocorrem dentro
do método, como o registrador de variáveis locais os valores são armazenados em um vetor mas
seus valores recuperados pela remoção do último elemento do vetor em vez de ser pelo índice. Ele é
baseado na estrutura de dados de Pilha (Primeiro a entrar último a sair). O tamanho das variáveis
acontecem de maneira semelhante as variáveis locais.
Figura 6: Pilha de operação, semelhante ao seu “irmão”, sua unidade possui o tamanho de 32 bits,
seu passo-a-passo segue o mesmo de uma pilha convencional, o ultimo que entrar será o primeiro a
sair. Nesse exemplo será utilizado a soma de dois inteiros ( 10 e 20).
Figura 7: Como a pilha de operação é composta por unidade de 32 bits, quando for double ou long ele
ocupará as duas unidades seguidas, nesse exemplo são somados dois doubles ( 10.10 e 20.20)
3.3.3 - Frame Data
Esse pequeno registrador possui o link da constant pool da classe que o possui o corrente
método que está sendo executado.
3.4 - Native Method Stacks
Possui finalidade semelhante ao registrador anterior, no entanto para armazenar variáveis e
valores nativos (métodos escritos em outras linguagens), é criado tão logo uma Thread é iniciada e
todas as Threads possuem o seu registrador.
Figura 8: Pilhas nativas, nesse ponto não se sabe quais informações estão contidos nesses
registradores. Também o PC counter, ele aponta para as ações, caso esteja java ele
apontará para uma variável returnAderess, caso seja nativo não se sabe o valor no qual ele
vai apontar.
3.5 - Method Area
Esse registrador tem a finalidade de armazenar logicamente o stream da classe, essa área é
compartilhada entre todas as Threads.E logicamente faz parte do Heap espace. Por ser parte do
Heap ele possui o recolhimento de memória automático, Garbage Collector. As informações que
contém as seguintes informações:
• O qualified da classe (O qualifed é o endereço da sua classe que é definido pelo pacote mais
“.” e o nome da Classe, por exemplo, java.lang.Object ou java.util.Date)
• O qualified da classe pai (menos para as Interfaces e o java.lang.Object)
• Informação se é uma classe ou interface
• Os modificadores
• A lista com os qualifieds das interfaces
Para cada classe carregada no Java é carregada um constant pool
Para cada classe é carregado as seguintes informações:
• O constant pool do tipo (Para cada classe Carregada é criada um pool de constant, ele
contém o link simbólico para os métodos e para os atributos além das constantes existentes no tipo).
• informações dos atributos (o nome do atributo, o tipo e o seu modificador)
• informação dos métodos (o nome do método, o seu retorno, o número e tipo dos parâmetros
em ordem e o tipo e o seu modificador)
• Referência para o ClassLoader (classe responsável para carregar a classe)
• Variáveis da classe (variáveis compartilhadas entre todas as classes isso inclui as constantes)
• Referência da classe (uma instância da java.lang.Class para toda classe carregada
3.6 - Heap Space
Tão logo uma instância é criada as informações do seu objeto ficam armazenados aqui, esse
espaço de memória também é compartilhado entre as Threads. O heap tem seu mecanismo de
reclamar memória em tempo de execução além de mover objetos evitando a fragmentação do
espaço.
Representação de uma variável do tipo de referência dentro do Heap é diferente dos tipos
primitivos, ele tem o seu mecanismo muito semelhante aos ponteiros do C/C++ já que ele não possui
a informação, apenas aponta para o local que o possui. O objeto de referência é constituído de dois
ponteiros menores, um apontará para o pool de objetos, local aonde estão as informações, e o
segundo apontará para o seu constant pool (que possui as informações da classe quanto aos
atributos, métodos, encapsulamentos, etc.) que fica localizado no method Area.
Figura 9: Representação de uma variável do tipo de referência dentro do Heap
A representação dos vetores se comporta de forma semelhante as variáveis de referência,
mas eles ganham alguns basicamente dois campos a mais: O primeiro é o tamanho, que define o
tamanho do vetor, e o segundo é uma lista de referência que apontam para os objetos que estão
dentro desse vetor.
3.7 – Cache de código
Esse registrador é usado para compilação e armazenamento dos métodos que foram
compilados para o modo nativo pelo JIT, esse registrador é compartilhado por todas as Threads.
3.7.1 - Just In Time (JIT) Compilation
O byte code Java interpretado não são tão rápido quanto os códigos nativos, para cobrir esse
problema de performance, a JVM verifica métodos críticos (regiões que executam constantemente,
por exemplo) e compila para o código nativo. Esse código nativo será armazenado dentro do cachê
de código em uma região fora da heap. A JVM tenta escolher as regiões mais críticas para que
mesmo com o gasto memória e poder computacional de compilar o código para nativo, seja uma
Figura 10: Representação de um vetor dentro do Heap
decisão vantajosa.
Com isso foi falado sobre os registradores que contém na JVM, vale lembrar que algumas são
exclusivas por Threads ou outra não. A pilha nativa são importantes para obter recursos da máquina,
no entanto, os seus valores são indefinidos, já as pilhas java são criados tão logo um método é
iniciado ele é subdividido em frames, ambas as pilhas são particular por Thread. O registrador PC
não possui informações, apenas aponta para a instrução que está sendo executada e possui um por
Thread. O Heap e o method Area são compartilhada entre a JVM e tão a responsabilidade de
armazenar a instância dos objetos e o streams e informações da classe respectivamente.
4 - ByteCodes
Uma vez tendo noção dos registradores e aonde ficam armazenados cada valor na JVM, será
falado um pouco sobre o funcionamento das instruções, ela possui opcode e no tamanho de 8 bites
ou 1 byte, daí o bytecode. Cada bytecode representa uma ação ou uma operação. A maioria das
operações desse código operam para um tipo específico de valor, por exemplo, iload (carregar um int
para a pilha) e o fload (carrega um float para a pilha) possuem operações semelhantes, no entanto,
bytecodes diferentes. Uma boa dica para saber o tipo operado é saber a letra inicial da operação: i
para uma operação de inteiro, l para long, s para short, b para byte, c para char, f para float, d para
double e a para referência.
As instruções podemos basicamente dividir em:
Carregar e salvar informações: Essas instruções realizam troca de informações entre o vetor
de variáveis locais e a pilha operações, sendo que carregar (definido por iload,lload,fload, dload e
aload) informações da pilha de variáveis para operações e armazenar (definido por: istore, lstore,
fstore, dstore, astore) realiza o processo inverso.
Operações aritméticas: Elas são realizadas com os dois primeiros valores na pilha de
operações e retornando o resultado. O seu processamento é subdividido em flutuantes e inteiros que
possuem comportamento diferentes para alguns resultados, por exemplo, em estouro de pilha e
divisão por zero.
• adicionar: iadd, ladd, fadd, dadd.
• subtrair: isub, lsub, fsub, dsub.
• multiplicar: imul, lmul, fmul, dmul.
• divisão: idiv, ldiv, fdiv, ddiv.
• resto: irem, lrem, frem, drem.
• negação: ineg, lneg, fneg, dneg.
• deslocar: ishl, sidh, iushr, lshl, lshr, lushr.
• bit a bit 'or': ior, lor.
• bit a bit 'and': iand, a terra.
• bit a bit ou exclusivo: ixor, lxor.
• Variável local incremente: iinc.
• Comparação: dcmpg, dcmpl, fcmpg, fcmpl, lcmp.
Conversão de valores: As instruções de conversão de valores serve para fazer modificar o tipo
da variável, essa variável pode ter seu tipo ampliado como: int para long, float, double. long para float
e double e float para double( i2l, i2f, i2d, l2f, l2d, e f2d) e esse ampliamento perde não perde a
precisão do valor original. E o encurtamento do tipo como: int para byte, short, ou char, long para int,
float para int or long e double para int, long ou float (i2b, i2c, i2s, l2i, f2i, f2l, d2i,d2l, e d2f) vale
lembrar que tais modificações existe a possibilidade de perder precisão e estouro do valor.
Criação e manipulação de objetos: Instruções para a criação e manipulação de instâncias
(new) e arrays( newarray, anewarray, multianewarray). Acessar atributos estáticos ou da instância de
uma classe (getfield,putfield, getstatic, putstatic). Carregar (baload, caload, saload, iaload, laload,
faload, daload, aaload) e salvar(bastore, castore,sastore, iastore, lastore, fastore, dastore e aastore)
vetores além do seu tamanho (arraylength). Checa a propriedade da instância ou array (instanceof e
checkcast ).
Instruções condicionais (ifeq, ifne, iflt, ifle, ifgt, ifge, ifnull, ifnonnull, if_icmpeq, if_icmpne,
if_icmplt, if_icmple, if_icmpgt if_icmpge, if_acmpeq, if_acmpne, tableswitch elookupswitch, goto,
goto_w, jsr, jsr_w e ret),
Chamada de métodos e retorno de valores: chama um método de uma
instância(invokevirtual), chama um método de uma interface (invokeinterface), chamada de um
método privado ou da superclasse (invokespecial), realiza a chamada de um método
estático( invokestatic), método que constrói um objeto (invokedynamic). O retorno de uma instrução é
pode ser definido(ireturn,lreturn, freturn, dreturn e areturn). Durante a execução do método caso seja
interrompinda de maneira inesperada com uma exceção a chamada athrow é realizada. Os métodos
sincronos são possíveis graças a presença de um simples os encapsulando chamado de monitor,
esses tipos de métodos são definidos pela flag ACC_SYNCHRONIZED em seu constate pool, que
quando possui tal flag o método entra no monitor(monitorenter) e é executado, e nenhuma outra
Thread pode acessá-lo, e sai (monitorexit) quando seu método é encerrado (de um modo normal ou
por interrupção).
Uma vez falando dos bytecodes é interessantes “puxar o gancho” e falar como fica uma
classe após sua compilação. Como já foi dito após a compilação é gerado um bytecode, cujo arquivo
possui a extensão .class, e cada arquivo representa apenas uma classe. Cada arquivo possui as
seguintes características:
1) Um número mágico em hexadecimal definindo que essa clase é um .class o valor é 0xCAFEBABE
2) O maior e menor número da versão do arquivo class que juntos definem a versão do arquivo, ou
seja, JVM antes rodar precisa verificar se a versão V que ela pode executar estar entre: Menor
Versão<V< Maior Versão.
3) access_flags: Essa flag indica o tipo de encapsulamento da classe, métodos e de suas
propriedades os flags podem ser:
ACC_PUBLIC - flag método, atributo públicos
ACC_PRIVATE - flag para privados
ACC_PROTECTED - protected
ACC_STATIC - estático
ACC_FINAL - final
ACC_SYNCHRONIZED - indica um método sincronizado
ACC_BRIDGE - indica que o método foi gerado pelo compilador
ACC_VARARGS - indica que é varags
ACC_NATIVE - nativo
ACC_ABSTRACT - abstrato
ACC_STRICT - indica que o método é strict
ACC_SYNTHETIC - indica que o método não é “original”
4) this_class: o valor da corrente classe deve ter um índice válido na constant pool
5) super_class: as informações da superclasse devem estar dentro e pode ocupar o índice zero ou
não, se ocupar o índice zero essa classe é o java.lang.Object, a única classe que não possui pai, do
contrário terá que ser um índice válido e ter informações que apontam para a classe pai. As
informações da classe é definida pelo seu nome com o seu caminho, por exemplo, a nome da String
seria java/lang/String.
6) constant pool: O constant pool é uma estrutura de tabela que contém o nome das classes,
interfaces, métodos, atributos e outras informações das classes. Para guardar essas informações
existem dois registadores par cada informação importante: O vetor coma s informações da classe,
método, ou atributos e o seu contador ou índice que funciona como limitador do vetor. Esse é o caso
das interfaces que a classe implementa, atributos, métodos que possuem seus respectivos vetor e
índices.
As descrições dos atributos ou dos parâmetros em um método quanto ao seu tipo é definido a
seguir:
B byte signed byte
C char
D double
F float
I int
J long
L Classname ; referência
S short
Z boolean
[ referência de um vetor
[[ referência de uma matriz
Assim, por exemplo: double dobro(double d) é igual (D)D e Double dobro(Double d) é
(Ljava/lang/Double;)Ljava/lang/Double.
Dentro da constant pool cada informação possui o seu primeiro byte que indica o seu tipo de
informação:
CONSTANT_Class 7
CONSTANT_Fieldref 9
CONSTANT_Methodref 10
CONSTANT_InterfaceMethodref 11
CONSTANT_String 8
CONSTANT_Integer 3
CONSTANT_Float 4
CONSTANT_Long 5
CONSTANT_Double 6
CONSTANT_NameAndType 12
CONSTANT_Utf8 1
CONSTANT_MethodHandle 15
CONSTANT_MethodType 16
CONSTANT_InvokeDynamic 18
StackMapTable: é composto de stackmapframe e tem o objetivo de verificações para o bytecode
Para auxiliar a depuração na linguagem Java existem algumas informações para depurar o código
essas variáveis são: LocalVariableTable e LocalVariableTypeTable que define as informações das
variáveis locais para o debug e LineNumberTable define a parte do bytecode e sua correspondente
linha de código.
Para as anotações exitem: RuntimeVisibleAnnotations, RuntimeInvisibleAnnotations,
RuntimeVisibleParameterAnnotations,RuntimeInvisibleParameterAnnotations que contém
informações das anotações quanto a sua visibilidade em tempo de execução aos atributos e métodos
ou não, existem essas mesmas informações para os parâmetros quanto as suas visibilidades.
AnnotationDefault define as informações dentro das anotações.
O contador da consant pool possui 16 bits ou seja ele só pode conter 2¹ =65535 elementos,⁶
esse mesmo número vale para o número de métodos, atributos, interfaces implementadas, variáveis
locais, pilha de operações (sendo que para esses dois últimos o longo e o double ocupam dois
espaços), o nome do método ou atributo. O número de dimensões de uma matriz é 255 o mesmo
número vale para a quantidade de parâmetros, caso não seja um método estático deve-se incluir a
instância.
Com o objetivo de pôr em prática e visualizar esses bytes codes, será demonstrado um
simples código e o seu respectivo bytecode.
public class PrimeiroTeste{
public Double somarInstancias(Double a, Double b) {
Double resultado = a + b;
return resultado;
}
public double somarDouble(double a, double b) {
return a + b;
}
public int somarInteiros(int a, int b) {
return a + b;
}
public short somarShort(short a, byte b) {
return (short) (a + b);
}
public static int somarStatic(int a, int b) {
return a + b;
}
}
Código 1: Código Java que será analisado no bytecode passo a passo.
Criado o arquivo PrimeiroTeste.java e inserido o código 1 nesse arquivo, o próximos passos
serão entrar pelo terminal no caminho que se encontra o arquivo PrimeiroTeste.java, compilar e
analisar o seu respectivo byte code com os seguintes comandos.
• javac PrimeiroTeste.java
• javap -verbose PrimeiroTeste
minor version: 0
major version: 51
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #5.#21 // java/lang/Object."<init>":()V
#2 = Methodref #22.#23 // java/lang/Double.doubleValue:()D
#3 = Methodref #22.#24 // java/lang/Double.valueOf:(D)Ljava/lang/Double;
#4 = Class #25 // PrimeiroTeste
#5 = Class #26 // java/lang/Object
#6 = Utf8 <init>
#7 = Utf8 ()V
#8 = Utf8 Code
#9 = Utf8 LineNumberTable
#10 = Utf8 somarInstancias
#11 = Utf8 (Ljava/lang/Double;Ljava/lang/Double;)Ljava/lang/Double;
#12 = Utf8 somarDouble
#13 = Utf8 (DD)D
#14 = Utf8 somarInteiros
#15 = Utf8 (II)I
#16 = Utf8 somarShort
#17 = Utf8 (SB)S
#18 = Utf8 somarStatic
#19 = Utf8 SourceFile
#20 = Utf8 PrimeiroTeste.java
#21 = NameAndType #6:#7 // "<init>":()V
#22 = Class #27 // java/lang/Double
#23 = NameAndType #28:#29 // doubleValue:()D
#24 = NameAndType #30:#31 // valueOf:(D)Ljava/lang/Double;
#25 = Utf8 PrimeiroTeste
#26 = Utf8 java/lang/Object
#27 = Utf8 java/lang/Double
#28 = Utf8 doubleValue
#29 = Utf8 ()D
#30 = Utf8 valueOf
#31 = Utf8 (D)Ljava/lang/Double;
Código 2: Constant Pool da Classe, nessa tabela estão contidas informações da classe, como os
métodos, atributos, classes pai, encapsulamentos, meta datas, essas informações são armazenadas no
vetor. Acima fica a maior e a menor versão do bytecode.
Nesse primeiro resultado podemos visualizar a constant pool e a menor e a maior versão do
class. O Constant Pool contém as informações da respectiva classe, já que toda classe possui essa
informação, inclusive as InnerClasses, e eles são armazenadas em um vetor.
public PrimeiroTeste();
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 1: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this LPrimeiroTeste;
public java.lang.Double somarInstancias(java.lang.Double, java.lang.Double);
flags: ACC_PUBLIC
Code:
stack=4, locals=4, args_size=3
0: aload_1
1: invokevirtual #2 // Method java/lang/Double.doubleValue:()D
4: aload_2
5: invokevirtual #2 // Method java/lang/Double.doubleValue:()D
8: dadd
9: invokestatic #3 // Method java/lang/Double.valueOf:(D)Ljava/lang/Double;
12: astore_3
13: aload_3
14: areturn
LineNumberTable:
line 5: 0
line 6: 13
LocalVariableTable:
Start Length Slot Name Signature
0 15 0 this LPrimeiroTeste;
0 15 1 a Ljava/lang/Double;
0 15 2 b Ljava/lang/Double;
13 2 3 resultado Ljava/lang/Double;
public double somarDouble(double, double);
flags: ACC_PUBLIC
Code:
stack=4, locals=5, args_size=3
0: dload_1
1: dload_3
2: dadd
3: dreturn
LineNumberTable:
line 10: 0
LocalVariableTable:
Start Length Slot Name Signature
0 4 0 this LPrimeiroTeste;
0 4 1 a D
0 4 3 b D
public int somarInteiros(int, int);
flags: ACC_PUBLIC
Code:
stack=2, locals=3, args_size=3
0: iload_1
1: iload_2
2: iadd
3: ireturn
LineNumberTable:
line 13: 0
LocalVariableTable:
Start Length Slot Name Signature
0 4 0 this LPrimeiroTeste;
0 4 1 a I
0 4 2 b I
public short somarShort(short, byte);
flags: ACC_PUBLIC
Code:
stack=2, locals=3, args_size=3
0: iload_1
1: iload_2
2: iadd
3: i2s
4: ireturn
LineNumberTable:
line 17: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this LPrimeiroTeste;
0 5 1 a S
0 5 2 b B
public static int somarStatic(int, int);
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=2, args_size=2
0: iload_0
1: iload_1
2: iadd
3: ireturn
LineNumberTable:
line 21: 0
LocalVariableTable:
Start Length Slot Name Signature
0 4 0 a I
0 4 1 b I
Código 3: Os métodos com suas informações tamanho da pilha de operações, stack, tamanho da pilha
de variáveis, locals e o número de informações envolvidas, args_size
A vermos os métodos podemos perceber que todos os métodos possui o tamanho da pilha de
operação e de variáveis além do tamanho das variáveis envolvidas em um determinado método. No
primeiro que é o método construtor, esse método é construído automaticamente caso não seja feita
pelo usuário, ele possui 1 tamanho de operação, já que se trata da criação de um objeto do tipo
referência e esse ocupa um espaço no vetor de operação, 1 no tamanho de variável, já que ele é um
método não estático e essas informações pertence a interface que a está chamando e o número de
variáveis utilizadas é de um já que estamos nos referindo a instância criada. No segundo método, a
soma de instâncias e retorna uma terceira instância,
(Ljava/lang/Double;Ljava/lang/Double;)Ljava/lang/Double, ele possui o tamanho de três na pilha de
variáveis, já que uma para as duas variáveis de referência e uma já que o método é da instância,
quatro no tamanho de pilha de operações, já que no processo os Doubles serão transformados em
double primitivo, assim cada um ocupará duas unidades no vetor. O campo LineNumberTable é para
ajudar a debutar o código num determinado método, LineNumberTable 5: 0, diz que a linha cinco do
código java equivale a instrução do começo, linha zero do byte code e line 6: 13, a linha seis do
código java começa na instrução do bytecode número 13. O campo LocalVariableTable também serve
para debugar e define o nome do campo, tipo, linha que ele nasce e a que morre. Isso demonstra
como é diferente o código Java e o ByteCode gerado.
Nesse capítulo falamos um pouco sobre o bytecode e o seu macete para entender, olhando
pelas inicias do comando, vale lembrar que durante a execução os booleandos, byte, shorts são
transformados para inteiros, assim suas operações são realizadas como inteiros. Se demonstrou
quão diferente é o código Java do bytecode gerado e em função disse se criou tabelas e variáveis,
por exemplo, LocalVariable e LineNumberTable para auxiliar na hora de debugar, essas variáveis são
utilizadas pelo modo debug das IDEs modernas.
5 – Ciclo de vida de uma classe
Toda classe pela JVM possui o seu ciclo de vida, mas ante de ser iniciada o seu objetivo é
criar dinamicamente e isso é feito sob demanda, ou seja a class X será carregada no momento em
que for necessário, ao instanciar um objeto é feito o processo de encontrar a representação binária
da classe, carregar as informações e colocar a sua classe em Stream dentro da JVM, então criar o
objeto. Todas as classes precisam passar por esse processo inclusive a classe que inicia a JVM.
Caso a mesma seja filha ou implemente interfaces as mesmas terão de ser carregadas primeiro.
Como cada um desses três processos possui detalhes, se discriminará as ações de cada um.
O carregamento de classe consiste em subir a classe para memória principal e colocar na
JVM, esse processo acontece uma vez com pot qualifield, com esse stream carregado se realiza o
parser para o registrador method Area e concluindo gerar a interface que representa tal arquivo, o
java.lang.Class.
A interface Class é o produto do processo de carregar a classe, que é a representação do
arquivo, com isso ele contém as informações do mesmo, como lista dos métodos, atributos,
Figura 11: O ciclo normal de uma classe Java, primeiro é carregada para dentro
da JVM, dentro da memória principal e virando stream, em seguida suas instruções
são “traduzidas” para o execução nativa.
interfaces, anotações, etc.
As Classes por sua vez são carregadas pelo ClassLoader (com exceção dos array que não
possui representação binária) na qual existem dois tipos: a bootstrap que carregam as classes da API
e a segunda que é responsável por carregar as classes restantes.
Na JVM existem múltiplas classe loaders com diferentes regras, assim podemos classificar-las
como:
• BootStrap ele se encontra no topo da hierarquia dos class loaders, esse objeto é responsável
por estar carregado a API básica do Java, e os objetos que possuam um altíssimo nível de
confiança pela JVM.
• Extensão é responsável por carregar as API padrões do Java como as de segurança e
Collection.
• O system esse é o class loader padrão da aplicação, ele é responsável por carregar as
classes que estão contidas no classpath.
• Abaixo do System Class Loader o usuário adicionará um class loader, que tem alguns motivos
especiais, entre eles definir um grupo de class loader específico para um domínio ou
aplicação, é o caso dos servidores de aplacação como o tomcat e o Glassfish.
Figura 12: Hierarquia dos Class Loaders
Após a classe ser carregada o próximo passo será linkar para JVM, esse processo consiste na
verificação da classe recém-carregada, ele verifica a palavra-chave, se a estrutura está correta, o
tamanho dos arquivos, após a verificação são alocadas memórias para os atributos e serão setados
os valores padrão dos campos, são carregados os atributos estáticos, encerrando esse processo
todos os link de referência são substituídos por links diretos.
No último estágio será a criada a instância que a chamada do método construtor se o mesmo
tiver, sendo que antes é chamado o construtor da superclasse, não existe verificação para as
interfaces apenas se os métodos foram implementados.
Figura 13: Processo em que a classe é carregada para dentro da JVM, verificada, então é instanciado
o objeto da respectiva classe. Lembrando caso ele tenha “pais” as classes pais terão de fazer esse
processo primeiro.
6 – Garbage Collector
Diferente de algumas linguagens o java possui um gerenciamento de memória automática,
isso permite que a memória que de um objeto não mais utilizado seja retomada, essa certamente é
uma das grandes vantagem da plataforma em relação ao C. O primeiro desafio da JVM é identificar
quais objetos dispensáveis e assim retomar a memória com isso foi realizado várias técnicas para
assim o fazer. Assim podemos definir dois algorismo:
O mais conhecido certamente é o Mark and Sweep em basicamente dois processos que
marca os objetos utilizados e no final os objetos não marcados são dispensáveis para retomar a
memória, o maior problema é que todos os processos são parados para executar tal procedimento
inviabilizando o chamado desse processo constantemente. Em função desse problema citado
anteriormente falaremos do segundo algorismo, que leva em consideração que muitos objetos não
possuem uma longa vida, no entanto, alguns levam bastante tempo na memória, assim os algoritmos
se baseia em gerações que divide a memória em três partes (jovem, efetiva e permanente).
Para melhor gerenciar o coletor de lixo a memória heap é dividia basicamente em três partes:
Figura 14: Estilo mark and sweep, em que os objetos utilizados são marcados, os
não utilizados são marcados, após esses dois passos o próximo passo será
desfragmentar a memória principal.
Young Generation: É onde contém os objetos recém-criados, a grande maioria dos objetos
morrem dentro dessa área. Ela é subdivida em duas partes: Edern (local aonde o objetos nascem) e
SurviverN locais aonde os objetos vão passando até sair da Young Generation. O seu funcionamento
é de maneira simples: Os objetos nascem no Edem, depois de um tempo, os objetos são copiados
“vivos” para os Suvivers, os objetos que não foram copiados não são apagados, mas no futuro, outros
objetos ocuparão seu espaço. Com o passar das coleções os objetos existentes saem da Young e vai
para Tenured generation, nesse espaço o gerenciamento de objetos é realizado de forma diferente,
assim não há cópia, existem algoritmos derivados do Swep and Mark, com os objetos apagados a
próxima preocupação será em relação a fragmentação do registrador, assim haverá o processo de
compactação.
Comentado um pouco sobre os processos de minor collector (procedimento de generation que
copia objetos para registradores sobreviventes e a JVM não para) e o maior collector (procedimento
cujo os algoritmos são derivados de Mark and Swep que apaga os objetos não utilizados e quando
fragmentada a memória haverá o procedimento de compactação, vale lembrar que para tal
procedimento a JVM para todos os seus processos). O objetivo agora será falar o estilo ou o modo
dos Garbage Collector.
6.1 – Implementação Serial
Figura 15: Divisão da memória por geração
Figura 16: A implementação Serial
Esse estilo é muito indicado para pequenas aplicações ou hardware de pequeno poder
computacional e apenas um processador (monocore), ele se na baseia na execução dos processos
(maior e menor collector) utilizando apenas uma Thread acaba economizando, porém caso haja um
grande número de memória haverá um grande delay. Assim haverá uma Thread para minor collector,
serial, e para o maior collector, serialOld.
6.2 – Implementação Paralelo
Trabalha de forma semelhante com a forma semelhante ao serial, no entanto, será utilizado
duas ou mais Threads por coleção, assim realizando por menos tempo. Existem três
implementações: Parallel Scavenge: minor collector utilizando várias threads, ParNew: semelhante ao
anterior, seu diferencial é que foi otimizado para o uso com o collector Concurrent, Parallel Old: atua
com maior collector com várias Threads e usa o Mark Sweep ao mesmo tempo que executa a
compactação.
6.3 – Implementação Concurrent
Esse também executa processos em paralelos, no entanto, o seu objetivo é diminuir o tempo
do maior collector, mesmo que para isso o execute várias vezes. Indicado para muitos objetos duram
muito tempo, assim eles ficam na Turnered. Em resumo seu processo divide realizar marcação em
que todas as Thread estão paradas e marcações concorrentes, mas a remoção dos objetos ocorrem
sem nenhum processo parar, o único problema desse estilo é o fato que não há compactação de
dados periódicos, apenas quando se torna crítico (usando o SerialOdl).
Figura 17: Implementação Paralelo
Figura 18: Implementação Concurrent
6.4 – Implementação Incremental Concurrent
Também é concorrente, mas é realizado de forma incremental (realizado aos poucos e
agendado entre as minor-collector) seu funcionamento é bem semelhante ao anterior, mas adiciona
um processo que é redimensionar e preparar os dados para uma próxima coleção o ciclo que controla
o tempo que o colector fica no processador. Caso tenha problemas com fragmentação, ele também
acionará o serialOdl (que além de remover os dados também compactará os objetos sobreviventes).
6.4 – Implementação Garbage First
Lançado na versão 7 do Java, o Garbage first, é coletor paralelo projetada para ter um grande
throughput, ele foi projetado para sistemas com uma alta quantidade de memória e de
processadores. Para alcançar seu objetivo ele divide igualmente o tamanho do Heap. Assim como os
dois últimos concorrentes, ele possui uma faze em que realiza a marcação concorrente, e realiza o
Figura 19: Implementação Incremental Concurrent
Figura 20: Implementação Garbage First
calculo de objetos alcançáveis de cada região, assim de tempos em tempos, todos os processos são
parados os objetos vivos são copiados para outra região, fazendo com que a região em questão seja
totalmente tomada. Terão maior prioridade as regiões com o maior número de objetos “mortos”
( assim se terá menos trabalho em realizar a copia para a outra área). O G1 veio para substituir os
tipos concorrente de coleção (CMS e I-CMS) devido a lacuna que ambos deixavam: Não ter um
tempo determinado para deixar o processador e a compactação do heap (uma vez que muito crítica
chamada o serialOdl). O G1 toma como estratégia o fato de ser mais fácil controlar pequenas regiões
do que uma geração, um outro aspecto é que tão logo as memórias existentes tenha sido copiado
para uma nova área, a anterior é considerada uma área limpa.
6.5 – Comandos para tunning da JVM
Abaixo os comandos e parâmetros para a configuração do tamanho da heap e da JVM:
1. -Xms<N>. Especifica o tamanho inicialmente reservado da memória heap em N megabytes.
2. -Xmx<N>. Especifica o tamanho máximo da memória heap em N megabytes.
3. -XX:MinHeapFreeRatio=<N>. Especifica a porcentagem mínima de espaço livre da memória heap.
Se o espaço livre vier a ser menor que N%, o tamanho da memória heap será aumentado para
garantir esta porcentagem de espaço livre mínimo.
4. -XX:MaxHeapFreeRatio=<N>. Especifica a porcentagem máxima de espaço livre da memória
heap. Se o espaço livre vier a ser maior que N%, o tamanho da memória heap será diminuído para
garantir esta porcentagem de espaço livre máximo,
5. -XX:NewRatio=<N>. Especifica a proporção de tamanho 1:N entre Young generation e o resto da
memória heap. Por exemplo, se N=3, então a proporção será 1:3, ou seja, a Young generation
ocupará 1/4 do espaço total da memória heap.
6. -XX:NewSize=<N>. Especifica o tamanho inicialmente reservado da Young generation em N
megabytes. É uma alternativa a -XX:NewRatio pois pode ser difícil estimar este tamanho em
proporção 1:N.
7. -XX:MaxNewSize=<N>. Especifica o tamanho máximo da Young generation em N megabytes.
8. -XX:SurvivorRatio=<N>. Especifica a proporção de tamanho 1:N entre cada espaço Survivor e
Eden. Por exemplo, se N=6, então a proporção será 1:6, ou seja, cada espaço Survivor ocupará 1/8
do espaço total da Young generation (pois há dois espaços Survivor).
9. -XX:PermSize=<N>. Especifica o tamanho inicialmente reservado da Permanent generation em N
megabytes.
10. -XX:MaxPermSize=<N>. Especifica o tamanho máximo da Permanent generation em N
megabytes.
Além disto, há as seguintes opções para imprimir logs sobre Garbage Collection, essas
informações são úteis verificar o resultado do processo do GC, horários que são realizados,
estatísticas de objetos, dentre outras funções.
1. -verbosegc. Imprime uma linha no console a cada collection realizada, no formato [GC <tamanho
da memória heap antes da collection> -><tamanho da memória heap após a collection> (<tamanho
máximo da memória heap>), <tempo de pausa> secs].
2. -XX:+PrintGCDetails. Similar a -verbosegc, mas inclui mais informações como os detalhes da
execução de cada collector.
3. -XX:+PrintGCTimeStamps. Quando usado com -XX:+PrintGCDetails mostra os horários em que
cada collection foi realizada.
4. -XX:+PrintGCDateStamps. Quando usado com -XX:+PrintGCDetails mostra as datas em que cada
collection foi realizada.
5. -XX:+PrintReferenceGC. Quando usado com -XX:+PrintGCDetails mostra estatísticas de objetos
de referência fraca, como WeakReference, SoftReference e PhantomReference.
6. -XX:+PrintTenuringDistribution. Imprime uma linha no console a cada collection realizada a respeito
da utilização dos espaços Survivor e um threshold indicando quantas vezes um objeto pode ser
copiado dentro da Young generation antes de ser considerado apto para pertencer à Tenured
generation.
Esses comandos possui detalhes quanto ao estilo do GC que será utilizado, essas opções são
importantes e vão variar de acordo com a máquina ( número de processadores, tamanho do heap) e
com aplicação ( média de objetos que são criados, tempo médio de vida, etc.)
1. -XX:UseSerialGC. Esta opção seleciona as implementações Serial (para Young generation) e
SerialOld (para Tenured generation).
2. -XX:UseParallelGC: seleciona as implementações Parallel Scavenge (para Young generation) e
Serial Old (para Tenured generation).
3. -XX:UseParNewGC: seleciona as implementações ParNew(para Young generation) e Serial Old
(para Tenured generation).
4. -XX:UseParallelOldGC:seleciona as implementaçõesParallel Scavenge(paraYoung generation) e
Parallel Old (paraTenured generation).
5- -XX:UseConcMarkSweepGC. Esta opção seleciona as implementações ParNew (para Young
generation), CMS e Serial Old (ambos para Tenured generation).
Neste caso, primeiramente tentará utilizar CMS, mas se houver problemas de fragmentação,
utilizará Serial Old.
6- XX:UseConcMarkSweepGC e -XX:+CMSIncrementalMode. Estas opções selecionam as
implementações ParNew (para Young generation), i-CMS e Serial Old (ambos para Tenured
generation). Novamente, primeiramente tentará utilizar i-CMS, mas se houver problemas de
fragmentação, utilizará Serial Old.
Além do estilo ou tunnig que a GC será usado na execução da aplicação também é possível
uma customização mais avançada da JVM, essas modificações são mais avançadas e são
recomendadas.
1. -XX:ParallelGCThreads=<N>. Especifica o número de threads e Garbage Collection a serem
utilizadas. Por padrão, o collector Parallel utilizará X threads de Garbage Collection em uma máquina
com X processadores. Tipicamente, em uma máquina com 1 processador, o collector Parallel terá
performance pior que o collector Serial. Em uma máquina com 2 processadores ou mais, com uma
quantidade média a grande de dados, o collector Parallel já se sobressai.
2. -XX:MaxGCPauseMillis=<N>. Especifica a pausa máxima desejada. Por padrão, não há pausa
máxima desejada previamente definida. A utilização desta opção faz com que o tamanho da memória
heap e outros parâmetros sejam ajustados para tentar manter as pausas menores ou iguais a N
milissegundos, podendo assim afetar o throughput da aplicação. Contudo, não há garantias que o
tempo de pausa será menor ou igual a N milissegundos em todas as execuções.
3. -XX:GCTimeRatio=<N>. Especifica a razão de tempo total para Garbage Collection na aplicação,
segundo a fórmula 1 / (1 + <N>). Por exemplo, -XX:GCTimeRatio=19 define a razão de 1/20 ou 5%
como o tempo total para Garbage Collection na aplicação.
4. -XX:YoungGenerationSizeIncrement=<Y>. Especifica a porcentagem de incremento quando o
tamanho da Young generation aumenta. Por padrão, é 20%.
5. -XX:TenuredGenerationSizeIncrement=<T>. Especifica a porcentagem de incremento quando o
tamanho da Tenured generation aumenta. Por padrão, é 20%.
6. -XX:AdaptiveSizeDecrementScaleFactor=<D>. Especifica o fator D para calcular a porcentagem
de decremento quando o tamanho de alguma generation diminui. Tal porcentagem é calculada como
X / D, onde X é a porcentagem de incremento. Por padrão, a porcentagem de decremento é 5%.
7. -XX:DefaultInitialRAMFraction=<N>. Especifica o fator N para calcular o tamanho inicial da
memória heap, que é igual a R / N, onde R é o tamanho da memória RAM da máquina. Por padrão, N
é 64.
8. -XX:DefaultMaxRAMFraction=<N>. Especifica o fator N para calcular o tamanho máximo da
memória heap, que é calculada como o valor mínimo entre 1 GB ou R / N, onde R é o tamanho da
memória RAM da máquina. Por padrão, N é 4.
9. -XX:-UseGCOverheadLimit. Desabilita o disparo de OutOfMemoryError quando mais de 98% do
tempo total é usado em Garbage Collection, sobrando menos de 2% para a aplicação.
Essas configurações, são como anterior, configurações mais avanças, mas são para as
implementações do GC paralelas ou concorrentes.
1- -XX:CMSInitiatingOccupancyFraction=<N>. Especifica a porcentagem de ocupação da
Tenuredgeneration necessária para disparar uma collection. Por padrão, este valor é
aproximadamente 92%.
2. -XX:+CMSIncrementalPacing. Habilita automaticpacing, que é a estimativa automática do duty
cycle baseado em estatísticas da JVM. Por padrão, é habilitado.
3. -XX:+CMSIncrementalDutyCycle=<N>. Especifica a porcentagem de tempo entre Minor collections
quando o collector pode executar. Se automatic pacing está habilitado, especifica apenas o valor
inicial. Por padrão, é 10.
4. -XX:CMSIncrementalSafetyFactor=<N>. Especifica a porcentagem de uma margem de segurança
que será adicionada ao tempo de execução das Minor collections. Por padrão, é 10.
5. -XX:CMSIncrementalOffset=<N>. Especifica a porcentagem na qual o duty cycle tem seu início
intencionalmente atrasado. Por padrão, é 0.
6. -XX:CMSExpAvgFactor=<N>. Especifica a porcentagem usada para pesar a amostra atual quando
computar médias exponenciais para as estatísticas de collections concorrentes. Por padrão, é 25.
7- XX:+UnlockExperimentalVMOptions -XX:+UseG1GC. Na JVM HotSpot 7 apenas o segundo
parâmetro é necessário.
8. -XX:MaxGCPauseMillis=<P>. Especifica o tempo de pausa máximo desejado, em milissegundos.
9. -XX:GCPauseIntervalMillis=<I>. Especifica o intervalo desejado de tempo de execução da
aplicação que permitirá o tempo de pausa máximo especificado acima, em milissegundos. Por
exemplo, se I=200ms e P=20ms, significa que a cada 200ms de execução da aplicação, o collector
deverá utilizar no máximo 20ms de tempo de pausa.
10. -XX:+G1YoungGenSize=<N>. Especifica o tamanho da Young generation, em megabytes.
11. -XX:+G1ParallelRSetUpdatingEnabled -XX:+G1ParallelRSetScanningEnabled.Estes parâmetros
permitem aproveitar o máximo possível de Garbage First, no entanto podem produzir uma rara
situação de concorrência chamada condição de corrida (race condition) e resultar em erro.
Um último detalhe é que Garbage First é muito mais verboso que os outros collectors da JVM
HotSpot quando utilizando a opção -XX:+PrintGCDetails, pois pretende fornecer mais informações
para troubleshooting.
7 – Interface Nativa Java
Muito se fala do Java, principalmente do fato dele ser multiplataforma, talvez essa
característica em especial, de se compilar uma vez e poder rodar em diversas plataformas, tornou o
Java a linguagem e plataforma mais popular no mundo. Muito se tem explorado já que toda a
complexidade é feita pela JVM, mas surge a seguinte dúvida: Como a JVM faz isso? Como ela
conseguir abstrair as outras plataformas para min? Obter esse conhecimento é muito importante uma
vez que pode ser necessário utilizar um recurso específico da máquina e fazer com esse recurso
converse diretamente com a JVM.
A comunicação entre a JVM e o código nativo quase em sua grande maioria é feito na
linguagem C/C++ uma vez que elas possuem o padrão ANSI e que o mesmo é compatível com a
grande maioria das plataformas, assim é possível um grande aproveitamento de código, mas em
alguns casos é necessário que seja feita uma implementação específica para cada plataforma ou que
existe compatibilidade com um sistema legado que foi feito em C, por exemplo. A porta entre o código
nativo e o Java é conhecido como JNI (Java Native Interface). Esse recurso é muito interessante
também para fazer a ponte para plataformas em que o Java ainda não atingiu. Dentro da JVM esse
recurso é usado para alguns na plataforma JSE, por exemplo, Java I/O, alguns métodos da classe
java.lang.System, JavaSound, a comunicação entre a classe, o arquivo .class, e a sua representação
dentro da JVM, a implementação da interface java.lang.Class, as implementações do Garbage
Colector dentre outros recursos. Com o JNI é possível chamar método do objeto, instanciar classes,
verificar estado do objeto criado dentro da JVM, dentre outros recursos.
No entanto usar o requer algumas responsabilidades, vale lembrar que usar o JNI perde a
portabilidade, um erro nativo não é controlado pela JVM (Vale lembrar na parte em que se falou de
registrados, a pilha nativa e o PC quando aponta para um processo nativo, não se sabe o seu valor
preciso), não é possível debugar o código nativo através da plataforma Java, caso acontece um erro
nativo pode quebrar a execução da JVM, ele não prover um Garbage Collector automático ou
qualquer gerenciamento por parte da JVM. Assim é muito importante saber o momento em que se
usará o JNI.
Os objetos em Java podem ser mapeados para objetos nativos e virse-versa, para garantir a
comunicação de duas mãos, assim é possível estar passando um objeto Java para o lado nativo ver o
seu valor.
Tipo em Java Tipo Nativo
boolean jboolean
byte jbyte
char jchar
double jdouble
float jfloat
int jint
long jlong
short jshort
void void
Tabela 2: mapeamento entre os objetos nativos e os objetos Java
Com o objetivo de dar um pequeno exemplo com o JNI será mostrado dois simples exemplos,
o primeiro será o “olá mundo” com o JNI e o segundo será um método estático que calcula o dobro do
resultado passado, para isso é necessário que se tenha instalado o GCC eu JDK. O código será bem
simples, no primeiro caso será enviado o nome por parâmetro e essa String será passada para o
valor nativo, uma vez no nativo será concatenado o “Hello world” com o nome digitado, no segundo
exemplo, o segundo parâmetro seria calculado o seu dobro.
Primeiramente será criado a classe HelloWorld.java.
public class HelloWorld {
private native void chamarMensagem(String nome);
public native static int dobrar(int valor);
public static void main(String[] args) {
String nome=args[0]==null?"nome":args[0];
int valor=args[1]==null?2:Integer.valueOf(args[1]);
HelloWorld helloWorld=new HelloWorld();
helloWorld.chamarMensagem(nome);
int resultado=HelloWorld.dobrar(valor);
System.out.println("O dobro de "+valor+" é: "+ resultado);
}
static {System.loadLibrary("HelloWorld");}
}
Código 4: Classe HelloWorld chamando o método nativo
Em seguida compilamos com o seguinte comando: javac HelloWorld.java
Uma vez o arquivo compilado, será necessário gerar a interface JNI como seguinte comando:
javah -jni HelloWorld
Com o comando será gerado um arquivo HelloWorld.h
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class HelloWorld */
#ifndef _Included_HelloWorld
#define _Included_HelloWorld
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: HelloWorld
* Method: chamarMensagem
* Signature: (Ljava/lang/String;)V
*/
JNIEXPORT void JNICALL Java_HelloWorld_chamarMensagem
(JNIEnv *, jobject, jstring);
/*
* Class: HelloWorld
* Method: dobro
* Signature: (I)I
*/
JNIEXPORT jint JNICALL Java_HelloWorld_dobrar
(JNIEnv *, jclass, jint);
#ifdef __cplusplus
}
#endif
#endif
Código 5: interface JNI do HelloWorld
Repare que na interface o método possui o seguinte formato:
Java_NomeClasse_nomeMetodo. Em relação aos parâmetros o primeiro elemento, o JNIEnv,
ele é um ponteiro que aponta para um vetor no qual possui todas as funções do JNI, o segundo
depende se é método da classe ou da instância. Caso seja estático, ou seja o método possua a
palavra-chave static, o próximo parâmetro será o jclass que conterá as informações da classe, caso
seja da instância o próximo parâmetro será o jobject que conterá as informações da instância.
O próximo passo é a criação do arquivo que “implemente” a interface do HelloWorld.h, assim
será criado o HelloWorld.c que implemente tal interface.
#include <jni.h>
#include "HelloWorld.h"
#include <stdio.h>
JNIEXPORT void JNICALL Java_HelloWorld_chamarMensagem(JNIEnv * env, jobject obj,jstring
nome){
const char * nomeNativo=(*env)->GetStringUTFChars(env, nome, NULL);
printf("Hello World!!!! %sn", nomeNativo);
return;
}
JNIEXPORT jint JNICALL Java_HelloWorld_dobrar(JNIEnv * env, jclass classe, jint valor){
return 2*valor;
}
Código 6: implementação da interface HelloWorld.h
Com o arquivo criado o próximo passo é a compilação, levando em consideração as devidas
importações, como se trata de libs nativas as pastas variam de acordo com a plataforma. No caso do
linux para compilar será necessário o seguinte comando:
gcc -o libHelloWorld.so -shared -I$JAVA_HOME/include -I$JAVA_HOME/linux HelloWorld.c
Uma vez compilado o código-fonte e o transformado em uma lib, no caso do linux o arquivo
com extensão .so de Shared Object. O próximo passo será “linkar” o arquivo nativo com o projeto, o
primeiro passo é carregar a biblioteca dentro do código java (para isso será utilizado o comando
System.loadLibrary("NomedaLib");). O próximo passo é colocar a lib nativa no classpath no sistema
operacional ou definir o seu caminho pelo parâmetro java.library.path ao executar o projeto java.
Nesse exemplo será utilizado a segunda opção juntamente o parâmetro que será o nome da que será
impresso no console, assim o comando ficará:
java -Djava.library.path=. HelloWorld Otávio 4
A saída seria:
Hello World!!!! Otávio
O dobro de 5 é: 10
Com isso se apresentou a o recurso do JNI, a interface que se comunica o JVM para
linguagens nativa como C e C++ e da sua importância para a JVM como a implementação do
Garbage Collector, sua existência em algumas APIs como o JavaSound além de se integrar com
código legado e com plataformas cujo a JVM até o momento não atingiu. No entanto vale salientar
que se perde o fator multiplataforma e não será mais gerenciado pela JVM usando este recurso.
Aprender sobre JNI é muito importante para compreender o código da máquina virtual Java, mas é
necessário um conhecimento na linguagem C e C++.
8 – O projeto OpenJDK
O OpenJDK é um projeto que foi iniciado pela Sun Microsystems, atualmente mantido pela por
várias empresas e a comunidade, para a criação de um Java Development Kit baseado totalmente
em software livre e de código aberto. O projeto foi iniciado em 2006 e tem como base o HotSpot (a
jvm da Sun). Uma conquista para o projeto que vale salientar é que a partir da versão 7 do Java o
OpenJDK é a versão de referência, mas além dessa o uso do OpenJDK te garante algumas
vantagens:
1.A primeira vantagem é que ele é open source, ou seja, pode estudar o seu código fonte.
2. Ela agora é a implementação de referência ou seja se fazer um aplicativo que rode em
qualquer JVM, essa garantia será possível apenas com o OpenJDK
3.A comunidade Java é certamente uma das comunidades mais fortes do mundo. A JVM do
projeto, por exemplo, está passando por constantes refatorações para melhoria de
performance, atualização de bibliotecas e atualização do código sem falar que para adicionar
qualquer recurso é necessário que se tenha testes.
4.A Oracle doou o código fonte do jRockit e no java 8, previsto para o final de 2013, o código
seja integrado com o Hotspot. Ou seja no openjdk haverá os melhores de dois mundos em um
só lugar.
5.Várias empresas fazem parte desse projeto, ou seja é uma JVM com o Know-how de várias
empresas em todo o mundo. Empresas como IBM, Apple, SAP, Mac, Azul, Intel, RedHat etc.
fazem parte do projeto.
6.Se a Oracle deixar o Java (Algo que eu acho muito difícil por diversos motivos) e deixar de
fazer a JVM. O OpenJDK não será em nenhum momento abalado já que existem outras
empresas apoiando além da comunidade.
Figura 21: Integração entre o HotSpot e o JRockit gera o OpenJDK
A diferença entre essas duas JVMs, HotSpot (a JVM mais popular da Sun atualmente da
Oracle) e o OpenJDK, está na adição de códigos fechados além de pequenas mudanças na
implementação para implementações fechadas para a JVM da Oracle, a dessemelhança é de cerca
de 4% de código. O que acontece é que nem todos os códigos foram abertos com êxito já que alguns
pertence a terceiros e são apenas licenciados na época pela Sun.
Toda mudança dentro do Java é realizada através da submissão de uma JSR, Java
Specification Requests, que é um documento que possui informações quanto a melhoria a ser feita e
seus impactos dentro da linguagem. Essas JSRs são selecionada a partir do JCP, Java Community
Process, que é composta por 31 instituições (Podemos destacar a participação da Oracle, SouJava e
London Comunity). Essas instituições tem a missão de votar a favor ou contra uma JSR. Quando
existe uma mudança na plataforma (JSE, JEE, JME) é dito que ela possui um guarda-chuva de
especificações (Já que uma mudança de plataforma é resultado de diversas JSRs, por exemplo com
o Java 7, documentada na JSR 336, possui dentro dela as JSRs 314 o projeto Coin, 203 o NIO2, 292
o invoke dynamic).
Com o OpenJDK não é diferente, todas as suas mudanças precisam estar documentadas em
JSRs que são votada pelo JCP, no caso de uma nova versão da plataforma JSE, precisa ter um
conjunto de JSR ou um guarda-chuva de especificação. No entanto para melhorias, refatorações
existe o JEP, JDK Enhancement Proposals ou propostas de melhorias para o JDK.
O código do projeto é mantido em mercurial e mais informações do projeto pode ser
encontrado em: http://openjdk.java.net/
O próximo passo será demonstrar como é simples compilar o projeto, para esse exemplo será
utilizado o Java 8, e para isso é necessário que o mercurial, o gcc e o JDK 7 está instalado. Feito
isso, realize os seguintes passos (nesse caso consideraremos que leitor usar o Linux como Sistema
Operacional):
hg clone http://hg.openjdk.java.net/jdk8/jdk8 jdk8 (para baixar o código fonte em sua máquina).
cd jdk8 (entrando no diretório, onde se encontra o código fonte).
sh get_source.sh (shell script para baixar o código fonte dos módulos da JVM).
./configure (realiza todas as configurações e solicita a instalação de dependências, caso necessite)
make all CC=gcc CPP=g++ ALLOW_DOWNLOADS=true (compila todo o projeto permitindo que
baixe outros módulos e dependências oriundos da internet).
Ao baixar o código se verá que o projeto OpenJdk é composto por subprojetos:
. (root) configurações comuns para compilar a OpenJDK
hotspot o código fonte para construir o OpenJDK (baseado no fonte do hotspot) Nesse projeto
é encontrado as implementações do GC
langtools o código fonte para o compilador e utilitários para a linguagem
jdk o código fonte da plataforma JSE, por exemplo, java.lang.String, java.lang.Object, etc.
jaxp o projeto JAXP
jaxws o projeto JAX-WS
corba o projeto Corba
nashorn o projeto nashorn
Tabela 3: subprojetos do OpenJDK
Esses subprojetos, conforme a tabela anterior, tem o seu código estruturado pensando no seu
lado multiplataforma, assim suas pastas são agrupadas por Sistema operacional e o código comum
para todos os SOs ficam em uma pasta “share” que são os recursos e códigos que serão utilizados
por todos os SOs.
Após conhecer como baixar, compilar, os subprojetos e a organização básica do projeto
sinta-se à vontade para estudar o código e fazendo modificações com fins de estudos, caso tenha
interesse de ajudar o projeto de alguma forma faça parte do projeto adote OpenJDK, que é um
projeto internacional que possui como principal objetivo de evoluir o projeto, lá haverá mais materiais,
por exemplo, para compilar em outros sistemas operacionais, realizar os testes da JVM, dentre outras
informações, basta acessar o seguinte link: https://java.net/projects/adoptopenjdk
9 – Conclusão
Muito se tem para discutir sobre JVM, além do fato da sua constante evolução. Vale salientar
que esse funcionamento é baseando da JVM 7 e muitos desses recursos deixarão de existir com a
evolução da plataforma, por exemplo, o metaspace no Java 8.
Com o objetivo de seguir o estudo e ajudar a desenvolver a plataforma existem os grupos de
usuários Java e seus projetos. Pode-se citar o Adopt o OpenJDK e também o SouJavaLivre.
Criei um pequeno projeto no github com os exemplos citados nessa pequena apostila:
https://github.com/otaviojava/imergindo_jvm
Caso encontra alguém problema com erro de português, queria tirar dúvidas, queria conhecer
mais sobre o OpenJDK, dentre outras coisas, esteja à vontade para me contatar o meu e-mail é:
otaviojava@gmail.com.
Espero que tenha gostado da apostila e que tenha ajudado no crescimento profissional.

Contenu connexe

Tendances

Introdução a Linguagem Java
Introdução a Linguagem JavaIntrodução a Linguagem Java
Introdução a Linguagem JavaUFPA
 
Java recursos avançados - multithreading
Java   recursos avançados - multithreadingJava   recursos avançados - multithreading
Java recursos avançados - multithreadingArmando Daniel
 
Palestra Desenvolvimento Ágil para Web com ROR UVA
Palestra Desenvolvimento Ágil para Web com ROR UVAPalestra Desenvolvimento Ágil para Web com ROR UVA
Palestra Desenvolvimento Ágil para Web com ROR UVAThiago Cifani
 
RubyMasters 2011 - Beyond Ruby with NodeJS
RubyMasters 2011 - Beyond Ruby with NodeJSRubyMasters 2011 - Beyond Ruby with NodeJS
RubyMasters 2011 - Beyond Ruby with NodeJSEmerson Macedo
 
Resolvendo problemas de dependências com o Bundler
Resolvendo problemas de dependências com o BundlerResolvendo problemas de dependências com o Bundler
Resolvendo problemas de dependências com o BundlerFrevo on Rails
 
Resolvendo problemas com o Bbundler
Resolvendo problemas com o BbundlerResolvendo problemas com o Bbundler
Resolvendo problemas com o BbundlerRodrigo Alves Vieira
 
C# 6.0 - DotNetBaixada - Novembro/2015
C# 6.0 - DotNetBaixada - Novembro/2015C# 6.0 - DotNetBaixada - Novembro/2015
C# 6.0 - DotNetBaixada - Novembro/2015Renato Groff
 
Linux Real-Time e Java Real Time, um mundo sem delays! por Flávio Buccianti
Linux Real-Time e Java Real Time, um mundo sem delays! por Flávio BucciantiLinux Real-Time e Java Real Time, um mundo sem delays! por Flávio Buccianti
Linux Real-Time e Java Real Time, um mundo sem delays! por Flávio BucciantiJoao Galdino Mello de Souza
 
Programação Paralela - Threads
Programação Paralela - ThreadsProgramação Paralela - Threads
Programação Paralela - ThreadsGlaucio Scheibel
 
Python - Programando em alto nível
Python - Programando em alto nívelPython - Programando em alto nível
Python - Programando em alto nívelIgor Sobreira
 
Três anos de Scala em Produção: desafios, aprendizados e dores de cabeça
Três anos de Scala em Produção: desafios, aprendizados e dores de cabeçaTrês anos de Scala em Produção: desafios, aprendizados e dores de cabeça
Três anos de Scala em Produção: desafios, aprendizados e dores de cabeçaFelipe Hummel
 
Programação Concorrente - Gerenciamento de Threads - Parte I
Programação Concorrente - Gerenciamento de Threads - Parte IProgramação Concorrente - Gerenciamento de Threads - Parte I
Programação Concorrente - Gerenciamento de Threads - Parte IFabio Moura Pereira
 

Tendances (20)

drools + robocode
drools + robocodedrools + robocode
drools + robocode
 
Introdução a Linguagem Java
Introdução a Linguagem JavaIntrodução a Linguagem Java
Introdução a Linguagem Java
 
Java recursos avançados - multithreading
Java   recursos avançados - multithreadingJava   recursos avançados - multithreading
Java recursos avançados - multithreading
 
Robocode
RobocodeRobocode
Robocode
 
Palestra Desenvolvimento Ágil para Web com ROR UVA
Palestra Desenvolvimento Ágil para Web com ROR UVAPalestra Desenvolvimento Ágil para Web com ROR UVA
Palestra Desenvolvimento Ágil para Web com ROR UVA
 
RubyMasters 2011 - Beyond Ruby with NodeJS
RubyMasters 2011 - Beyond Ruby with NodeJSRubyMasters 2011 - Beyond Ruby with NodeJS
RubyMasters 2011 - Beyond Ruby with NodeJS
 
Robocode
RobocodeRobocode
Robocode
 
Resolvendo problemas de dependências com o Bundler
Resolvendo problemas de dependências com o BundlerResolvendo problemas de dependências com o Bundler
Resolvendo problemas de dependências com o Bundler
 
Resolvendo problemas com o Bbundler
Resolvendo problemas com o BbundlerResolvendo problemas com o Bbundler
Resolvendo problemas com o Bbundler
 
C# 6.0 - DotNetBaixada - Novembro/2015
C# 6.0 - DotNetBaixada - Novembro/2015C# 6.0 - DotNetBaixada - Novembro/2015
C# 6.0 - DotNetBaixada - Novembro/2015
 
Linux Real-Time e Java Real Time, um mundo sem delays! por Flávio Buccianti
Linux Real-Time e Java Real Time, um mundo sem delays! por Flávio BucciantiLinux Real-Time e Java Real Time, um mundo sem delays! por Flávio Buccianti
Linux Real-Time e Java Real Time, um mundo sem delays! por Flávio Buccianti
 
52 java
52 java52 java
52 java
 
Programação Paralela - Threads
Programação Paralela - ThreadsProgramação Paralela - Threads
Programação Paralela - Threads
 
Sistemas Distribuídos - Multithreading
Sistemas Distribuídos - MultithreadingSistemas Distribuídos - Multithreading
Sistemas Distribuídos - Multithreading
 
Erros comuns em java
Erros comuns em javaErros comuns em java
Erros comuns em java
 
JavaFX 1.2
JavaFX 1.2JavaFX 1.2
JavaFX 1.2
 
Python - Programando em alto nível
Python - Programando em alto nívelPython - Programando em alto nível
Python - Programando em alto nível
 
Três anos de Scala em Produção: desafios, aprendizados e dores de cabeça
Três anos de Scala em Produção: desafios, aprendizados e dores de cabeçaTrês anos de Scala em Produção: desafios, aprendizados e dores de cabeça
Três anos de Scala em Produção: desafios, aprendizados e dores de cabeça
 
Programação Concorrente - Gerenciamento de Threads - Parte I
Programação Concorrente - Gerenciamento de Threads - Parte IProgramação Concorrente - Gerenciamento de Threads - Parte I
Programação Concorrente - Gerenciamento de Threads - Parte I
 
Freedomday2016 - Fique na caixinha, docker
Freedomday2016 - Fique na caixinha, dockerFreedomday2016 - Fique na caixinha, docker
Freedomday2016 - Fique na caixinha, docker
 

Similaire à Imergindo na JVM

Java basico modulo_01
Java basico modulo_01Java basico modulo_01
Java basico modulo_01Daniel Alves
 
Java basico modulo_01
Java basico modulo_01Java basico modulo_01
Java basico modulo_01rollbackpt
 
Java Fundamentos
Java FundamentosJava Fundamentos
Java FundamentosWilson Lima
 
Java, historico e futuro
Java, historico e futuroJava, historico e futuro
Java, historico e futuroFabiano Souza
 
Introdução a Tecnologia Java
Introdução a Tecnologia JavaIntrodução a Tecnologia Java
Introdução a Tecnologia JavaThiago Gonzaga
 
Programação Desktop: Uma abordagem com Java
Programação Desktop: Uma abordagem com JavaProgramação Desktop: Uma abordagem com Java
Programação Desktop: Uma abordagem com JavaRosicleia Frasson
 
Interop itcare: Interoperabilidade Java e .NET
Interop itcare: Interoperabilidade Java e .NETInterop itcare: Interoperabilidade Java e .NET
Interop itcare: Interoperabilidade Java e .NETAlessandro Binhara
 
Java não é tão difícil quanto parece
Java não é tão difícil quanto pareceJava não é tão difícil quanto parece
Java não é tão difícil quanto parecejesuinoPower
 
JAVA: Muito mais que uma linguagem
JAVA: Muito mais que uma linguagemJAVA: Muito mais que uma linguagem
JAVA: Muito mais que uma linguagemliverig
 
LIVRO PROPRIETÁRIO - PROGRAMAÇÃO I
LIVRO PROPRIETÁRIO - PROGRAMAÇÃO ILIVRO PROPRIETÁRIO - PROGRAMAÇÃO I
LIVRO PROPRIETÁRIO - PROGRAMAÇÃO IOs Fantasmas !
 
Programação Orientada a Objetos com Java
Programação Orientada a Objetos com JavaProgramação Orientada a Objetos com Java
Programação Orientada a Objetos com JavaÁlvaro Farias Pinheiro
 
Desmistificando a Certificação Sun Java Programmer
Desmistificando a Certificação Sun Java ProgrammerDesmistificando a Certificação Sun Java Programmer
Desmistificando a Certificação Sun Java ProgrammerMagno A. Cavalcante
 
JVM’s Livres - Paloma Costa
JVM’s Livres - Paloma CostaJVM’s Livres - Paloma Costa
JVM’s Livres - Paloma CostaTchelinux
 

Similaire à Imergindo na JVM (20)

Java20141215 17[1]
Java20141215 17[1]Java20141215 17[1]
Java20141215 17[1]
 
Java basico modulo_01
Java basico modulo_01Java basico modulo_01
Java basico modulo_01
 
Java basico modulo_01
Java basico modulo_01Java basico modulo_01
Java basico modulo_01
 
Java basico modulo_01
Java basico modulo_01Java basico modulo_01
Java basico modulo_01
 
Java basico modulo_01
Java basico modulo_01Java basico modulo_01
Java basico modulo_01
 
Java Fundamentos
Java FundamentosJava Fundamentos
Java Fundamentos
 
Java, historico e futuro
Java, historico e futuroJava, historico e futuro
Java, historico e futuro
 
Introdução a Tecnologia Java
Introdução a Tecnologia JavaIntrodução a Tecnologia Java
Introdução a Tecnologia Java
 
Conhecendo Java
Conhecendo JavaConhecendo Java
Conhecendo Java
 
Java e Software Livre
Java e Software LivreJava e Software Livre
Java e Software Livre
 
Java pode ser_hipster
Java pode ser_hipsterJava pode ser_hipster
Java pode ser_hipster
 
Programação Desktop: Uma abordagem com Java
Programação Desktop: Uma abordagem com JavaProgramação Desktop: Uma abordagem com Java
Programação Desktop: Uma abordagem com Java
 
Interop itcare: Interoperabilidade Java e .NET
Interop itcare: Interoperabilidade Java e .NETInterop itcare: Interoperabilidade Java e .NET
Interop itcare: Interoperabilidade Java e .NET
 
Java não é tão difícil quanto parece
Java não é tão difícil quanto pareceJava não é tão difícil quanto parece
Java não é tão difícil quanto parece
 
JAVA: Muito mais que uma linguagem
JAVA: Muito mais que uma linguagemJAVA: Muito mais que uma linguagem
JAVA: Muito mais que uma linguagem
 
LIVRO PROPRIETÁRIO - PROGRAMAÇÃO I
LIVRO PROPRIETÁRIO - PROGRAMAÇÃO ILIVRO PROPRIETÁRIO - PROGRAMAÇÃO I
LIVRO PROPRIETÁRIO - PROGRAMAÇÃO I
 
Java
JavaJava
Java
 
Programação Orientada a Objetos com Java
Programação Orientada a Objetos com JavaProgramação Orientada a Objetos com Java
Programação Orientada a Objetos com Java
 
Desmistificando a Certificação Sun Java Programmer
Desmistificando a Certificação Sun Java ProgrammerDesmistificando a Certificação Sun Java Programmer
Desmistificando a Certificação Sun Java Programmer
 
JVM’s Livres - Paloma Costa
JVM’s Livres - Paloma CostaJVM’s Livres - Paloma Costa
JVM’s Livres - Paloma Costa
 

Plus de Otávio Santana

NoSQL design pitfalls with Java
NoSQL design pitfalls with JavaNoSQL design pitfalls with Java
NoSQL design pitfalls with JavaOtávio Santana
 
Modern Cloud-Native Jakarta EE Frameworks: tips, challenges, and trends.
Modern Cloud-Native Jakarta EE Frameworks: tips, challenges, and trends.Modern Cloud-Native Jakarta EE Frameworks: tips, challenges, and trends.
Modern Cloud-Native Jakarta EE Frameworks: tips, challenges, and trends.Otávio Santana
 
Architecting Cloud Computing Solutions with Java [1.1]
Architecting Cloud Computing Solutions with Java [1.1]Architecting Cloud Computing Solutions with Java [1.1]
Architecting Cloud Computing Solutions with Java [1.1]Otávio Santana
 
Arquitetando soluções de computação em nuvem com Java
Arquitetando soluções de computação em nuvem com JavaArquitetando soluções de computação em nuvem com Java
Arquitetando soluções de computação em nuvem com JavaOtávio Santana
 
Build, run, and scale your Java applications end to end
Build, run, and scale your Java applications end to endBuild, run, and scale your Java applications end to end
Build, run, and scale your Java applications end to endOtávio Santana
 
Jakarta NoSQL: Meet the first Jakarta EE specification in the Cloud
Jakarta NoSQL: Meet the first Jakarta EE specification in the CloudJakarta NoSQL: Meet the first Jakarta EE specification in the Cloud
Jakarta NoSQL: Meet the first Jakarta EE specification in the CloudOtávio Santana
 
ORMs: Heroes or Villains Inside the Architecture?
ORMs: Heroes or Villains Inside the Architecture?ORMs: Heroes or Villains Inside the Architecture?
ORMs: Heroes or Villains Inside the Architecture?Otávio Santana
 
Jakarta EE Meets NoSQL at the Cloud Age
Jakarta EE Meets NoSQL at the Cloud AgeJakarta EE Meets NoSQL at the Cloud Age
Jakarta EE Meets NoSQL at the Cloud AgeOtávio Santana
 
Boost your APIs with GraphQL 1.0
Boost your APIs with GraphQL 1.0Boost your APIs with GraphQL 1.0
Boost your APIs with GraphQL 1.0Otávio Santana
 
Jakarta EE Meets NoSQL in the Cloud Age [DEV6109]
Jakarta EE Meets NoSQL in the Cloud Age [DEV6109]Jakarta EE Meets NoSQL in the Cloud Age [DEV6109]
Jakarta EE Meets NoSQL in the Cloud Age [DEV6109]Otávio Santana
 
Let’s Make Graph Databases Fun Again with Java [DEV6043]
Let’s Make Graph Databases Fun Again with Java [DEV6043]Let’s Make Graph Databases Fun Again with Java [DEV6043]
Let’s Make Graph Databases Fun Again with Java [DEV6043]Otávio Santana
 
Eclipse JNoSQL: One API to Many NoSQL Databases - BYOL [HOL5998]
Eclipse JNoSQL: One API to Many NoSQL Databases - BYOL [HOL5998]Eclipse JNoSQL: One API to Many NoSQL Databases - BYOL [HOL5998]
Eclipse JNoSQL: One API to Many NoSQL Databases - BYOL [HOL5998]Otávio Santana
 
The new generation of data persistence with graph
The new generation of data persistence with graphThe new generation of data persistence with graph
The new generation of data persistence with graphOtávio Santana
 
Eclipse JNoSQL updates from JCP September 11
Eclipse JNoSQL updates from JCP September 11Eclipse JNoSQL updates from JCP September 11
Eclipse JNoSQL updates from JCP September 11Otávio Santana
 
Stateless Microservice Security via JWT and MicroProfile - Guatemala
Stateless Microservice Security via JWT and MicroProfile - GuatemalaStateless Microservice Security via JWT and MicroProfile - Guatemala
Stateless Microservice Security via JWT and MicroProfile - GuatemalaOtávio Santana
 
Stateless Microservice Security via JWT and MicroProfile - Mexico
Stateless Microservice Security via JWT and MicroProfile - MexicoStateless Microservice Security via JWT and MicroProfile - Mexico
Stateless Microservice Security via JWT and MicroProfile - MexicoOtávio Santana
 
Eclipse JNoSQL: The Definitive Solution for Java and NoSQL Database
Eclipse JNoSQL: The Definitive Solution for Java and NoSQL DatabaseEclipse JNoSQL: The Definitive Solution for Java and NoSQL Database
Eclipse JNoSQL: The Definitive Solution for Java and NoSQL DatabaseOtávio Santana
 
Management 3.0 and open source
Management 3.0 and open sourceManagement 3.0 and open source
Management 3.0 and open sourceOtávio Santana
 
Building a Recommendation Engine with Java EE
Building a Recommendation Engine with Java EEBuilding a Recommendation Engine with Java EE
Building a Recommendation Engine with Java EEOtávio Santana
 

Plus de Otávio Santana (20)

NoSQL design pitfalls with Java
NoSQL design pitfalls with JavaNoSQL design pitfalls with Java
NoSQL design pitfalls with Java
 
Modern Cloud-Native Jakarta EE Frameworks: tips, challenges, and trends.
Modern Cloud-Native Jakarta EE Frameworks: tips, challenges, and trends.Modern Cloud-Native Jakarta EE Frameworks: tips, challenges, and trends.
Modern Cloud-Native Jakarta EE Frameworks: tips, challenges, and trends.
 
Architecting Cloud Computing Solutions with Java [1.1]
Architecting Cloud Computing Solutions with Java [1.1]Architecting Cloud Computing Solutions with Java [1.1]
Architecting Cloud Computing Solutions with Java [1.1]
 
Arquitetando soluções de computação em nuvem com Java
Arquitetando soluções de computação em nuvem com JavaArquitetando soluções de computação em nuvem com Java
Arquitetando soluções de computação em nuvem com Java
 
Build, run, and scale your Java applications end to end
Build, run, and scale your Java applications end to endBuild, run, and scale your Java applications end to end
Build, run, and scale your Java applications end to end
 
Jakarta NoSQL: Meet the first Jakarta EE specification in the Cloud
Jakarta NoSQL: Meet the first Jakarta EE specification in the CloudJakarta NoSQL: Meet the first Jakarta EE specification in the Cloud
Jakarta NoSQL: Meet the first Jakarta EE specification in the Cloud
 
ORMs: Heroes or Villains Inside the Architecture?
ORMs: Heroes or Villains Inside the Architecture?ORMs: Heroes or Villains Inside the Architecture?
ORMs: Heroes or Villains Inside the Architecture?
 
Jakarta EE Meets NoSQL at the Cloud Age
Jakarta EE Meets NoSQL at the Cloud AgeJakarta EE Meets NoSQL at the Cloud Age
Jakarta EE Meets NoSQL at the Cloud Age
 
Boost your APIs with GraphQL 1.0
Boost your APIs with GraphQL 1.0Boost your APIs with GraphQL 1.0
Boost your APIs with GraphQL 1.0
 
Jakarta EE Meets NoSQL in the Cloud Age [DEV6109]
Jakarta EE Meets NoSQL in the Cloud Age [DEV6109]Jakarta EE Meets NoSQL in the Cloud Age [DEV6109]
Jakarta EE Meets NoSQL in the Cloud Age [DEV6109]
 
Let’s Make Graph Databases Fun Again with Java [DEV6043]
Let’s Make Graph Databases Fun Again with Java [DEV6043]Let’s Make Graph Databases Fun Again with Java [DEV6043]
Let’s Make Graph Databases Fun Again with Java [DEV6043]
 
Eclipse JNoSQL: One API to Many NoSQL Databases - BYOL [HOL5998]
Eclipse JNoSQL: One API to Many NoSQL Databases - BYOL [HOL5998]Eclipse JNoSQL: One API to Many NoSQL Databases - BYOL [HOL5998]
Eclipse JNoSQL: One API to Many NoSQL Databases - BYOL [HOL5998]
 
The new generation of data persistence with graph
The new generation of data persistence with graphThe new generation of data persistence with graph
The new generation of data persistence with graph
 
Eclipse JNoSQL updates from JCP September 11
Eclipse JNoSQL updates from JCP September 11Eclipse JNoSQL updates from JCP September 11
Eclipse JNoSQL updates from JCP September 11
 
Stateless Microservice Security via JWT and MicroProfile - Guatemala
Stateless Microservice Security via JWT and MicroProfile - GuatemalaStateless Microservice Security via JWT and MicroProfile - Guatemala
Stateless Microservice Security via JWT and MicroProfile - Guatemala
 
Stateless Microservice Security via JWT and MicroProfile - Mexico
Stateless Microservice Security via JWT and MicroProfile - MexicoStateless Microservice Security via JWT and MicroProfile - Mexico
Stateless Microservice Security via JWT and MicroProfile - Mexico
 
Eclipse JNoSQL: The Definitive Solution for Java and NoSQL Database
Eclipse JNoSQL: The Definitive Solution for Java and NoSQL DatabaseEclipse JNoSQL: The Definitive Solution for Java and NoSQL Database
Eclipse JNoSQL: The Definitive Solution for Java and NoSQL Database
 
Polyglot persistence
Polyglot persistencePolyglot persistence
Polyglot persistence
 
Management 3.0 and open source
Management 3.0 and open sourceManagement 3.0 and open source
Management 3.0 and open source
 
Building a Recommendation Engine with Java EE
Building a Recommendation Engine with Java EEBuilding a Recommendation Engine with Java EE
Building a Recommendation Engine with Java EE
 

Imergindo na JVM

  • 2. Imergindo na JVM de Otávio Gonçalves de Santana
  • 3.
  • 4. Texto copyright © 2013 Otávio Gonçalves de Santana Este trabalho está licenciado sob a Licença Atribuição-NãoComercial-CompartilhaIgual 3.0 Brasil da Creative Commons. Para ver uma cópia desta licença, visite http://creativecommons.org/licenses/by-nc-sa/3.0/br/ ou envie uma carta para Creative Commons, 444 Castro Street, Suite 900, Mountain View, California, 94041, USA. Você tem a liberdade de: •Compartilhar — copiar, distribuir e transmitir a obra. •Remixar — criar obras derivadas. Sob as seguintes condições: Atribuição — Você deve creditar a obra da forma especificada pelo autor ou licenciante (mas não de maneira que sugira que estes concedem qualquer aval a você ou ao seu uso da obra). Uso não comercial — Você não pode usar esta obra para fins comerciais. Compartilhamento pela mesma licença— Se você alterar, transformar ou criar em cima desta obra, você poderá distribuir a obra resultante apenas sob a mesma licença, ou sob uma licença similar à presente. Ficando claro que: Renúncia — Qualquer das condições acima pode ser renunciada se você obtiver permissão do titular dos direitos autorais. Domínio Público — Onde a obra ou qualquer de seus elementos estiver em domínio público sob o direito aplicável, esta condição não é, de maneira alguma, afetada pela licença. Outros Direitos — Os seguintes direitos não são, de maneira alguma, afetados pela licença: • Limitações e exceções aos direitos autorais ou quaisquer usos livres aplicáveis; • Os direitos morais do autor; • Direitos que outras pessoas podem ter sobre a obra ou sobre a utilização da obra, tais como direitos de imagem ou privacidade.
  • 5. Prefácio Certamente o Java é atualmente uma das linguagens mais usadas e uma das mais populares no mundo, sendo que os seus maiores diferenciais não estão na linguagem e sim na JVM (máquina virtual Java). A JVM vem sendo alvo de muitos estudos e pesquisas, afinal conhecer bem a JVM vai garantir ao desenvolvedor Java, maneiras para tirar o melhor proveito da linguagem e da plataforma além de programar de maneira mais eficiente levando em consideração tudo o que a JVM pode fazer por ele e ajudando a mesma a te ajudar. Com esse objetivo estou criando esse pequeno texto, estar ajudando a comunidade brasileira a conhecer profundamente o software que é alicerce de inúmeros outros. Esse material é fruto dos meus trabalhos junto com o OpenJDK, a JVM open source e a partir da versão 7 se tornou a implementação de referência. Ao contrário do que muitas pessoas imaginam, existem milhões de máquinas virtuais, dentre as mais populares está a HotSpot (também conhecida como a JVM da Oracle). O OpenJDK é um projeto é vivo com ajuda de algumas empresas (Oracle, Intel, RedHat, AMD, Google, etc.), mas principalmente com a grande ajuda da comunidade, que dentre as diversas frentes de trabalho que existem podemos destacar o Adote o OpenJDK que visa a evolução da plataforma e do Java Livre (ajudando na refatoração, evangelizando sobre o OpenJDK, identificando e corrigindo bugs). Para facilitar o entendimento do leitor esse trabalho foi dividido em seis partes: A primeira falará um pouco sobre o Java, fazendo a devida separação entre linguagem, plataforma e máquina virtual além de falar um pouco sobre a história do Java e da sua evolução junto com as versões. Em seguida se falará sobre o funcionamento básico da JVM, fará a distinção básica entre o Java linguagem e da máquina virtual, já que a última precisa ser compilada para que a linguagem seja multiplataforma, o ciclo de vida da JVM e dos processos em paralelo que nascem com uma aplicação Java. Saber aonde fica cada informação dentro da JVM e o nome dos seus respectivos registradores, será o alvo dessa terceira parte do trabalho. Se saberá quais registradores serão compartilhados por todas as Threads e aqueles que são específicos de cada, assim nascem e morrem com a sua respectiva Thread. O bytecode, a linguagem da Máquina virtual, pouco é explicado sobre ela, mas são graças aos seus opcodes que a JVM é multiplataforma. Nessa parte se verá quão diferente é o seu código em Java e do produto gerado, o bytecode, além da estrutura que a classe adquire após o processo de compilação. A JVM consiste em um processo básico de pegar a informação da classe, gerar stream para dentro da JVM (Naturalmente na memória principal) e executá-lo o tornando em código nativo, esse processo de carregar uma classe é feita em tempo de execução, ou seja, só é carregada a classe X no momento em que ela for chamada, não basta estar apenas no import, caso essa classe tenha um pai ou implemente interfaces elas serão carregadas antes dessa classe X. Toda classe possui um ciclo de vida e conheça um pouco mais sobre este ciclo na parte número cinco.
  • 6. Um grande diferencial da JVM de algumas linguagens é o recurso de gerenciamento automático da memória, esse processo consistem em matar e recuperar memória de objetos que não estão mais sendo utilizados, esse é o papel do Garbage Collector. Conheça um pouco mais sobre as implementações e em quais situações elas são mais aconselhadas. Para finalizar será demonstrada uma visão prática do JNI e do projeto OpenJDK além dos conceitos de compilar a JVM.
  • 7. Sobre o Autor Otávio Gonçalves de Santana: Desenvolvedor entusiasta do mundo Open Source. Praticante da filosofia ágil e do desenvolvimento poliglota na Bahia, JUG Leader do JavaBahia, membro do SouJava, um dos fomentadores do grupo LinguÁgil. Presente nos maiores eventos Java e ágil em nível nacional, contribui para o projeto OpenJDK e a plataforma JSE, criador do Apache Easy-Cassandra, presente na comunidade Java mundial, membro ativo e/ou moderador dos maiores fóruns de língua portuguesa do mundo além de escrever artigos para DevMedia, revista espírito livre e java.net. http://about.me/otaviojava Arte da capa do Ebook Raul Libório é do openSUSE Project, onde atua na área de wiki, artwork, criação de artigos e tradução. Tem as motos como paixão e Muay Thai como esporte.
  • 8. Sumário 1 – Introdução da JVM 1.1 - Histórico da JVM 1.1.1 - JDK Alpha and Beta (1995) 1.1.2 - JDK 1.0 (23 de janeiro de 1996) 1.1.3 - JDK 1.1 (19 de fevereiro de 1997) 1.1.4 - J2SE 1.2 (8 de dezembro de 1998) 1.1.5 - J2SE 1.3 (8 de maio de 2000) 1.1.6 - J2SE 1.4 (6 de fevereiro de 2002) 1.1.7 - J2SE 5.0 (30 de setembro de 2004) 1.1.8 - Java SE 6 (11 de dezembro de 2006) 1.1.9 - Java SE 7 (28 de julho de 2011) 2 – Funcionamento básico da JVM 3 – Registradores da JVM 3.1 - Program Counter 3.2 - Java Stack (Java virtual machine stack) 3.3 - Stack Frame 3.3.1 - Stack variables 3.3.2 – Stack Operand 3.3.3 - Frame Data 3.4 - Native Method Stacks 3.5 - Method Area 3.6 - Heap Space 3.7 – Cache de código 3.7.1 - Just In Time (JIT) Compilation 4 - ByteCodes 5 – Ciclo de vida de uma classe 6 – Garbage Collector 6.1 – Implementação Serial 6.2 – Implementação Paralelo 6.3 – Implementação Concurrent 6.4 – Implementação Incremental Concurrent 6.4 – Implementação Garbage First 6.5 – Comandos para tunning da JVM 7 – Interface Nativa Java 8 – O projeto OpenJDK 9 – Conclusão
  • 9. 1 – Introdução da JVM JVM, java virtual machine ou máquina virtual java, tem sua história inciada em 1992 com o Green Project na época a linguagem era denominada de oak. Com o passar do tempo a máquina virtual foi evoluindo e ficando cada vez mais complexa. A linguagem java possui uma sintaxe similar ao C++, ele pode ser usado de várias maneiras e é orientado a objetos e se tornou popular em conjunto com a web. A JVM funciona como o alicerce da plataforma java ficando responsável por tratar todas as plataformas e SO de modo independente para a linguagem. A JVM não conhece absolutamente nada da linguagem Java, apenas o seu bytecode, que vem no formato .class, que são as instruções da JVM (daí a possibilidade de portar outras linguagens para a JVM já que ele não roda Java e sim o bytecode). Esse class é o código compilado em java e representa uma classe ou interface em java. Do seu inicio até a presente data o Java teve diversas entre as suas versões. Essas modificações são gerenciadas pelo JCP, Java Community Process (o comitê que rege as mudanças da plataforma java com cerca de 30 empresas), a partir de JSRs, Java Specification Requests, especificações que fornecem tais modificações e melhorias. Essas mudanças são documentadas no JSL, Java Language Specification. 1.1 - Histórico da JVM Antes de se falar dos aspectos do Java, como linguagem, plataforma e máquina virtual é interessante conhecer um pouco sobre a evolução que o Java vem sofrendo. O projeto nasceu em 1995 e a partir desta data veio softendo constante evolução com ajuda de empresas e da comunidade. 1.1.1 - JDK Alpha and Beta (1995) Nas versões alfas e betas se tiveram uma máquina instável. 1.1.2 - JDK 1.0 (23 de janeiro de 1996) Com o código nome Oak, que também foi o primeiro nome da linguagem. Na versão 1.0.2 foi lançado a primeira versão estável. 1.1.3 - JDK 1.1 (19 de fevereiro de 1997) • Grande melhorias e refatorações nos modelos de evento do AWT
  • 10. • Inner classes adicionado a linguagem • JavaBeans • JDBC • RMI • Reflection que suportava apenas introspecção apenas, nenhuma modificação em tempo real era possível 1.1.4 - J2SE 1.2 (8 de dezembro de 1998) Com o codinome Plaground. Essa e as outras versões foram denominadas de Java 2, J2SE Java 2 Platform, Standard Edition, Houve modificações significantes nessa versão triplicando o código para 1520 classes em 59 pacotes incluindo: • palavra-chave strictfp • A api do Swing foram integrados ao Core • Adicionando o JIT compilador • Java Plug-in • Java IDL, uma implementação CORBA IDL para interoperabilidade • Collections framework 1.1.5 - J2SE 1.3 (8 de maio de 2000) Com o codinome Kestrel, as modificações mais importantes foram: HotSpot JVM incluído (a JVM HotSpot foi lançado em abril de 1999 para os 1,2 J2SE JVM) • RMI foi modificado para suportar a compatibilidade opcional com CORBA • JavaSound • Java Naming and Directory Interface (JNDI) incluído em bibliotecas centrais • Java Platform Debugger Architecture (ACDP) • Sintéticos classes de proxy 1.1.6 - J2SE 1.4 (6 de fevereiro de 2002) Com o codinome Merlin, foi a primeira versão para a plataforma desenvolvida pelo JCP como
  • 11. a JSR 59: • Alterações na linguagem • a palavra-chave assert( JSR 41) • melhorias nas bibliotecas • Expressões regulares • encadeamento de exceção permite uma exceção para encapsular exceção de menor nível original • Suporte ao Protocolo de internet versão 6 (IPv6) • Chamadas de IO (chamado de NIO) novos Input/Output (JSR 51) • API de loggin (JSR 47) • API para ler e escrever imagens in formatos como JPED e PNG • Integração com o XML e XSLT (JAXP) na JSR 63 • Nova integrações com extensões de segurança e criptografia (JCE, JSSE, JAAS) • Java Web Start incluído (JSR 56) • API de preferências (java.util.prefs) 1.1.7 - J2SE 5.0 (30 de setembro de 2004) Com o codinome Tiger, foi desenvolvida na JSR 176, teve seu final de vida em 8 de abril de 2008 e o encerramento do suporte dia 3 de novembro de 2009. O Tiger adicionou um número significantes de melhorias para a linguagem: • Generics: (JSR14). • Annotations: (JSR 175) • Autoboxing/unboxing: Conversão automática entre os tipos primitivos e as classes encapsuladas (JSR 201). • Enumerations: (JSR 201.) • Varargs: • for each loop • Correções para o Java Memory Model(Que define como Threads interagem através da memória). • Static imports • Geração automática do stub para objetos RMI • Novo look and feel para o Swing chamado synth • Um pacote utilitário de concorrência ( java.util.concurrent)
  • 12. • A classe Scanner • Classe Scanner para analisar dados de input streams e buffers 1.1.8 - Java SE 6 (11 de dezembro de 2006) Com o codinome Mustangue, nessa versão a Sun substitui o nome “J2SE” e removeu o “.0” do número da versão e foi desenvolvida na JSR 270. • Suporte a linguagem de script JSR 223): Uma API Genérica para integração com linguagens scripts e foi embutido a integração com o Mozilla JavaScript Rhino. • Suporte a Web Service através do JAX-WS (JSR 224) • JDBC 4.0 (JSR 221). • Java Compiler API (JSR 199): uma API para permitir chamar compilação programando • JAXB 2.0: • melhorias no Annotations (JSR 269) • Melhorias no GUI, como SwingWorker, tabela filtrada e ordenada • Melhorias na JVM incluindo: sincronização e otimizações do compilador, melhorias no algorismo do coletor de lixo. 1.1.9 - Java SE 7 (28 de julho de 2011) Com o codinome Dolphin possui o maior número de atualização no Java. Foi lançado no dia 7 de Julho e foi disponibilizado no dia 28 de julho do mesmo ano. • Da Vinci Machine: Suporte para linguagens dinâmicas • Projeto Coin • Strings in switch • Automatic resource management in try-statement • Diamond • Simplified varargs • Binary integer literals • numeric literals • mult-try • Novo pacote utilitário de concorrência: JSR 166 • NIO2: novos biblioteca para IO Nesse primeiro capítulo houve uma pequena introdução sobre o interior da JVM e da sua importância para a plataforma além do aspecto histórico das versões do Java e de sua evolução tanto como plataforma, linguagem e máquina virtual.
  • 13. 2 – Funcionamento básico da JVM Nesse capítulo será falado um pouco do funcionamento básico da JVM além das variáveis. Falará um pouco do coração da linguagem Java, a sua JVM. Ela faz a independência entre as plataformas e roda basicamente dois tipos de processos: O escrito em java e são gerados bytecodes e os nativos que são realizados em linguagens como o CC++ que são linkadas dinâmicamentes para uma plataforma específica. Os métodos nativos são muito interessantes para obter informações do SO sendo utilizado além de usar recursos da máquina e é em função disso que apesar de a linguagem ser RunAnyWhere a JVM não, ou seja, se precisa usar a máquina virtual específica para aquela plataforma. Isso acontece, por exemplo, para usar recursos específicas de máquina, por exemplo, existem chamadas especificas para cada diretório e arquivos. Figura 1: A JVM precisa ser compilada para uma plataforma específica.
  • 14. O único e principal motivo da JVM é rodar o aplicativo, quando se iniciar uma execução a JVM nasce e quando a aplicação termina ela morre. É criado uma JVM para cada aplicação, ou seja, se executar três vezes o mesmo código em uma mesma máquina serão criadas 3 JVMs. Para rodar uma aplicação basta que sua classe possua um método público e estático com o nome main e tenha como parâmetro um vetor de String. Ao iniciar uma JVM existem alguns processos que rodam em paralelos e em backgrouns e executam diversas operações e processos para manter a JVM sempre disponível: • Os Timers que são responsáveis pelos eventos que acontecem periodicamente, por exemplo, interrupções, eles são usados para organizar os processos que acontecem continuamente. • Os processos do Garbage Collector é responsável por executar as atividades do coletor de lixo da JVM. • Compiladores que são responsáveis por transformar byte code em código nativo. • Os ouvintes, que recebem sinais (informações) e tem como principal objetivo enviar essas informações para o processo correto dentro da JVM. Falando um pouco mais sobre esses processos paralelos ou Thread, a JVM permite que múltiplos processos execute concorrentemente, essa rotina em Java está diretamente relacionada com uma Thread nativa. Tão logo um processo paralelo em Java nasça, os seus primeiros passos são alocação de memória, sincronização dos objetos, criação dos registradores específicos para a mesma e a alocação da Thread nativa. Quando essa rotina gera uma exceção a parte nativa envia essa informação para a JVM que a encerra. Quando a Thread termina todos os recursos específicos, tanto para em Java quanto a parte nativa, são entregues para JVM. Como na linguagem a JVM opera em dois tipos de dados: Os primitivos e os valores de referência. A máquina espera que toda a verificação quanto ao tipo tenha sido feito no momento da execução, sendo que os tipos primitivos não precisão de tal verificação ou inspeção já que eles operam com um tipo específico de instrução( por exemplo: iadd, ladd, fadd, e dadd para inteiro, long, float e double respectivamente). A JVM tem suporte para objetos que são ou instância de uma classe alocada dinamicamente ou um array, esses valores são do tipo reference, o seu funcionamento é semelhante de linguagens como C. Os tipos primitivos existentes na JVM são: numéricos, booleano e returnAdress, sendo que os tipos numéricos são os valores inteiros e flutuantes.
  • 15. Nome Tamanho variação Valor padrão Tipo byte 8-bit -2⁷ até 2⁷ 0 inteiro short 16-bits -2¹ até 2¹⁵ ⁵ 0 inteiro integer 32-bits -2³² até 2³¹ 0 inteiro long 64-bits -2 ³ até 2 ³⁶ ⁶ 0 inteiro char 16-bits UFT-8 'u0000' inteiro Float 32-bits 0 flutuante Double 64-bits 0 flutuante boolean inteiro false booleano returnAddress nulo ponteiro Tabela 1: relação dos tipos primitivos e os seus respectivos tamanho Os formatos de ponto flutuante são o float com precisão simples o double com dupla precisão no formato IEE 754 os valores e as operações como especificado no padrão IEEE para aritmética de ponto flutuante binário (ANSI / IEEE. 754-1985, Nova York). Esse padrão não inclui apenas valores positivos e negativos, mas zero, positivo e negativo infinito e não um número ( abreviado como Nan é utilizado para representar valores inválidos como divisão por zero). Por padrão as JVM suportam esse formato, mas também podem suportar versões estendidas de double e float. O returnAdress são usadas pela JVM, não possui representação na linguagem, tem seu funcionamento similar a ponteiros e diferentes dos tipos primitivos não podem ser modificados em tempo de execução. Na JVM o tipo booleano possui um suporte bem limitado, não existem instruções para booleano, na verdade eles são compilados para usar os tipos de instruções do int e o array de booleano são manipulados como array de byte. Os valores são representados com 1 para verdadeiro e 0 para falso. Falando um pouco sobre o tipo de referência, existem três tipos: classes, array e interfaces todas são instanciadas dinamicamente. Um array é uma matriz simples ou vetor cujo o seu tamanho não é definido pelo seu tipo. O Valor de referência é iniciado como nulo, o nulo não é um tipo definido, mas pode ser feito cast para qualquer tipo de referência. Recapitulando, existem basicamente dois tipos de dados: • Primitivos e Referência. • As referências tem os seus subtipos os de classe, interface e array. • Os primitivos possuem returnAdress, booleano, flutuantes (float e double de dupla e simples precisão respectivamente), inteiros (short, byte, int, long, char). Conforme mostra a figura1:
  • 16. Figura 2: Tipos de variáveis existentes dentro da JVM
  • 17. Com isso se apresentou os tipos de dados que são armazenados na JVM além do seu tamanho, o seu ciclo de vida além dos métodos nativos e escritos em java. O Java possui características multiplataformas, pelo fato que a JVM realiza essa abstração e para isso ela precisa ser compilada para a plataforma destino, vale salientar, que a JVM é composta por diversas linguagens além do Java (C, C++, shell script, Objective C, dentre outras) e essas necessitam dessa compilação para usar recursos específicos da plataforma e da máquina que a JVM rodarão.
  • 18. 3 – Registradores da JVM Falado um pouco sobre os tipos de dados que são armazenados na JVM e o seu tamanho é necessário também que se tenha informações de onde são armazenadas tais informações. A JVM usa registradores para armazenar várias coisas sendo que para todo tipo de dado existe um local específico. Nessa parte será falado um pouco os registradores da JVM. Durante a execução de um programa existem registrados que são compartilhados entre toda a JVM e outros que tem a visibilidade da Thread corrente, esses registradores serão falados abaixo: Figura 3: Os registradores da JVM, Method Area e o Heap são compartilhados por toda a JVM e o PC Counter, Pilha Java e a Pilha Nativa cada Thread possui a sua.
  • 19. 3.1 - Program Counter O registrador PC, Program counter, é criado tão logo uma Thread é criado, ou seja, cada Thread possui o seu. Ele pode armazenar dois tipos de dados: que são os ponteiros nativos e o returnAdress, esses dados possuem informações quanto o local aonde se encontra as instruções que estão sendo executadas pela Thread. Se o método executado não for nativo o PC conterá o endereço da instrução, returnAdress, caso seja um método nativo será um ponteiro e não tem o seu valor definido. 3.2 - Java Stack (Java virtual machine stack) Assim como o PC é um registrador privado para cada Thread, esse registrador armazena frames ( que será visto a frente). Seu funcionamento é similar a linguagens clássicas como o C, ele serve para armazenar variáveis locais e resultados parciais, invocações e resultados dos métodos. Ele não modifica as variáveis diretamente somente inserindo e removendo frames do registrador. Tão logo a corrente Thread chama um método um novo frame é inserindo contado informações como parâmetros, variáveis locais, etc. Assim quando o método termina de uma maneira normal, quando acaba o método, ou por interrupção, quando ocorre uma exceção dentro do método, esse frame é descartado. O Java Stack pode ter tamanho de fixo ou determinado dinamicamente. 3.3 - Stack Frame
  • 20. O frame é a unidade do Java Stack ele é criado tão logo se cria um método e é destruído quando o método é encerrado (normalmente ou ininterrompido por uma exceção). Cada frame possui uma lista das variáveis locais, pilha de operações além da referência da classe corrente e do método corrente. Esse frame é dividido em três partes: 3.3.1 - Stack variables Figura 4: Representação da pilha Java, composta por dois sub-registradores: Pilha de operações e a pilha de variáveis.
  • 21. Cada frame contém um vetor para armazenar variáveis locais e os parâmetros e esse tamanho é definido em tempo de execução. Nesse vetor as variáveis double e long ocupam dois elementos do vetor e são armazenados consequentemente. Variáveis do tipo int e returnAdress ocupam um elemento desse vetor (byte, short e char são convertidos para int). Caso o método seja da instância, não seja estático, o primeiro elemento desse vetor será ocupado pela instância que está executando esse método e em seguida os parâmetros, na ordem que foram passados. Caso o método seja da classe, o método seja estático, Não haverá referência da instância que chama o método, assim o primeiro elemento será os parâmetros. 3.3.2 – Stack Operand Figura 5: As pilhas de variáveis são compostas por índices de 32 bits, assim as variáveis do tipo double e long ocupam dois espaços contínuos e as outras variáveis apenas um.
  • 22. Como o nome indica esse registrador serve para armazenar as instruções que ocorrem dentro do método, como o registrador de variáveis locais os valores são armazenados em um vetor mas seus valores recuperados pela remoção do último elemento do vetor em vez de ser pelo índice. Ele é baseado na estrutura de dados de Pilha (Primeiro a entrar último a sair). O tamanho das variáveis acontecem de maneira semelhante as variáveis locais. Figura 6: Pilha de operação, semelhante ao seu “irmão”, sua unidade possui o tamanho de 32 bits, seu passo-a-passo segue o mesmo de uma pilha convencional, o ultimo que entrar será o primeiro a sair. Nesse exemplo será utilizado a soma de dois inteiros ( 10 e 20). Figura 7: Como a pilha de operação é composta por unidade de 32 bits, quando for double ou long ele ocupará as duas unidades seguidas, nesse exemplo são somados dois doubles ( 10.10 e 20.20)
  • 23. 3.3.3 - Frame Data Esse pequeno registrador possui o link da constant pool da classe que o possui o corrente método que está sendo executado. 3.4 - Native Method Stacks Possui finalidade semelhante ao registrador anterior, no entanto para armazenar variáveis e valores nativos (métodos escritos em outras linguagens), é criado tão logo uma Thread é iniciada e todas as Threads possuem o seu registrador. Figura 8: Pilhas nativas, nesse ponto não se sabe quais informações estão contidos nesses registradores. Também o PC counter, ele aponta para as ações, caso esteja java ele apontará para uma variável returnAderess, caso seja nativo não se sabe o valor no qual ele vai apontar.
  • 24. 3.5 - Method Area Esse registrador tem a finalidade de armazenar logicamente o stream da classe, essa área é compartilhada entre todas as Threads.E logicamente faz parte do Heap espace. Por ser parte do Heap ele possui o recolhimento de memória automático, Garbage Collector. As informações que contém as seguintes informações: • O qualified da classe (O qualifed é o endereço da sua classe que é definido pelo pacote mais “.” e o nome da Classe, por exemplo, java.lang.Object ou java.util.Date) • O qualified da classe pai (menos para as Interfaces e o java.lang.Object) • Informação se é uma classe ou interface • Os modificadores • A lista com os qualifieds das interfaces Para cada classe carregada no Java é carregada um constant pool Para cada classe é carregado as seguintes informações: • O constant pool do tipo (Para cada classe Carregada é criada um pool de constant, ele contém o link simbólico para os métodos e para os atributos além das constantes existentes no tipo). • informações dos atributos (o nome do atributo, o tipo e o seu modificador) • informação dos métodos (o nome do método, o seu retorno, o número e tipo dos parâmetros em ordem e o tipo e o seu modificador) • Referência para o ClassLoader (classe responsável para carregar a classe) • Variáveis da classe (variáveis compartilhadas entre todas as classes isso inclui as constantes) • Referência da classe (uma instância da java.lang.Class para toda classe carregada 3.6 - Heap Space Tão logo uma instância é criada as informações do seu objeto ficam armazenados aqui, esse espaço de memória também é compartilhado entre as Threads. O heap tem seu mecanismo de reclamar memória em tempo de execução além de mover objetos evitando a fragmentação do espaço.
  • 25. Representação de uma variável do tipo de referência dentro do Heap é diferente dos tipos primitivos, ele tem o seu mecanismo muito semelhante aos ponteiros do C/C++ já que ele não possui a informação, apenas aponta para o local que o possui. O objeto de referência é constituído de dois ponteiros menores, um apontará para o pool de objetos, local aonde estão as informações, e o segundo apontará para o seu constant pool (que possui as informações da classe quanto aos atributos, métodos, encapsulamentos, etc.) que fica localizado no method Area. Figura 9: Representação de uma variável do tipo de referência dentro do Heap
  • 26. A representação dos vetores se comporta de forma semelhante as variáveis de referência, mas eles ganham alguns basicamente dois campos a mais: O primeiro é o tamanho, que define o tamanho do vetor, e o segundo é uma lista de referência que apontam para os objetos que estão dentro desse vetor. 3.7 – Cache de código Esse registrador é usado para compilação e armazenamento dos métodos que foram compilados para o modo nativo pelo JIT, esse registrador é compartilhado por todas as Threads. 3.7.1 - Just In Time (JIT) Compilation O byte code Java interpretado não são tão rápido quanto os códigos nativos, para cobrir esse problema de performance, a JVM verifica métodos críticos (regiões que executam constantemente, por exemplo) e compila para o código nativo. Esse código nativo será armazenado dentro do cachê de código em uma região fora da heap. A JVM tenta escolher as regiões mais críticas para que mesmo com o gasto memória e poder computacional de compilar o código para nativo, seja uma Figura 10: Representação de um vetor dentro do Heap
  • 27. decisão vantajosa. Com isso foi falado sobre os registradores que contém na JVM, vale lembrar que algumas são exclusivas por Threads ou outra não. A pilha nativa são importantes para obter recursos da máquina, no entanto, os seus valores são indefinidos, já as pilhas java são criados tão logo um método é iniciado ele é subdividido em frames, ambas as pilhas são particular por Thread. O registrador PC não possui informações, apenas aponta para a instrução que está sendo executada e possui um por Thread. O Heap e o method Area são compartilhada entre a JVM e tão a responsabilidade de armazenar a instância dos objetos e o streams e informações da classe respectivamente.
  • 28. 4 - ByteCodes Uma vez tendo noção dos registradores e aonde ficam armazenados cada valor na JVM, será falado um pouco sobre o funcionamento das instruções, ela possui opcode e no tamanho de 8 bites ou 1 byte, daí o bytecode. Cada bytecode representa uma ação ou uma operação. A maioria das operações desse código operam para um tipo específico de valor, por exemplo, iload (carregar um int para a pilha) e o fload (carrega um float para a pilha) possuem operações semelhantes, no entanto, bytecodes diferentes. Uma boa dica para saber o tipo operado é saber a letra inicial da operação: i para uma operação de inteiro, l para long, s para short, b para byte, c para char, f para float, d para double e a para referência. As instruções podemos basicamente dividir em: Carregar e salvar informações: Essas instruções realizam troca de informações entre o vetor de variáveis locais e a pilha operações, sendo que carregar (definido por iload,lload,fload, dload e aload) informações da pilha de variáveis para operações e armazenar (definido por: istore, lstore, fstore, dstore, astore) realiza o processo inverso. Operações aritméticas: Elas são realizadas com os dois primeiros valores na pilha de operações e retornando o resultado. O seu processamento é subdividido em flutuantes e inteiros que possuem comportamento diferentes para alguns resultados, por exemplo, em estouro de pilha e divisão por zero. • adicionar: iadd, ladd, fadd, dadd. • subtrair: isub, lsub, fsub, dsub. • multiplicar: imul, lmul, fmul, dmul. • divisão: idiv, ldiv, fdiv, ddiv. • resto: irem, lrem, frem, drem. • negação: ineg, lneg, fneg, dneg. • deslocar: ishl, sidh, iushr, lshl, lshr, lushr. • bit a bit 'or': ior, lor. • bit a bit 'and': iand, a terra. • bit a bit ou exclusivo: ixor, lxor. • Variável local incremente: iinc. • Comparação: dcmpg, dcmpl, fcmpg, fcmpl, lcmp. Conversão de valores: As instruções de conversão de valores serve para fazer modificar o tipo da variável, essa variável pode ter seu tipo ampliado como: int para long, float, double. long para float e double e float para double( i2l, i2f, i2d, l2f, l2d, e f2d) e esse ampliamento perde não perde a precisão do valor original. E o encurtamento do tipo como: int para byte, short, ou char, long para int, float para int or long e double para int, long ou float (i2b, i2c, i2s, l2i, f2i, f2l, d2i,d2l, e d2f) vale lembrar que tais modificações existe a possibilidade de perder precisão e estouro do valor. Criação e manipulação de objetos: Instruções para a criação e manipulação de instâncias (new) e arrays( newarray, anewarray, multianewarray). Acessar atributos estáticos ou da instância de uma classe (getfield,putfield, getstatic, putstatic). Carregar (baload, caload, saload, iaload, laload, faload, daload, aaload) e salvar(bastore, castore,sastore, iastore, lastore, fastore, dastore e aastore) vetores além do seu tamanho (arraylength). Checa a propriedade da instância ou array (instanceof e checkcast ).
  • 29. Instruções condicionais (ifeq, ifne, iflt, ifle, ifgt, ifge, ifnull, ifnonnull, if_icmpeq, if_icmpne, if_icmplt, if_icmple, if_icmpgt if_icmpge, if_acmpeq, if_acmpne, tableswitch elookupswitch, goto, goto_w, jsr, jsr_w e ret), Chamada de métodos e retorno de valores: chama um método de uma instância(invokevirtual), chama um método de uma interface (invokeinterface), chamada de um método privado ou da superclasse (invokespecial), realiza a chamada de um método estático( invokestatic), método que constrói um objeto (invokedynamic). O retorno de uma instrução é pode ser definido(ireturn,lreturn, freturn, dreturn e areturn). Durante a execução do método caso seja interrompinda de maneira inesperada com uma exceção a chamada athrow é realizada. Os métodos sincronos são possíveis graças a presença de um simples os encapsulando chamado de monitor, esses tipos de métodos são definidos pela flag ACC_SYNCHRONIZED em seu constate pool, que quando possui tal flag o método entra no monitor(monitorenter) e é executado, e nenhuma outra Thread pode acessá-lo, e sai (monitorexit) quando seu método é encerrado (de um modo normal ou por interrupção). Uma vez falando dos bytecodes é interessantes “puxar o gancho” e falar como fica uma classe após sua compilação. Como já foi dito após a compilação é gerado um bytecode, cujo arquivo possui a extensão .class, e cada arquivo representa apenas uma classe. Cada arquivo possui as seguintes características: 1) Um número mágico em hexadecimal definindo que essa clase é um .class o valor é 0xCAFEBABE 2) O maior e menor número da versão do arquivo class que juntos definem a versão do arquivo, ou seja, JVM antes rodar precisa verificar se a versão V que ela pode executar estar entre: Menor Versão<V< Maior Versão. 3) access_flags: Essa flag indica o tipo de encapsulamento da classe, métodos e de suas propriedades os flags podem ser: ACC_PUBLIC - flag método, atributo públicos ACC_PRIVATE - flag para privados ACC_PROTECTED - protected ACC_STATIC - estático ACC_FINAL - final ACC_SYNCHRONIZED - indica um método sincronizado ACC_BRIDGE - indica que o método foi gerado pelo compilador ACC_VARARGS - indica que é varags ACC_NATIVE - nativo ACC_ABSTRACT - abstrato ACC_STRICT - indica que o método é strict ACC_SYNTHETIC - indica que o método não é “original” 4) this_class: o valor da corrente classe deve ter um índice válido na constant pool 5) super_class: as informações da superclasse devem estar dentro e pode ocupar o índice zero ou não, se ocupar o índice zero essa classe é o java.lang.Object, a única classe que não possui pai, do contrário terá que ser um índice válido e ter informações que apontam para a classe pai. As informações da classe é definida pelo seu nome com o seu caminho, por exemplo, a nome da String seria java/lang/String. 6) constant pool: O constant pool é uma estrutura de tabela que contém o nome das classes, interfaces, métodos, atributos e outras informações das classes. Para guardar essas informações existem dois registadores par cada informação importante: O vetor coma s informações da classe,
  • 30. método, ou atributos e o seu contador ou índice que funciona como limitador do vetor. Esse é o caso das interfaces que a classe implementa, atributos, métodos que possuem seus respectivos vetor e índices. As descrições dos atributos ou dos parâmetros em um método quanto ao seu tipo é definido a seguir: B byte signed byte C char D double F float I int J long L Classname ; referência S short Z boolean [ referência de um vetor [[ referência de uma matriz Assim, por exemplo: double dobro(double d) é igual (D)D e Double dobro(Double d) é (Ljava/lang/Double;)Ljava/lang/Double. Dentro da constant pool cada informação possui o seu primeiro byte que indica o seu tipo de informação: CONSTANT_Class 7 CONSTANT_Fieldref 9 CONSTANT_Methodref 10 CONSTANT_InterfaceMethodref 11 CONSTANT_String 8 CONSTANT_Integer 3 CONSTANT_Float 4 CONSTANT_Long 5 CONSTANT_Double 6 CONSTANT_NameAndType 12 CONSTANT_Utf8 1 CONSTANT_MethodHandle 15 CONSTANT_MethodType 16 CONSTANT_InvokeDynamic 18 StackMapTable: é composto de stackmapframe e tem o objetivo de verificações para o bytecode Para auxiliar a depuração na linguagem Java existem algumas informações para depurar o código essas variáveis são: LocalVariableTable e LocalVariableTypeTable que define as informações das variáveis locais para o debug e LineNumberTable define a parte do bytecode e sua correspondente linha de código. Para as anotações exitem: RuntimeVisibleAnnotations, RuntimeInvisibleAnnotations, RuntimeVisibleParameterAnnotations,RuntimeInvisibleParameterAnnotations que contém informações das anotações quanto a sua visibilidade em tempo de execução aos atributos e métodos ou não, existem essas mesmas informações para os parâmetros quanto as suas visibilidades.
  • 31. AnnotationDefault define as informações dentro das anotações. O contador da consant pool possui 16 bits ou seja ele só pode conter 2¹ =65535 elementos,⁶ esse mesmo número vale para o número de métodos, atributos, interfaces implementadas, variáveis locais, pilha de operações (sendo que para esses dois últimos o longo e o double ocupam dois espaços), o nome do método ou atributo. O número de dimensões de uma matriz é 255 o mesmo número vale para a quantidade de parâmetros, caso não seja um método estático deve-se incluir a instância. Com o objetivo de pôr em prática e visualizar esses bytes codes, será demonstrado um simples código e o seu respectivo bytecode. public class PrimeiroTeste{ public Double somarInstancias(Double a, Double b) { Double resultado = a + b; return resultado; } public double somarDouble(double a, double b) { return a + b; } public int somarInteiros(int a, int b) { return a + b; } public short somarShort(short a, byte b) { return (short) (a + b); } public static int somarStatic(int a, int b) { return a + b; } } Código 1: Código Java que será analisado no bytecode passo a passo. Criado o arquivo PrimeiroTeste.java e inserido o código 1 nesse arquivo, o próximos passos serão entrar pelo terminal no caminho que se encontra o arquivo PrimeiroTeste.java, compilar e analisar o seu respectivo byte code com os seguintes comandos. • javac PrimeiroTeste.java • javap -verbose PrimeiroTeste
  • 32. minor version: 0 major version: 51 flags: ACC_PUBLIC, ACC_SUPER Constant pool: #1 = Methodref #5.#21 // java/lang/Object."<init>":()V #2 = Methodref #22.#23 // java/lang/Double.doubleValue:()D #3 = Methodref #22.#24 // java/lang/Double.valueOf:(D)Ljava/lang/Double; #4 = Class #25 // PrimeiroTeste #5 = Class #26 // java/lang/Object #6 = Utf8 <init> #7 = Utf8 ()V #8 = Utf8 Code #9 = Utf8 LineNumberTable #10 = Utf8 somarInstancias #11 = Utf8 (Ljava/lang/Double;Ljava/lang/Double;)Ljava/lang/Double; #12 = Utf8 somarDouble #13 = Utf8 (DD)D #14 = Utf8 somarInteiros #15 = Utf8 (II)I #16 = Utf8 somarShort #17 = Utf8 (SB)S #18 = Utf8 somarStatic #19 = Utf8 SourceFile #20 = Utf8 PrimeiroTeste.java #21 = NameAndType #6:#7 // "<init>":()V #22 = Class #27 // java/lang/Double #23 = NameAndType #28:#29 // doubleValue:()D #24 = NameAndType #30:#31 // valueOf:(D)Ljava/lang/Double; #25 = Utf8 PrimeiroTeste #26 = Utf8 java/lang/Object #27 = Utf8 java/lang/Double #28 = Utf8 doubleValue #29 = Utf8 ()D #30 = Utf8 valueOf #31 = Utf8 (D)Ljava/lang/Double; Código 2: Constant Pool da Classe, nessa tabela estão contidas informações da classe, como os métodos, atributos, classes pai, encapsulamentos, meta datas, essas informações são armazenadas no vetor. Acima fica a maior e a menor versão do bytecode. Nesse primeiro resultado podemos visualizar a constant pool e a menor e a maior versão do class. O Constant Pool contém as informações da respectiva classe, já que toda classe possui essa informação, inclusive as InnerClasses, e eles são armazenadas em um vetor.
  • 33. public PrimeiroTeste(); flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return LineNumberTable: line 1: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this LPrimeiroTeste; public java.lang.Double somarInstancias(java.lang.Double, java.lang.Double); flags: ACC_PUBLIC Code: stack=4, locals=4, args_size=3 0: aload_1 1: invokevirtual #2 // Method java/lang/Double.doubleValue:()D 4: aload_2 5: invokevirtual #2 // Method java/lang/Double.doubleValue:()D 8: dadd 9: invokestatic #3 // Method java/lang/Double.valueOf:(D)Ljava/lang/Double; 12: astore_3 13: aload_3 14: areturn LineNumberTable: line 5: 0 line 6: 13 LocalVariableTable: Start Length Slot Name Signature 0 15 0 this LPrimeiroTeste; 0 15 1 a Ljava/lang/Double; 0 15 2 b Ljava/lang/Double; 13 2 3 resultado Ljava/lang/Double; public double somarDouble(double, double); flags: ACC_PUBLIC Code: stack=4, locals=5, args_size=3 0: dload_1 1: dload_3 2: dadd 3: dreturn LineNumberTable: line 10: 0 LocalVariableTable: Start Length Slot Name Signature 0 4 0 this LPrimeiroTeste; 0 4 1 a D 0 4 3 b D public int somarInteiros(int, int);
  • 34. flags: ACC_PUBLIC Code: stack=2, locals=3, args_size=3 0: iload_1 1: iload_2 2: iadd 3: ireturn LineNumberTable: line 13: 0 LocalVariableTable: Start Length Slot Name Signature 0 4 0 this LPrimeiroTeste; 0 4 1 a I 0 4 2 b I public short somarShort(short, byte); flags: ACC_PUBLIC Code: stack=2, locals=3, args_size=3 0: iload_1 1: iload_2 2: iadd 3: i2s 4: ireturn LineNumberTable: line 17: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this LPrimeiroTeste; 0 5 1 a S 0 5 2 b B public static int somarStatic(int, int); flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=2, args_size=2 0: iload_0 1: iload_1 2: iadd 3: ireturn LineNumberTable: line 21: 0 LocalVariableTable: Start Length Slot Name Signature 0 4 0 a I 0 4 1 b I Código 3: Os métodos com suas informações tamanho da pilha de operações, stack, tamanho da pilha de variáveis, locals e o número de informações envolvidas, args_size A vermos os métodos podemos perceber que todos os métodos possui o tamanho da pilha de operação e de variáveis além do tamanho das variáveis envolvidas em um determinado método. No primeiro que é o método construtor, esse método é construído automaticamente caso não seja feita
  • 35. pelo usuário, ele possui 1 tamanho de operação, já que se trata da criação de um objeto do tipo referência e esse ocupa um espaço no vetor de operação, 1 no tamanho de variável, já que ele é um método não estático e essas informações pertence a interface que a está chamando e o número de variáveis utilizadas é de um já que estamos nos referindo a instância criada. No segundo método, a soma de instâncias e retorna uma terceira instância, (Ljava/lang/Double;Ljava/lang/Double;)Ljava/lang/Double, ele possui o tamanho de três na pilha de variáveis, já que uma para as duas variáveis de referência e uma já que o método é da instância, quatro no tamanho de pilha de operações, já que no processo os Doubles serão transformados em double primitivo, assim cada um ocupará duas unidades no vetor. O campo LineNumberTable é para ajudar a debutar o código num determinado método, LineNumberTable 5: 0, diz que a linha cinco do código java equivale a instrução do começo, linha zero do byte code e line 6: 13, a linha seis do código java começa na instrução do bytecode número 13. O campo LocalVariableTable também serve para debugar e define o nome do campo, tipo, linha que ele nasce e a que morre. Isso demonstra como é diferente o código Java e o ByteCode gerado. Nesse capítulo falamos um pouco sobre o bytecode e o seu macete para entender, olhando pelas inicias do comando, vale lembrar que durante a execução os booleandos, byte, shorts são transformados para inteiros, assim suas operações são realizadas como inteiros. Se demonstrou quão diferente é o código Java do bytecode gerado e em função disse se criou tabelas e variáveis, por exemplo, LocalVariable e LineNumberTable para auxiliar na hora de debugar, essas variáveis são utilizadas pelo modo debug das IDEs modernas.
  • 36. 5 – Ciclo de vida de uma classe Toda classe pela JVM possui o seu ciclo de vida, mas ante de ser iniciada o seu objetivo é criar dinamicamente e isso é feito sob demanda, ou seja a class X será carregada no momento em que for necessário, ao instanciar um objeto é feito o processo de encontrar a representação binária da classe, carregar as informações e colocar a sua classe em Stream dentro da JVM, então criar o objeto. Todas as classes precisam passar por esse processo inclusive a classe que inicia a JVM. Caso a mesma seja filha ou implemente interfaces as mesmas terão de ser carregadas primeiro. Como cada um desses três processos possui detalhes, se discriminará as ações de cada um. O carregamento de classe consiste em subir a classe para memória principal e colocar na JVM, esse processo acontece uma vez com pot qualifield, com esse stream carregado se realiza o parser para o registrador method Area e concluindo gerar a interface que representa tal arquivo, o java.lang.Class. A interface Class é o produto do processo de carregar a classe, que é a representação do arquivo, com isso ele contém as informações do mesmo, como lista dos métodos, atributos, Figura 11: O ciclo normal de uma classe Java, primeiro é carregada para dentro da JVM, dentro da memória principal e virando stream, em seguida suas instruções são “traduzidas” para o execução nativa.
  • 37. interfaces, anotações, etc. As Classes por sua vez são carregadas pelo ClassLoader (com exceção dos array que não possui representação binária) na qual existem dois tipos: a bootstrap que carregam as classes da API e a segunda que é responsável por carregar as classes restantes. Na JVM existem múltiplas classe loaders com diferentes regras, assim podemos classificar-las como: • BootStrap ele se encontra no topo da hierarquia dos class loaders, esse objeto é responsável por estar carregado a API básica do Java, e os objetos que possuam um altíssimo nível de confiança pela JVM. • Extensão é responsável por carregar as API padrões do Java como as de segurança e Collection. • O system esse é o class loader padrão da aplicação, ele é responsável por carregar as classes que estão contidas no classpath. • Abaixo do System Class Loader o usuário adicionará um class loader, que tem alguns motivos especiais, entre eles definir um grupo de class loader específico para um domínio ou aplicação, é o caso dos servidores de aplacação como o tomcat e o Glassfish. Figura 12: Hierarquia dos Class Loaders
  • 38. Após a classe ser carregada o próximo passo será linkar para JVM, esse processo consiste na verificação da classe recém-carregada, ele verifica a palavra-chave, se a estrutura está correta, o tamanho dos arquivos, após a verificação são alocadas memórias para os atributos e serão setados os valores padrão dos campos, são carregados os atributos estáticos, encerrando esse processo todos os link de referência são substituídos por links diretos. No último estágio será a criada a instância que a chamada do método construtor se o mesmo tiver, sendo que antes é chamado o construtor da superclasse, não existe verificação para as interfaces apenas se os métodos foram implementados. Figura 13: Processo em que a classe é carregada para dentro da JVM, verificada, então é instanciado o objeto da respectiva classe. Lembrando caso ele tenha “pais” as classes pais terão de fazer esse processo primeiro.
  • 39. 6 – Garbage Collector Diferente de algumas linguagens o java possui um gerenciamento de memória automática, isso permite que a memória que de um objeto não mais utilizado seja retomada, essa certamente é uma das grandes vantagem da plataforma em relação ao C. O primeiro desafio da JVM é identificar quais objetos dispensáveis e assim retomar a memória com isso foi realizado várias técnicas para assim o fazer. Assim podemos definir dois algorismo: O mais conhecido certamente é o Mark and Sweep em basicamente dois processos que marca os objetos utilizados e no final os objetos não marcados são dispensáveis para retomar a memória, o maior problema é que todos os processos são parados para executar tal procedimento inviabilizando o chamado desse processo constantemente. Em função desse problema citado anteriormente falaremos do segundo algorismo, que leva em consideração que muitos objetos não possuem uma longa vida, no entanto, alguns levam bastante tempo na memória, assim os algoritmos se baseia em gerações que divide a memória em três partes (jovem, efetiva e permanente). Para melhor gerenciar o coletor de lixo a memória heap é dividia basicamente em três partes: Figura 14: Estilo mark and sweep, em que os objetos utilizados são marcados, os não utilizados são marcados, após esses dois passos o próximo passo será desfragmentar a memória principal.
  • 40. Young Generation: É onde contém os objetos recém-criados, a grande maioria dos objetos morrem dentro dessa área. Ela é subdivida em duas partes: Edern (local aonde o objetos nascem) e SurviverN locais aonde os objetos vão passando até sair da Young Generation. O seu funcionamento é de maneira simples: Os objetos nascem no Edem, depois de um tempo, os objetos são copiados “vivos” para os Suvivers, os objetos que não foram copiados não são apagados, mas no futuro, outros objetos ocuparão seu espaço. Com o passar das coleções os objetos existentes saem da Young e vai para Tenured generation, nesse espaço o gerenciamento de objetos é realizado de forma diferente, assim não há cópia, existem algoritmos derivados do Swep and Mark, com os objetos apagados a próxima preocupação será em relação a fragmentação do registrador, assim haverá o processo de compactação. Comentado um pouco sobre os processos de minor collector (procedimento de generation que copia objetos para registradores sobreviventes e a JVM não para) e o maior collector (procedimento cujo os algoritmos são derivados de Mark and Swep que apaga os objetos não utilizados e quando fragmentada a memória haverá o procedimento de compactação, vale lembrar que para tal procedimento a JVM para todos os seus processos). O objetivo agora será falar o estilo ou o modo dos Garbage Collector. 6.1 – Implementação Serial Figura 15: Divisão da memória por geração Figura 16: A implementação Serial
  • 41. Esse estilo é muito indicado para pequenas aplicações ou hardware de pequeno poder computacional e apenas um processador (monocore), ele se na baseia na execução dos processos (maior e menor collector) utilizando apenas uma Thread acaba economizando, porém caso haja um grande número de memória haverá um grande delay. Assim haverá uma Thread para minor collector, serial, e para o maior collector, serialOld. 6.2 – Implementação Paralelo Trabalha de forma semelhante com a forma semelhante ao serial, no entanto, será utilizado duas ou mais Threads por coleção, assim realizando por menos tempo. Existem três implementações: Parallel Scavenge: minor collector utilizando várias threads, ParNew: semelhante ao anterior, seu diferencial é que foi otimizado para o uso com o collector Concurrent, Parallel Old: atua com maior collector com várias Threads e usa o Mark Sweep ao mesmo tempo que executa a compactação. 6.3 – Implementação Concurrent Esse também executa processos em paralelos, no entanto, o seu objetivo é diminuir o tempo do maior collector, mesmo que para isso o execute várias vezes. Indicado para muitos objetos duram muito tempo, assim eles ficam na Turnered. Em resumo seu processo divide realizar marcação em que todas as Thread estão paradas e marcações concorrentes, mas a remoção dos objetos ocorrem sem nenhum processo parar, o único problema desse estilo é o fato que não há compactação de dados periódicos, apenas quando se torna crítico (usando o SerialOdl). Figura 17: Implementação Paralelo Figura 18: Implementação Concurrent
  • 42. 6.4 – Implementação Incremental Concurrent Também é concorrente, mas é realizado de forma incremental (realizado aos poucos e agendado entre as minor-collector) seu funcionamento é bem semelhante ao anterior, mas adiciona um processo que é redimensionar e preparar os dados para uma próxima coleção o ciclo que controla o tempo que o colector fica no processador. Caso tenha problemas com fragmentação, ele também acionará o serialOdl (que além de remover os dados também compactará os objetos sobreviventes). 6.4 – Implementação Garbage First Lançado na versão 7 do Java, o Garbage first, é coletor paralelo projetada para ter um grande throughput, ele foi projetado para sistemas com uma alta quantidade de memória e de processadores. Para alcançar seu objetivo ele divide igualmente o tamanho do Heap. Assim como os dois últimos concorrentes, ele possui uma faze em que realiza a marcação concorrente, e realiza o Figura 19: Implementação Incremental Concurrent Figura 20: Implementação Garbage First
  • 43. calculo de objetos alcançáveis de cada região, assim de tempos em tempos, todos os processos são parados os objetos vivos são copiados para outra região, fazendo com que a região em questão seja totalmente tomada. Terão maior prioridade as regiões com o maior número de objetos “mortos” ( assim se terá menos trabalho em realizar a copia para a outra área). O G1 veio para substituir os tipos concorrente de coleção (CMS e I-CMS) devido a lacuna que ambos deixavam: Não ter um tempo determinado para deixar o processador e a compactação do heap (uma vez que muito crítica chamada o serialOdl). O G1 toma como estratégia o fato de ser mais fácil controlar pequenas regiões do que uma geração, um outro aspecto é que tão logo as memórias existentes tenha sido copiado para uma nova área, a anterior é considerada uma área limpa. 6.5 – Comandos para tunning da JVM Abaixo os comandos e parâmetros para a configuração do tamanho da heap e da JVM: 1. -Xms<N>. Especifica o tamanho inicialmente reservado da memória heap em N megabytes. 2. -Xmx<N>. Especifica o tamanho máximo da memória heap em N megabytes. 3. -XX:MinHeapFreeRatio=<N>. Especifica a porcentagem mínima de espaço livre da memória heap. Se o espaço livre vier a ser menor que N%, o tamanho da memória heap será aumentado para garantir esta porcentagem de espaço livre mínimo. 4. -XX:MaxHeapFreeRatio=<N>. Especifica a porcentagem máxima de espaço livre da memória heap. Se o espaço livre vier a ser maior que N%, o tamanho da memória heap será diminuído para garantir esta porcentagem de espaço livre máximo, 5. -XX:NewRatio=<N>. Especifica a proporção de tamanho 1:N entre Young generation e o resto da memória heap. Por exemplo, se N=3, então a proporção será 1:3, ou seja, a Young generation ocupará 1/4 do espaço total da memória heap. 6. -XX:NewSize=<N>. Especifica o tamanho inicialmente reservado da Young generation em N megabytes. É uma alternativa a -XX:NewRatio pois pode ser difícil estimar este tamanho em proporção 1:N. 7. -XX:MaxNewSize=<N>. Especifica o tamanho máximo da Young generation em N megabytes. 8. -XX:SurvivorRatio=<N>. Especifica a proporção de tamanho 1:N entre cada espaço Survivor e Eden. Por exemplo, se N=6, então a proporção será 1:6, ou seja, cada espaço Survivor ocupará 1/8 do espaço total da Young generation (pois há dois espaços Survivor). 9. -XX:PermSize=<N>. Especifica o tamanho inicialmente reservado da Permanent generation em N megabytes. 10. -XX:MaxPermSize=<N>. Especifica o tamanho máximo da Permanent generation em N megabytes. Além disto, há as seguintes opções para imprimir logs sobre Garbage Collection, essas informações são úteis verificar o resultado do processo do GC, horários que são realizados, estatísticas de objetos, dentre outras funções.
  • 44. 1. -verbosegc. Imprime uma linha no console a cada collection realizada, no formato [GC <tamanho da memória heap antes da collection> -><tamanho da memória heap após a collection> (<tamanho máximo da memória heap>), <tempo de pausa> secs]. 2. -XX:+PrintGCDetails. Similar a -verbosegc, mas inclui mais informações como os detalhes da execução de cada collector. 3. -XX:+PrintGCTimeStamps. Quando usado com -XX:+PrintGCDetails mostra os horários em que cada collection foi realizada. 4. -XX:+PrintGCDateStamps. Quando usado com -XX:+PrintGCDetails mostra as datas em que cada collection foi realizada. 5. -XX:+PrintReferenceGC. Quando usado com -XX:+PrintGCDetails mostra estatísticas de objetos de referência fraca, como WeakReference, SoftReference e PhantomReference. 6. -XX:+PrintTenuringDistribution. Imprime uma linha no console a cada collection realizada a respeito da utilização dos espaços Survivor e um threshold indicando quantas vezes um objeto pode ser copiado dentro da Young generation antes de ser considerado apto para pertencer à Tenured generation. Esses comandos possui detalhes quanto ao estilo do GC que será utilizado, essas opções são importantes e vão variar de acordo com a máquina ( número de processadores, tamanho do heap) e com aplicação ( média de objetos que são criados, tempo médio de vida, etc.) 1. -XX:UseSerialGC. Esta opção seleciona as implementações Serial (para Young generation) e SerialOld (para Tenured generation). 2. -XX:UseParallelGC: seleciona as implementações Parallel Scavenge (para Young generation) e Serial Old (para Tenured generation). 3. -XX:UseParNewGC: seleciona as implementações ParNew(para Young generation) e Serial Old (para Tenured generation). 4. -XX:UseParallelOldGC:seleciona as implementaçõesParallel Scavenge(paraYoung generation) e Parallel Old (paraTenured generation). 5- -XX:UseConcMarkSweepGC. Esta opção seleciona as implementações ParNew (para Young generation), CMS e Serial Old (ambos para Tenured generation). Neste caso, primeiramente tentará utilizar CMS, mas se houver problemas de fragmentação, utilizará Serial Old. 6- XX:UseConcMarkSweepGC e -XX:+CMSIncrementalMode. Estas opções selecionam as implementações ParNew (para Young generation), i-CMS e Serial Old (ambos para Tenured generation). Novamente, primeiramente tentará utilizar i-CMS, mas se houver problemas de fragmentação, utilizará Serial Old. Além do estilo ou tunnig que a GC será usado na execução da aplicação também é possível uma customização mais avançada da JVM, essas modificações são mais avançadas e são recomendadas.
  • 45. 1. -XX:ParallelGCThreads=<N>. Especifica o número de threads e Garbage Collection a serem utilizadas. Por padrão, o collector Parallel utilizará X threads de Garbage Collection em uma máquina com X processadores. Tipicamente, em uma máquina com 1 processador, o collector Parallel terá performance pior que o collector Serial. Em uma máquina com 2 processadores ou mais, com uma quantidade média a grande de dados, o collector Parallel já se sobressai. 2. -XX:MaxGCPauseMillis=<N>. Especifica a pausa máxima desejada. Por padrão, não há pausa máxima desejada previamente definida. A utilização desta opção faz com que o tamanho da memória heap e outros parâmetros sejam ajustados para tentar manter as pausas menores ou iguais a N milissegundos, podendo assim afetar o throughput da aplicação. Contudo, não há garantias que o tempo de pausa será menor ou igual a N milissegundos em todas as execuções. 3. -XX:GCTimeRatio=<N>. Especifica a razão de tempo total para Garbage Collection na aplicação, segundo a fórmula 1 / (1 + <N>). Por exemplo, -XX:GCTimeRatio=19 define a razão de 1/20 ou 5% como o tempo total para Garbage Collection na aplicação. 4. -XX:YoungGenerationSizeIncrement=<Y>. Especifica a porcentagem de incremento quando o tamanho da Young generation aumenta. Por padrão, é 20%. 5. -XX:TenuredGenerationSizeIncrement=<T>. Especifica a porcentagem de incremento quando o tamanho da Tenured generation aumenta. Por padrão, é 20%. 6. -XX:AdaptiveSizeDecrementScaleFactor=<D>. Especifica o fator D para calcular a porcentagem de decremento quando o tamanho de alguma generation diminui. Tal porcentagem é calculada como X / D, onde X é a porcentagem de incremento. Por padrão, a porcentagem de decremento é 5%. 7. -XX:DefaultInitialRAMFraction=<N>. Especifica o fator N para calcular o tamanho inicial da memória heap, que é igual a R / N, onde R é o tamanho da memória RAM da máquina. Por padrão, N é 64. 8. -XX:DefaultMaxRAMFraction=<N>. Especifica o fator N para calcular o tamanho máximo da memória heap, que é calculada como o valor mínimo entre 1 GB ou R / N, onde R é o tamanho da memória RAM da máquina. Por padrão, N é 4. 9. -XX:-UseGCOverheadLimit. Desabilita o disparo de OutOfMemoryError quando mais de 98% do tempo total é usado em Garbage Collection, sobrando menos de 2% para a aplicação. Essas configurações, são como anterior, configurações mais avanças, mas são para as implementações do GC paralelas ou concorrentes. 1- -XX:CMSInitiatingOccupancyFraction=<N>. Especifica a porcentagem de ocupação da Tenuredgeneration necessária para disparar uma collection. Por padrão, este valor é aproximadamente 92%. 2. -XX:+CMSIncrementalPacing. Habilita automaticpacing, que é a estimativa automática do duty cycle baseado em estatísticas da JVM. Por padrão, é habilitado. 3. -XX:+CMSIncrementalDutyCycle=<N>. Especifica a porcentagem de tempo entre Minor collections quando o collector pode executar. Se automatic pacing está habilitado, especifica apenas o valor inicial. Por padrão, é 10.
  • 46. 4. -XX:CMSIncrementalSafetyFactor=<N>. Especifica a porcentagem de uma margem de segurança que será adicionada ao tempo de execução das Minor collections. Por padrão, é 10. 5. -XX:CMSIncrementalOffset=<N>. Especifica a porcentagem na qual o duty cycle tem seu início intencionalmente atrasado. Por padrão, é 0. 6. -XX:CMSExpAvgFactor=<N>. Especifica a porcentagem usada para pesar a amostra atual quando computar médias exponenciais para as estatísticas de collections concorrentes. Por padrão, é 25. 7- XX:+UnlockExperimentalVMOptions -XX:+UseG1GC. Na JVM HotSpot 7 apenas o segundo parâmetro é necessário. 8. -XX:MaxGCPauseMillis=<P>. Especifica o tempo de pausa máximo desejado, em milissegundos. 9. -XX:GCPauseIntervalMillis=<I>. Especifica o intervalo desejado de tempo de execução da aplicação que permitirá o tempo de pausa máximo especificado acima, em milissegundos. Por exemplo, se I=200ms e P=20ms, significa que a cada 200ms de execução da aplicação, o collector deverá utilizar no máximo 20ms de tempo de pausa. 10. -XX:+G1YoungGenSize=<N>. Especifica o tamanho da Young generation, em megabytes. 11. -XX:+G1ParallelRSetUpdatingEnabled -XX:+G1ParallelRSetScanningEnabled.Estes parâmetros permitem aproveitar o máximo possível de Garbage First, no entanto podem produzir uma rara situação de concorrência chamada condição de corrida (race condition) e resultar em erro. Um último detalhe é que Garbage First é muito mais verboso que os outros collectors da JVM HotSpot quando utilizando a opção -XX:+PrintGCDetails, pois pretende fornecer mais informações para troubleshooting.
  • 47. 7 – Interface Nativa Java Muito se fala do Java, principalmente do fato dele ser multiplataforma, talvez essa característica em especial, de se compilar uma vez e poder rodar em diversas plataformas, tornou o Java a linguagem e plataforma mais popular no mundo. Muito se tem explorado já que toda a complexidade é feita pela JVM, mas surge a seguinte dúvida: Como a JVM faz isso? Como ela conseguir abstrair as outras plataformas para min? Obter esse conhecimento é muito importante uma vez que pode ser necessário utilizar um recurso específico da máquina e fazer com esse recurso converse diretamente com a JVM. A comunicação entre a JVM e o código nativo quase em sua grande maioria é feito na linguagem C/C++ uma vez que elas possuem o padrão ANSI e que o mesmo é compatível com a grande maioria das plataformas, assim é possível um grande aproveitamento de código, mas em alguns casos é necessário que seja feita uma implementação específica para cada plataforma ou que existe compatibilidade com um sistema legado que foi feito em C, por exemplo. A porta entre o código nativo e o Java é conhecido como JNI (Java Native Interface). Esse recurso é muito interessante também para fazer a ponte para plataformas em que o Java ainda não atingiu. Dentro da JVM esse recurso é usado para alguns na plataforma JSE, por exemplo, Java I/O, alguns métodos da classe java.lang.System, JavaSound, a comunicação entre a classe, o arquivo .class, e a sua representação dentro da JVM, a implementação da interface java.lang.Class, as implementações do Garbage Colector dentre outros recursos. Com o JNI é possível chamar método do objeto, instanciar classes, verificar estado do objeto criado dentro da JVM, dentre outros recursos. No entanto usar o requer algumas responsabilidades, vale lembrar que usar o JNI perde a portabilidade, um erro nativo não é controlado pela JVM (Vale lembrar na parte em que se falou de registrados, a pilha nativa e o PC quando aponta para um processo nativo, não se sabe o seu valor preciso), não é possível debugar o código nativo através da plataforma Java, caso acontece um erro nativo pode quebrar a execução da JVM, ele não prover um Garbage Collector automático ou qualquer gerenciamento por parte da JVM. Assim é muito importante saber o momento em que se usará o JNI. Os objetos em Java podem ser mapeados para objetos nativos e virse-versa, para garantir a comunicação de duas mãos, assim é possível estar passando um objeto Java para o lado nativo ver o seu valor.
  • 48. Tipo em Java Tipo Nativo boolean jboolean byte jbyte char jchar double jdouble float jfloat int jint long jlong short jshort void void Tabela 2: mapeamento entre os objetos nativos e os objetos Java Com o objetivo de dar um pequeno exemplo com o JNI será mostrado dois simples exemplos, o primeiro será o “olá mundo” com o JNI e o segundo será um método estático que calcula o dobro do resultado passado, para isso é necessário que se tenha instalado o GCC eu JDK. O código será bem simples, no primeiro caso será enviado o nome por parâmetro e essa String será passada para o valor nativo, uma vez no nativo será concatenado o “Hello world” com o nome digitado, no segundo exemplo, o segundo parâmetro seria calculado o seu dobro. Primeiramente será criado a classe HelloWorld.java. public class HelloWorld { private native void chamarMensagem(String nome); public native static int dobrar(int valor); public static void main(String[] args) { String nome=args[0]==null?"nome":args[0]; int valor=args[1]==null?2:Integer.valueOf(args[1]); HelloWorld helloWorld=new HelloWorld(); helloWorld.chamarMensagem(nome); int resultado=HelloWorld.dobrar(valor); System.out.println("O dobro de "+valor+" é: "+ resultado); } static {System.loadLibrary("HelloWorld");} } Código 4: Classe HelloWorld chamando o método nativo
  • 49. Em seguida compilamos com o seguinte comando: javac HelloWorld.java Uma vez o arquivo compilado, será necessário gerar a interface JNI como seguinte comando: javah -jni HelloWorld Com o comando será gerado um arquivo HelloWorld.h /* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class HelloWorld */ #ifndef _Included_HelloWorld #define _Included_HelloWorld #ifdef __cplusplus extern "C" { #endif /* * Class: HelloWorld * Method: chamarMensagem * Signature: (Ljava/lang/String;)V */ JNIEXPORT void JNICALL Java_HelloWorld_chamarMensagem (JNIEnv *, jobject, jstring); /* * Class: HelloWorld * Method: dobro * Signature: (I)I */ JNIEXPORT jint JNICALL Java_HelloWorld_dobrar (JNIEnv *, jclass, jint); #ifdef __cplusplus } #endif #endif Código 5: interface JNI do HelloWorld Repare que na interface o método possui o seguinte formato: Java_NomeClasse_nomeMetodo. Em relação aos parâmetros o primeiro elemento, o JNIEnv, ele é um ponteiro que aponta para um vetor no qual possui todas as funções do JNI, o segundo depende se é método da classe ou da instância. Caso seja estático, ou seja o método possua a palavra-chave static, o próximo parâmetro será o jclass que conterá as informações da classe, caso seja da instância o próximo parâmetro será o jobject que conterá as informações da instância. O próximo passo é a criação do arquivo que “implemente” a interface do HelloWorld.h, assim será criado o HelloWorld.c que implemente tal interface.
  • 50. #include <jni.h> #include "HelloWorld.h" #include <stdio.h> JNIEXPORT void JNICALL Java_HelloWorld_chamarMensagem(JNIEnv * env, jobject obj,jstring nome){ const char * nomeNativo=(*env)->GetStringUTFChars(env, nome, NULL); printf("Hello World!!!! %sn", nomeNativo); return; } JNIEXPORT jint JNICALL Java_HelloWorld_dobrar(JNIEnv * env, jclass classe, jint valor){ return 2*valor; } Código 6: implementação da interface HelloWorld.h Com o arquivo criado o próximo passo é a compilação, levando em consideração as devidas importações, como se trata de libs nativas as pastas variam de acordo com a plataforma. No caso do linux para compilar será necessário o seguinte comando: gcc -o libHelloWorld.so -shared -I$JAVA_HOME/include -I$JAVA_HOME/linux HelloWorld.c Uma vez compilado o código-fonte e o transformado em uma lib, no caso do linux o arquivo com extensão .so de Shared Object. O próximo passo será “linkar” o arquivo nativo com o projeto, o primeiro passo é carregar a biblioteca dentro do código java (para isso será utilizado o comando System.loadLibrary("NomedaLib");). O próximo passo é colocar a lib nativa no classpath no sistema operacional ou definir o seu caminho pelo parâmetro java.library.path ao executar o projeto java. Nesse exemplo será utilizado a segunda opção juntamente o parâmetro que será o nome da que será impresso no console, assim o comando ficará: java -Djava.library.path=. HelloWorld Otávio 4 A saída seria: Hello World!!!! Otávio O dobro de 5 é: 10 Com isso se apresentou a o recurso do JNI, a interface que se comunica o JVM para linguagens nativa como C e C++ e da sua importância para a JVM como a implementação do Garbage Collector, sua existência em algumas APIs como o JavaSound além de se integrar com código legado e com plataformas cujo a JVM até o momento não atingiu. No entanto vale salientar que se perde o fator multiplataforma e não será mais gerenciado pela JVM usando este recurso. Aprender sobre JNI é muito importante para compreender o código da máquina virtual Java, mas é necessário um conhecimento na linguagem C e C++.
  • 51. 8 – O projeto OpenJDK O OpenJDK é um projeto que foi iniciado pela Sun Microsystems, atualmente mantido pela por várias empresas e a comunidade, para a criação de um Java Development Kit baseado totalmente em software livre e de código aberto. O projeto foi iniciado em 2006 e tem como base o HotSpot (a jvm da Sun). Uma conquista para o projeto que vale salientar é que a partir da versão 7 do Java o OpenJDK é a versão de referência, mas além dessa o uso do OpenJDK te garante algumas vantagens: 1.A primeira vantagem é que ele é open source, ou seja, pode estudar o seu código fonte. 2. Ela agora é a implementação de referência ou seja se fazer um aplicativo que rode em qualquer JVM, essa garantia será possível apenas com o OpenJDK 3.A comunidade Java é certamente uma das comunidades mais fortes do mundo. A JVM do projeto, por exemplo, está passando por constantes refatorações para melhoria de performance, atualização de bibliotecas e atualização do código sem falar que para adicionar qualquer recurso é necessário que se tenha testes. 4.A Oracle doou o código fonte do jRockit e no java 8, previsto para o final de 2013, o código seja integrado com o Hotspot. Ou seja no openjdk haverá os melhores de dois mundos em um só lugar. 5.Várias empresas fazem parte desse projeto, ou seja é uma JVM com o Know-how de várias empresas em todo o mundo. Empresas como IBM, Apple, SAP, Mac, Azul, Intel, RedHat etc. fazem parte do projeto. 6.Se a Oracle deixar o Java (Algo que eu acho muito difícil por diversos motivos) e deixar de fazer a JVM. O OpenJDK não será em nenhum momento abalado já que existem outras empresas apoiando além da comunidade. Figura 21: Integração entre o HotSpot e o JRockit gera o OpenJDK
  • 52. A diferença entre essas duas JVMs, HotSpot (a JVM mais popular da Sun atualmente da Oracle) e o OpenJDK, está na adição de códigos fechados além de pequenas mudanças na implementação para implementações fechadas para a JVM da Oracle, a dessemelhança é de cerca de 4% de código. O que acontece é que nem todos os códigos foram abertos com êxito já que alguns pertence a terceiros e são apenas licenciados na época pela Sun. Toda mudança dentro do Java é realizada através da submissão de uma JSR, Java Specification Requests, que é um documento que possui informações quanto a melhoria a ser feita e seus impactos dentro da linguagem. Essas JSRs são selecionada a partir do JCP, Java Community Process, que é composta por 31 instituições (Podemos destacar a participação da Oracle, SouJava e London Comunity). Essas instituições tem a missão de votar a favor ou contra uma JSR. Quando existe uma mudança na plataforma (JSE, JEE, JME) é dito que ela possui um guarda-chuva de especificações (Já que uma mudança de plataforma é resultado de diversas JSRs, por exemplo com o Java 7, documentada na JSR 336, possui dentro dela as JSRs 314 o projeto Coin, 203 o NIO2, 292 o invoke dynamic). Com o OpenJDK não é diferente, todas as suas mudanças precisam estar documentadas em JSRs que são votada pelo JCP, no caso de uma nova versão da plataforma JSE, precisa ter um conjunto de JSR ou um guarda-chuva de especificação. No entanto para melhorias, refatorações existe o JEP, JDK Enhancement Proposals ou propostas de melhorias para o JDK. O código do projeto é mantido em mercurial e mais informações do projeto pode ser encontrado em: http://openjdk.java.net/ O próximo passo será demonstrar como é simples compilar o projeto, para esse exemplo será utilizado o Java 8, e para isso é necessário que o mercurial, o gcc e o JDK 7 está instalado. Feito isso, realize os seguintes passos (nesse caso consideraremos que leitor usar o Linux como Sistema Operacional): hg clone http://hg.openjdk.java.net/jdk8/jdk8 jdk8 (para baixar o código fonte em sua máquina). cd jdk8 (entrando no diretório, onde se encontra o código fonte). sh get_source.sh (shell script para baixar o código fonte dos módulos da JVM). ./configure (realiza todas as configurações e solicita a instalação de dependências, caso necessite) make all CC=gcc CPP=g++ ALLOW_DOWNLOADS=true (compila todo o projeto permitindo que baixe outros módulos e dependências oriundos da internet). Ao baixar o código se verá que o projeto OpenJdk é composto por subprojetos:
  • 53. . (root) configurações comuns para compilar a OpenJDK hotspot o código fonte para construir o OpenJDK (baseado no fonte do hotspot) Nesse projeto é encontrado as implementações do GC langtools o código fonte para o compilador e utilitários para a linguagem jdk o código fonte da plataforma JSE, por exemplo, java.lang.String, java.lang.Object, etc. jaxp o projeto JAXP jaxws o projeto JAX-WS corba o projeto Corba nashorn o projeto nashorn Tabela 3: subprojetos do OpenJDK Esses subprojetos, conforme a tabela anterior, tem o seu código estruturado pensando no seu lado multiplataforma, assim suas pastas são agrupadas por Sistema operacional e o código comum para todos os SOs ficam em uma pasta “share” que são os recursos e códigos que serão utilizados por todos os SOs. Após conhecer como baixar, compilar, os subprojetos e a organização básica do projeto sinta-se à vontade para estudar o código e fazendo modificações com fins de estudos, caso tenha interesse de ajudar o projeto de alguma forma faça parte do projeto adote OpenJDK, que é um projeto internacional que possui como principal objetivo de evoluir o projeto, lá haverá mais materiais, por exemplo, para compilar em outros sistemas operacionais, realizar os testes da JVM, dentre outras informações, basta acessar o seguinte link: https://java.net/projects/adoptopenjdk
  • 54. 9 – Conclusão Muito se tem para discutir sobre JVM, além do fato da sua constante evolução. Vale salientar que esse funcionamento é baseando da JVM 7 e muitos desses recursos deixarão de existir com a evolução da plataforma, por exemplo, o metaspace no Java 8. Com o objetivo de seguir o estudo e ajudar a desenvolver a plataforma existem os grupos de usuários Java e seus projetos. Pode-se citar o Adopt o OpenJDK e também o SouJavaLivre. Criei um pequeno projeto no github com os exemplos citados nessa pequena apostila: https://github.com/otaviojava/imergindo_jvm Caso encontra alguém problema com erro de português, queria tirar dúvidas, queria conhecer mais sobre o OpenJDK, dentre outras coisas, esteja à vontade para me contatar o meu e-mail é: otaviojava@gmail.com. Espero que tenha gostado da apostila e que tenha ajudado no crescimento profissional.