Preview only show first 10 pages with watermark. For full document please download

Imergindo Na Jvm

Descrição: imersão na java virtual machine

   EMBED


Share

Transcript

Imergindo na JVM Tabela de conteúdos 1. Introduction 2. Crédito 3. Sobre o Autor 4. Falando um pouco sobre JVM 5. Se queres prever o futuro, estuda o passado i. Histórico da JVM i. JDK Alpha e Beta (1995) ii. JDK 1.1 (19 de fevereiro de 1997) iii. J2SE 1.2 (8 de dezembro de 1998) iv. J2SE 1.3 (8 de maio de 2000) v. J2SE 1.4 (6 de fevereiro de 2002) vi. J2SE 5.0 (30 de setembro de 2004) vii. Java SE 6 (11 de dezembro de 2006) viii. Java SE 7 (28 de julho de 2011) ix. Java SE 8 (18 de março de 2014) 6. Funcionamento básico da JVM 7. Registradores da JVM i. Program Counter ii. Java Stack (Java virtual machine stack) i. Stack Frame) i. Stack variables ii. Stack Operand iii. Frame Data iii. Native Method Stacks iv. Method Area v. Heap Space vi. Cache de código i. Just In Time (JIT) Compilation vii. Recapitulando 8. ByteCodes i. Carregar e salvar informações ii. Operações aritméticas iii. Conversão de valores iv. Criação e manipulação de objetos v. Instruções condicionais vi. Chamada de métodos e retorno de valores vii. Classes após compilação 9. Ciclo de vida de uma classe 10. Garbage Collector i. Implementação Serial ii. Implementação Paralelo iii. Implementação Concurrent iv. Implementação Incremental Concurrent v. Implementação Garbage First 11. Interface Nativa Java 12. O projeto OpenJDK 2 Imergindo na JVM Imergindo na JVM Certamente o Java é atualmente uma das linguagens mais usadas e uma das mais populares no mundo, sendo que o seu maior diferencial não está 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! Introduction 3 Imergindo na JVM 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. Revisão do texto Weslley Andrade Servo de Jesus, amante da arte de programar, adepto do desenvolvimento poliglota, movimento DevOps e coordenador do grupo FTD (friendstechday.com) Crédito 4 Imergindo na JVM Autor Otavio Santana Um Desenvolvedor apaixonado pelo que faz. Praticante da filosofia ágil e do desenvolvimento poliglota na Bahia, JUG Leader do JavaBahia, coordenador do SouJava além de auxiliar em diversos JUGs ao redor do mundo, um dos fomentadores do grupo LinguÁgil. Leva a sério o termo “Make the future Java” presente como membro do Java Expert Group em diversas especificações Java nas plataformas SE, ME e EE, principalmente na SE em que contribui diretamente para o projeto OpenJDK, além de ser membro atuante do JCP, inclusive ganhando um outstanding member award e Java Champion pelos seus feitos. Presente nos maiores eventos Java e desenvolvimento de software do mundo. Contribuiu para diversos projetos Open Source também é membro da OSI, Open Source Instituite, desenvolve e realiza manutenções nos principais projetos Java da Apache Foundation na qual atualmente é commiter. Quando sobra tempo, escreve artigos, livros e ajudar revisão técnica de diversos materiais no mundo Java. Sobre o Autor 5 Imergindo na JVM Falando um pouco sobre JVM Certamente o Java é atualmente uma das linguagens mais usadas e populares no mundo, sendo que o seu maior diferencial não está 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, ajudar a comunidade brasileira a conhecer a JVM. 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 muitas 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. Saberá quais registradores serão compartilhados por todas as Threads e aqueles que não serão, 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 é 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. Falando um pouco sobre JVM 6 Imergindo na JVM Se queres prever o futuro, estuda o passado JVM, java virtual machine ou máquina virtual java, tem sua história inciada em 1992. 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++, é 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 Sistemas Operacionais 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 e representa uma classe ou interface em java. Do seu início até a presente data o Java teve diversas versões. Essas modificações são gerenciadas pelo JCP ou 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. A documentação da linguagem fica no JSL (Java Language Specification) e a documentação da JVM fica no Java Virtual Machine Specification. Se queres prever o futuro, estuda o passado 7 Imergindo na JVM 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 sofrendo constante evolução com ajuda de empresas e da comunidade. Histórico da JVM 8 Imergindo na JVM JDK Alpha e 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. JDK Alpha e Beta (1995) 9 Imergindo na JVM JDK 1.1 (19 de fevereiro de 1997) Grandes melhorias e refatorações nos modelos de evento do AWT Inner class adicionado a linguagem JavaBeans JDBC RMI Reflection que suportava apenas introspecção, nenhuma modificação em tempo real era possível. JDK 1.1 (19 de fevereiro de 1997) 10 Imergindo na JVM 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: A 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 J2SE 1.2 (8 de dezembro de 1998) 11 Imergindo na JVM 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 J2SE 1.3 (8 de maio de 2000) 12 Imergindo na JVM 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: A palavra-chave assert(JSR 41) Expressões regulares Encadeamento de exceção permite uma exceção de maior nível encapsule uma exceção de menor nível. 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 Novas 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) J2SE 1.4 (6 de fevereiro de 2002) 13 Imergindo na JVM 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 significantes 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 para analisar dados de input streams e buffers J2SE 5.0 (30 de setembro de 2004) 14 Imergindo na JVM 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. Essa versão 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. Java SE 6 (11 de dezembro de 2006) 15 Imergindo na JVM 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 Java SE 7 (28 de julho de 2011) 16 Imergindo na JVM Java SE 8 (18 de março de 2014) O Java 8 foi entregue no dia 18 de março de 2014 e nessa versão foi incluída alguns recursos que antes estavam planejados para o Java 7. Os recursos submetidos para essa versão foram baseadas em propostas de melhorias do JDK, os JEPS. Dentre essas melhorias podemos destacar: JSR 223, JEP 174: Projeto Nashorn, um executor runtime de JavaScript, permitindo a execução de código JavaScript dentro da aplicação. Esse projeto teve como objetivo ser o sucessor do Rhino, usando Invoke Dynamic em vez de reflection . JSR 335, JEP 126: Suporte para expressões lambdas JSR 310, JEP 150: Date and Time API JEP 122: Remoção do Permanent generation Java SE 8 (18 de março de 2014) 17 Imergindo na JVM Funcionamento básico da JVM Este capítulo falará um pouco sobre o funcionamento básico da JVM, que é o coração da linguagem java. Esta é responsável pela independência entre as plataformas e roda basicamente dois tipos de processos: Os escrito em java que são gerados bytecodes Os nativos que são escritas em linguagens como o C\C++ e linkadas dinamicamente para uma plataforma específica. Os métodos nativos são muito interessantes para obter informações do SO onde a JVM está em execução, além de utilizar recursos deste. E é em função disso que apesar de a linguagem ser RunAnyWhere a JVM não é, ou seja, para cada plataforma existe uma máquina virtual específica. Isso acontece, por exemplo, para usar recursos específicos da plataforma onde, por exemplo, existem chamadas distintas para trabalhar com diretório e arquivos. O único e principal motivo da JVM é rodar o aplicativo. Quando se inicia 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 iniciadas 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 . Funcionamento básico da JVM 18 Imergindo na JVM 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 que é responsável por executar as atividades do coletor de lixo da JVM. Compiladores que são responsáveis por transformar bytecode 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 executem 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 o Java quanto para a parte nativa, são entregues para a JVM. Como na linguagem, a JVM opera em dois tipos de dados: 1. Os primitivos 2. Os valores de referência. A JVM 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 e o seu funcionamento é semelhante ao de linguagens como C/C++. Os tipos primitivos existentes na JVM são: Numéricos Booleano 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 nulo ponteiro returnAddress Funcionamento básico da JVM 19 Imergindo na JVM Os formatos de ponto flutuante são o float , com precisão simples, e o double , com dupla precisão. Tantos os valores como as operações seguem o especificado no padrão IEEE para aritmética de ponto flutuante binário (ANSI/ IEEE. 7541985, 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 é usado apenas 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: 1. classes 2. array 3. interfaces O Valor de referência é iniciado como null , 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 possuem os seus subtipos: classe, interface e array. Os primitivos possuem returnAdress, booleano, flutuantes (float e double de simples e dupla precisão respectivamente), inteiros (short, byte, int, long, char). Funcionamento básico da JVM 20 Imergindo na JVM 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 ciência 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. 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. Registradores da JVM 21 Imergindo na JVM Program Counter O registrador PC, Program counter, é criado tão logo uma Thread é criada, ou seja, cada Thread possui o seu. Ele pode armazenar dois tipos de dados: 1. Ponteiros nativos 2. returnAdress Esses dados possuem informações quanto a instrução que está sendo executada pela Thread . Se o método executado for nativo o PC será um ponteiro e não tem o seu valor definido, do contrário, ele terá o endereço de instrução, o returnAdress. Stack Frame) 22 Imergindo na JVM Java Stack (Java virtual machine stack) Assim como o PC, ele é 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 fixo ou determinado dinamicamente. Java Stack (Java virtual machine stack) 23 Imergindo na JVM Stack variables 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. Java Stack (Java virtual machine stack) 24 Imergindo na JVM Stack Operand 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. 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 ). 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 ) Java Stack (Java virtual machine stack) 25 Imergindo na JVM Frame Data Esse pequeno registrador possui o link da constant pool da classe que o possui o corrente método que está sendo executado. Java Stack (Java virtual machine stack) 26 Imergindo na JVM Native Method Stacks Possui finalidade de 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. Native Method Stacks 27 Imergindo na JVM Method Area Esse registrador tem a finalidade de armazenar logicamente o stream da classe, essa área é compartilhada entre todas as Threads . Logicamente faz parte do Heap espace. Por ser parte do Heap ele possui o recolhimento de memória automático, Garbage Collector. As classes 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, que contém 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). Method Area 28 Imergindo na JVM 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. 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. A representação dos vetores se comporta de forma semelhante as variáveis de referência, mas eles ganham dois campos a mais: 1. O tamanho, que define o tamanho do vetor 2. Uma lista de referência que apontam para os objetos que estão dentro desse vetor. Heap Space 29 Imergindo na JVM Heap Space 30 Imergindo na JVM 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 . Cache de código 31 Imergindo na JVM Just In Time (JIT) Compilation O bytecode 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 cache 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 decisão vantajosa. Just In Time (JIT) Compilation 32 Imergindo na JVM Recapitulando 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 uma por Thread . O Heap e o method Area são compartilhadas entre a JVM, todas as Threads , e tem a responsabilidade de armazenar a instância dos objetos e o streams e informações da classe respectivamente. Recapitulando 33 Imergindo na JVM 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 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. ByteCodes 34 Imergindo na JVM 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. Carregar e salvar informações 35 Imergindo na JVM 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 comportamentos 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 . Operações aritméticas 36 Imergindo na JVM Conversão de valores As instruções de conversão de valores serve para modificar o tipo da variável, essa variável pode ter seu tipo ampliado como: Promoção: int para long , int para float , int para double . long para float e float para double ( i2l , i2f , i2d , l2f , l2d , e f2d ) esse ampliamento perde não perde a precisão do valor original. O encurtamento do tipo como: int para byte , int para short , int para char , long para int , long para float para int ou long para, double para int , double para long ou double para float ( i2b , i2c , i2s , l2i , f2i , f2l , d2i , d2l , e d2f ) vale lembrar que tais modificações existe a possibilidade de perder precisão ou estouro do valor. Conversão de valores 37 Imergindo na JVM Criação e manipulação de objetos: Instruções para a criação e manipulação de instâncias ( new ) Intruções para criação de 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 ) Salvar na pilha ( 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 ). Criação e manipulação de objetos 38 Imergindo na JVM Instruções condicionais Instruções que retornam valores boolianos ( 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`). Instruções condicionais 39 Imergindo na JVM Chamada de métodos e retorno de valores As chamadas de um método são: invokevirtual: Chama um método de uma instância invokeinterface: Chama um método de uma interface invokespecial: Chamada de um método privado ou da superclasse invokestatic: Realiza a chamada de um método estático invokedynamic: Método que constrói um objeto O retorno de uma instrução pode ser definido ( ireturn , lreturn , freturn , dreturn e areturn ). Durante a execução do método caso seja interrompida de maneira inesperada com uma exceção a chamada athrow é realizada. Os métodos síncronos são possíveis graças à presença de um simples 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). Chamada de métodos e retorno de valores 40 Imergindo na JVM Classes após compilaçã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: Um número mágico em hexadecimal definindo que essa clase é um .class o valor é 0xCAFEBABE 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 /* 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 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 Interface Nativa Java 57 Imergindo na JVM 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 #include "HelloWorld.h" #include JNIEXPORT void JNICALL Java_HelloWorld_chamarMensagem(JNIEnv * env, jobject obj,jstring nome){ const char * nomeNativo=(*env)->GetStringUTFChars(env, nome, NULL); printf("Hello World!!!! %s\n", nomeNativo); return; } JNIEXPORT jint JNICALL Java_HelloWorld_dobrar(JNIEnv * env, jclass classe, jint valor){ return 2*valor; } 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 será: Hello World!!!! Otávio `O dobro de 5 é: 10 Com isso se apresentou 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++ .` Interface Nativa Java 58 Imergindo na JVM 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. 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 selecionadas 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 têm 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, O projeto OpenJDK 59 Imergindo na JVM 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 votadas 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/ Para baixar o código é necessário: 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). Ao baixar o código se verá que o projeto OpenJDK é composto por subprojetos: O projeto OpenJDK 60