• Share
  • Email
  • Embed
  • Like
  • Save
  • Private Content
Imergindo na JVM
 

Imergindo na JVM

on

  • 3,707 views

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 ...

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). Conheça um pouco mais sobre esse motor, seu funcionamento e sua arquitetura para tirar melhor proveito dela em suas aplicações, além de conhecer um pouco sobre a implementação de referência e open source da JVM, o OpenJDK. O conteúdo desse E-book falará sobre:
* os registradores da JVM
* A interface do Java com código nativo, JNI, presente em diversos pontos do JVM, dentre eles, o NIO e Gargabe Collector,
* O funcionamento básico do Garbage Collector
* Como compilar o OpenJDK
* ByteCode e o seu funcionamento
* E muito mais!

Statistics

Views

Total Views
3,707
Views on SlideShare
3,612
Embed Views
95

Actions

Likes
17
Downloads
272
Comments
0

4 Embeds 95

http://www.pognao.com.br 87
https://twitter.com 6
http://cloud.feedly.com 1
http://www.steampdf.com 1

Accessibility

Categories

Upload Details

Uploaded via as Adobe PDF

Usage Rights

CC Attribution-NonCommercial LicenseCC Attribution-NonCommercial License

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

    Imergindo na JVM Imergindo na JVM Document Transcript

    • 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.