• Nenhum resultado encontrado

Imergindo Na Jvm

N/A
N/A
Protected

Academic year: 2021

Share "Imergindo Na Jvm"

Copied!
60
0
0

Texto

(1)
(2)

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

Tabela de conteúdos

2

(3)

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!

Imergindo na JVM

3 Introduction

(4)

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. Weslley Andrade Servo de Jesus, amante da arte de programar, adepto do desenvolvimento poliglota, movimento DevOps e coordenador do grupo FTD (friendstechday.com)

Arte da capa do Ebook

Revisão do texto

4 Crédito

(5)

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.

Autor

Otavio Santana

5 Sobre o Autor

(6)

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 Falando um pouco sobre JVM

(7)

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 Se queres prever o futuro, estuda o passado

(8)

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 Histórico da JVM

(9)

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 JDK Alpha e Beta (1995)

(10)

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 JDK 1.1 (19 de fevereiro de 1997)

(11)

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 J2SE 1.2 (8 de dezembro de 1998)

(12)

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 J2SE 1.3 (8 de maio de 2000)

(13)

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 J2SE 1.4 (6 de fevereiro de 2002)

(14)

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 J2SE 5.0 (30 de setembro de 2004)

(15)

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 Java SE 6 (11 de dezembro de 2006)

(16)

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 Java SE 7 (28 de julho de 2011)

(17)

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 Java SE 8 (18 de março de 2014)

(18)

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 Funcionamento básico da JVM

(19)

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

returnAddress nulo ponteiro

19 Funcionamento básico da JVM

(20)

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. 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 é 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). 20 Funcionamento básico da JVM

(21)

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 Registradores da JVM

(22)

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.

Program Counter

22 Stack Frame)

(23)

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 Java Stack (Java virtual machine stack)

(24)

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. Stack variables 24 Java Stack (Java virtual machine stack)

(25)

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 )

Stack Operand

25 Java Stack (Java virtual machine stack)

(26)

Esse pequeno registrador possui o link da constant pool da classe que o possui o corrente método que está sendo executado.

Frame Data

26 Java Stack (Java virtual machine stack)

(27)

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 Native Method Stacks

(28)

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 Method Area

(29)

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 Heap Space

(30)

30 Heap Space

(31)

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 Cache de código

(32)

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 Just In Time (JIT) Compilation

(33)

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 Recapitulando

(34)

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 ByteCodes

(35)

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 Carregar e salvar informações

(36)

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 Operações aritméticas

(37)

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 Conversão de valores

(38)

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 Criação e manipulação de objetos

(39)

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 Instruções condicionais

(40)

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 Chamada de métodos e retorno de valores

(41)

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<V< Maior Versão. 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, atributos 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” . this_class: o valor da corrente classe deve ter um índice válido na constant pool 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. 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

Classes após compilação

41 Classes após compilação

(42)

[[ 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 bytescodes, 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);

}

42 Classes após compilação

(43)

public static int somarStatic(int a, int b) { return a + b; } } Criado o arquivo PrimeiroTeste.java e inserido o código 1 nesse arquivo, os 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 O resultado: minor version: 0 major version: 51 flags: ACC_PUBLIC, ACC_SUPER Constant pool: #1 = Methodref #5.#21 // java/lang/Object."":()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 #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 // "":()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; 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."":()V 4: return LineNumberTable: line 1: 0 43 Classes após compilação

(44)

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 44 Classes após compilação

(45)

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 Ao 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 debugar o código num determinado método, LineNumberTable 5: 0, diz que a linha

de número cinco do código java equivale a instrução do começo, linha zero do bytecode 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 boolianos, 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 disso 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. 45 Classes após compilação

(46)

Toda classe pela JVM possui o seu ciclo de vida, e começa quando ela nasce dentro da JVM, esse processo é feito de modo lazy, 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 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 classe estenda de uma classe 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.

Ciclo de vida de uma classe

46 Ciclo de vida de uma classe

(47)

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 gera a interface que representa tal arquivo, o java.lang.Class .

A interface Class é o produto do processo de carregar a classe para a memória principal, é a representação do arquivo,

com isso ele contém as informações do mesmo, como lista dos métodos, atributos, 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).

47 Ciclo de vida de uma classe

(48)

Na JVM existem múltiplos 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 carregar 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. 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 com a chamada do método construtor, sendo que antes é chamado o construtor da superclasse, não existe verificação para as interfaces apenas se os métodos foram implementados. 48 Ciclo de vida de uma classe

(49)

Diferente de algumas linguagens o Java possui um gerenciamento de memória automática, isso permite que a memória 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. O mais conhecido certamente é o Mark and Sweep, 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 chamá-lo 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 algumas partes: Young Generation: É onde contém os objetos recém-criados, a grande maioria dos objetos morrem dentro dessa área. Ela é subdivida em duas partes: Eden (local aonde o objetos nascem) e Survivers(N) locais aonde os objetos vão passando até sair da Young Generation. O seu funcionamento é de maneira simples: Os objetos nascem no Eden, depois de um tempo, os objetos são copiados “vivos” para os Survivers, 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 vão 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 dos dados.

Garbage Collector

49 Garbage Collector

(50)

Comentado um pouco sobre os processos de minor collector (procedimento de generation que cópia objetos para registradores sobreviventes) 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. 50 Garbage Collector

(51)

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 , desse modo pode economizar recursos, porém caso haja um grande número de memória haverá um grande delay.

Implementação Serial

51 Implementação Serial

(52)

Trabalha de forma semelhante da serial, no entanto, será utilizado duas ou mais Threads por coleção, assim o processo

tende a ser realizar em um tempo menor, porém utilizando mais recursos de máquina.

Implementação Paralelo

52 Implementação Paralelo

(53)

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

Implementação Concurrent

53 Implementação Concurrent

(54)

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

Implementação Incremental Concurrent

54 Implementação Incremental Concurrent

(55)

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 fase em que realiza a marcação concorrente, e realiza o 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.

Implementação Garbage First

55 Implementação Garbage First

(56)

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

Interface Nativa Java

56 Interface Nativa Java

(57)

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");} } 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 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

57 Interface Nativa Java

(58)

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!!!! %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++ .` 58 Interface Nativa Java

(59)

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 O projeto OpenJDK

(60)

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: 60 O projeto OpenJDK

Referências

Documentos relacionados

Chora Peito Chora Joao Bosco e Vinicius 000 / 001.. Chão De Giz Camila e

O objetivo, tal como visto anteriormente, era traçar um modelo de quadro descritivo para a emissão da ‘Opinião Desfavorável’ em português do Brasil que pudesse servir de suporte

Possíveis danos para a saúde: Longo prazo - efeitos sistémicos Utilização final: Consumidores. Vias de

´e aquele pelo qual a filosofia alem˜a traduziu, depois de Kant, o latim existentia, mas Heidegger deu-lhe um sentido muito particu- lar, j´a que designa na sua filosofia

Este trabalho buscou, através de pesquisa de campo, estudar o efeito de diferentes alternativas de adubações de cobertura, quanto ao tipo de adubo e época de

A prova do ENADE/2011, aplicada aos estudantes da Área de Tecnologia em Redes de Computadores, com duração total de 4 horas, apresentou questões discursivas e de múltipla

O enfermeiro, como integrante da equipe multidisciplinar em saúde, possui respaldo ético legal e técnico cientifico para atuar junto ao paciente portador de feridas, da avaliação

Visando a este cenário, o Ministério da Saúde criou o Programa de Educação pelo Trabalho para a Saúde (PET-Saúde), regulamentado pela Portaria Interministerial