Arquitetura da M´aquina Virtual Java 1. Principais subsistemas

Propaganda
Arquitetura da Máquina Virtual Javaa
1. Principais subsistemas máquina virtual Java (JVM):
Carregador de classes (“class loader”): carrega classes e
interfaces a partir de nomes completamente qualificados.
Máquina de execução (“execution engine”): executa instruções
das classes carregadas.
Áreas de dados de execução (“runtime data areas”): organizada
em área de métodos (“method area”), área de memória dinâmica
(“heap”), pilhas (“stacks”), contadores de programa (“program
counters” ou pc) e pilhas dos métodos nativos (“native methods
stacks”). A especificação destas áreas varia de implementação
para implementação para permitir que caracterı́sticas especı́ficas
de uma dada arquitetura possam ser exploradas. Cada instância da
máquina virtual tem uma área de métodos e uma “heap” que são
a Baseado
no Capı́tulo 5 de ”Inside the Virtual Machine”, por Bill Vernners.
1
compartilhadas por todas as “threads” sendo executadas na
máquina virtual. Cada “thread” possui um contador e uma pilha.
Cada invocação de um método numa “thread” cria um registro de
ativação (“frame”) na pilha da “thread” contendo o estado do
método, o que inclui parâmetros, variáveis locais, valor de retorno
e cálculos intermediários.
Interface de métodos nativos (“native method interface”).
2. A JVM é uma máquina de pilha. As instruções da JVM utilizam a
pilha para armazenar resultados intermediários, ao invés de utilizar
registradores como é feito em arquiteturas concretas. Isto permite a
definição de um conjunto simples de instruções que é facilmente
implementado em diferentes arquiteturas.
3. Na JVM existem tipos primitivos, como byte, short, int, long,
float e double e referências a objetos. O tamanho de uma
palavra (“word”) na JVM varia de implementação para
2
implementação da JVM e deve ser grande o suficiente para
armazenar um byte, short, int, float, uma referência a um
objeto ou um “return address”, este último utilizado para
implementar cláusulas finally em Java. Duas palavras devem ser
capazes de armazenar os tipos long e double.
3
Class Loader
1. Responsável por carregar, ligar (“link”) e inicializar variáveis.
Devem verificar a integridade de um “class file” antes de carregá-lo,
podendo inclusive reconhecer outros tipos de arquivo além do “class
file”. Ao carregar uma classe, uma instância da classe
java.lang.Class é criada passando a habitar a heap. Habitam a
heap também, assim como todos os objetos, instâncias de
“User-defined class loaders” e instâncias de java.lang.Class.
Dados para tipos carregados ficam na área de métodos.
2. Pode ser de dois tipos: “Bootstrap Class Loader”, parte da JVM
responsável por carregar classes da API de Java, e “User-defined
class loaders”, implementados por uma aplicação.
3. Um “User-defined class loaders” pode basicamente invocar o
“Bootstrap Class Loader”, através do método findSystemClass,
carregar uma classe a partir de seu class file (defineClass) e
4
ligá-la (resolveClass). Um “User-defined class loader” pode ser
utilizado por exemplo para construir um interpretador que primeiro
gera o “bytecode” de um programa, isto a representação class file de
um programa e depois cria uma classe associada a representação class
file, podendo também escrevê-la para disco tornando-a persistente.
4. Cada class loader define um espaço de nomes (“namespace”). Com
isto um determinado tipo pode ser carregado duas vezes ou mais
vezes numa mesma instância da máquina virtual estando obviamente
em namespaces diferentes.
5
Method Area
1. A method area é onde o código associado a um tipo fica armazenado
após ser carregado pela máquina virtual.
2. A method area é compartilhada por todas as threads sendo executadas
pela máquina virtual, portanto o carregamento de um tipo pelo class
loader dever “thread safe”, isto é, se duas threads desejam acessar
uma determinada classe, uma delas deve ficar responsável pelo
carregamento enquanto a outra aguarda.
3. As os atributos e métodos estáticos (declarados com o modificador
static) também ficam na method area.
4. O tamanho da method area não é fixado pela especificação da JVM,
podendo inclusive utilizar a própria heap da JVM. A method area
pode também ser considerada pelo coletor de lixo (“garbage
collector”). Como classes podem ser carregadas dinamicamente
eventualmente uma classe pode deixar de ser referenciada podendo
6
então ser liberada pelo garbage collector.
5. As seguintes informações são armazenadas para cada tipo carregado
pela JVM:
O nome completo (“fully qualified name”) do tipo. (Um nome
completo inclui os nomes dos pacotes onde o tipo está declarado,
separados por pontos, como por exemplo
java.lang.Object. No class file este formato é um pouco
diferente: os pontos são substituidos por barras. O exemplo fica
então java/lang/Object.)
O nome completo da superclasse do tipo.
A informação de que o tipo é uma classe ou interface.
Os modificadores do tipo. Uma combinação de public,
abstract ou final.
Os nomes completos das superinterfaces que o tipo implementa.
O conjunto de constantes (“constant pool”). Este ı́tem tem um
7
papel central na ligação dinâmica de tipos. O constant pool é um
conjunto ordenado de constantes literais declaradas pelo tipo,
como strings e inteiros, referências simbólicas para tipos, campos
e métodos.
Informações sobre os campos (“fields”).
Informações sobre os métodos.
As variáveis estáticas.
Uma referência para a classe ClassLoader.
Uma referência para a classe Class.
Tabela de métodos. Implementações da JVM ficam livres para
adicionar outras estruturas que acelerem o processo a method
area.
6. A classe Class permite acesso por uma aplicação Java a method
area através de seus métodos. Dada uma instância da classe Class
(que representa um tipo, os seguintes métodos, dentre outros, podem
8
ser invocados: (i) getName que retorna o nome completo do tipo de
uma classe; (ii) getSuperClass, que retorna uma referência para
a instância de Class que representa a superclasse do tipo, (iii)
isInterface que indica se o tipo é uma interface ou não, (iv)
getInterfaces que retorna as interfaces, instâncias da classe
Class, implementadas pelo tipo e (v) getClassLoader que
retorna uma referência para o class loader que carregou o tipo. Uma
referência a um instância da classe Class pode ser obtida através
dos métodos forName, que recebe o nome completo de um tipo ou
getClass, que retorna a instância da classe Class do objeto que
executou getClass.
9
7. Exemplo de uso da method area: Considere o seguinte trecho de
código java.
class Lava {
private int speed = 5 ;
void flow() {}
}
class Volcano {
public static void main(String args[]) {
Lava lava = new Lava() ;
lava.flow() ;
}
}
10
A execução deste código pode ser a seguinte:
(a) O nome “Volcano” é dado a uma instância da JVM, por exemplo
chamando java Volcano com Volcano.java tendo sido
compilado produzindo Volcano.class. Outras formas dependente
de plataforma podem ser utilizadas. Lembre-se que Java foi idealizado
para “rodar em qualquer lugar”.
(b) A instância da JVM identifica e carrega Volcano.class extraindo
a definição da classe Volcano do class file e armazenando-a na
method area. O método main é invocado interpretando seus
bytecodes armazenados na method area, mantendo uma referência a
constant pool da classe Volcano, que é a classe corrente.
(c) A primeira instrução da função main manda que a JVM aloque
espaço para a classe listada no primeiro ı́ndice da constant pool.
Através da referência simbólica existente no pool a JVM verifica se a
classe está presente na method area e em caso negativo faz uso do seu
nome completo "Lava" presente na constant pool e carrega o class
11
file Lava.class colocando as informações do class file na method
area.
(d) A string "Lava" na constant pool de Volcano é substituı́da por
uma referência para a área de dados de Lava. (Note a necessidade de
alta-performance para o processo de carregamento de um tipo.)
(e) Utilizando esta referência a JVM pode então alocar espaço para uma
instância de Lava. Seus atributos, assim como aqueles herdados, são
então inicializados para seus valores default.
(f) Uma referência ao objeto Lava é então empilhado e a variável
speed inicializada para . Finalmente o método flow é invocado.
12
Heap
1. Objetos e vetores são alocados dinamicamente na heap, quando da
execução de uma aplicação Java. A heap é compartilhada numa
JVM, ou seja, diferentes threads numa mesma aplicação devem então
gerenciar a sincronização no acesso a objetos por estes serem
alocados na heap.
2. A heap é gerenciada por um garbage colector sendo então
desnecessária a desalocação explı́cita de objetos da heap. O garbage
colector gerencia também a fragmentação da heap. É interessante
notar que a especificação da JVM não impõe o uso de uma polı́tica de
coleção de lixo particular nem mesmo a implementação de um
coletor de lixo: só fica especificado que não existe uma desalocação
explı́cita de memória e que a JVM deve então resolver isso de alguma
maneira, podendo simplesmente dizer que a memória acabou.
3. A representação dos objetos na heap também não fica definida pela
13
JVM: devem no entanto conter as variáveis de instância e uma
referência a method area para acesso as informações estáticas do tipo
que ficam armazenadas naquela área assim como permitir a consulta
ao tipo para uma coerção de tipos (“typecasting”), execução do
comando instance of e para resolução do binding dinâmico: a
escolha do método a ser executado depende não da instância mas do
seu tipo.
4. Esquemas de memória para a heap devem levar em consideração:
Como é o acesso as informações do tipo a partir de uma instância.
Uso ou não de tabela de métodos para agilizar a chamada de
métodos. (Similar as tabelas virtuais em C++). Agilizam o acesso
aos métodos porém implicam no uso de mais memória.
“Lock” do objeto para no acesso multi-threaded.
“Wait set” do objeto, representando um conjunto de threads que
esperam por acesso a um objeto.
14
Informações necessárias ao garbage collector.
5. O tamanho de um vetor (“array”) não influencia no seu tipo, isto é,
um vetor de inteiros de tamanho tem o mesmo tipo de um vetor de
. A informação sobre o tamanho do vetor fica
tamanho
armazenada internamente na instância, devendo fazer parte então da
estrutura de representação do objeto. É importante enfatizar a
convenção de nomes nestes casos: um vetor de inteiros tem nome [I
enquanto que uma matriz bi-dimensional de booleanos tem nome
[[B.
15
Java Stack e Stack Frame
1. Uma pilha de frames é criada para cada thread de uma aplicação
Java. Cada vez que um método é invocado um novo frame é
empilhado, contendo as variáveis locais ao método, a pilha de
operandos e os dados do frame. Por isso não é necessário se
preocupar com acessos multi-threaded sobre dados na pilha, por
serem privados a thread proprietária daquela pilha.
2. Quando uma aplicação Java invoca um método, a JVM verifica
através do tipo quantas palavras serão necessárias apara alocar as
variáveis locais e a pilha de instruções do método, criando então um
frame do tamanho apropriado empilhando-o na pilha da thread que
criou invocou o método.
3. A área de variáveis locais de um frame é um vetor cujo primeiro
ı́ndice é e guarda os parâmetros atuais da chamada do método assim
como as variáveis locais ao método. Valores dos tipos int, float,
16
reference e returnAddress ocupam uma entrada enquanto
long e double ocupam duas. Os tipos byte, short, boolean e
char são convertidos para int antes de serem armazenados.
4. A área de variáveis locais num frame de um método de instância tem
na sua posição uma referência para o objeto, na heap, que invocou
aquele método. Objetos em Java são sempre passados por referência.
5. Tamanhos das áreas no frame e ordens de alocação de variáveis na
pilha, como possı́veis otimizações no uso das variáveis são deixadas
em aberto pela especificação da JVM.
6. A JVM é uma máquina de pilha, e não uma máquina de registradores
como na maioria das arquiteturas de hardware, a menos do program
counter, pois os operandos das suas instruções são retirados da pilha
de operandos contida em um frame. O exemplo a seguir soma os
valores em duas variáveis locais e coloca o numa terceira variável:
17
iload_0
iload_1
iadd
istore_2
//
//
//
//
//
//
//
//
Empilha o inteiro localizado na
variável local indexada por 0.
Empilha o inteiro localizado na
variável local indexada por 1.
Desempilha os dois inteiros e empilha
a soma.
Armazena o resultado na
variável local indexada por 2.
7. A área de dados do frame existe inclui informação para: (i) a
resolução de nomes da constant pool, (ii) retorno normal de um
método, (iii) término anormal de um método por sinalização de
exceções.
8. Algumas instruções da máquina virtual utilizam a constant pool para
buscar seus operandos. O acesso é feito então a partir da referência a
constant pool existente na área de dados do frame.
9. Quando um método termina normalmente, a JVM precisa restaurar o
18
frame do método chamador como frame corrente, remover o frame
do método que terminou, empilhar o retorno do método que concluiu
na pilha de operandos do frame do método chamador e acertar o
registrador program counter.
10. Quando um método termina anormalmente, a JVM precisa consultar
uma tabela de exceções que contém as seguintes informações: (i)
área protegida por um catch, um ı́ndice no constant pool que
identifica a classe da exceção sendo tratada e (iii) o inı́cio do código
do tratador. Se um catch apropriado não é encontrado, o método
termina abruptamente.
19
Class File: Sintaxe para Descritoresa
1. Um class file é a entradada para uma máquina virtual Java. É uma
descrição binária para a estrutura da method area.
2. Neste curso iremos utilizar a linguagem assembly Jasmin
(http://jasmin.sourceforge.net/) como saı́da para o
nosso compilador. Poderı́amos no entanto implementar nosso
compilador para que ele gerasse class files diretamente.
3. Apesar de gerarmos assembly, recomenda-se a leitura do Capı́tulo 4
da especificação da máquina virtual que fala sobre o formato do class
file.
4. Antes de falarmos sobre Jasmin, precisamos entender como
descritores para campos e métodos são representados na JVM.
a Baseado
no Capı́tulo 4 de Java Virtual Machine Specification
20
Descritores de Campos
1. Podem representar o tipo de uma classe, intância ou variável local.
Tem a seguinte gramática:
FieldDescriptor: FieldType
ComponentType: FieldType
FieldType: BaseType | ObjectType | ArrayType
BaseType: B | C | D | F | I | J | S | Z
ObjectType: L <classname> ;
ArrayType: [ ComponentType
2. Os caracteres de BaseType, o L e ; de ObjectType, e [ de
21
ArrayType são todos caracteres ASCII. A string <classname>
representa um nome completo de uma classe ou interface.
3. A interpretação dos tipos de campos são mostrados na tabela a seguir:
22
Caracter BaseType
Tipo
Interpretação
B
byte
byte com sinal
C
char
Caracter Unicode
D
double
valor float-point de dupla precisão
F
float
valor float-point de precisão simples
I
int
inteiro
J
long
inteiro longo
L<classname>;
reference
instância da classe <classname>
S
short
short com sinal
Z
boolean
true ou false
[
reference
uma dimensão de um array
4. Exemplos:
Variável de instância do tipo int: I.
23
Variável de instância do tipo Object:
Ljava/lang/Object;.
Note que é utilizada a forma interna para nome completo para a
classe Object.
Variável de instância que é um vetor multidimensional do tipo
double, declarada em Java como double d[][][]; é: [[[D.
24
Descritores de método
1. Um descritor de método representa os parâmetros que os método
recebe e o valor que ele retorna:
MethodDescriptor:
( ParameterDescriptor* ) ReturnDescriptor
2. Um descritor de parâmetro representa um parâmetro passado ao
método:
ParameterDescriptor: FieldType
3. O descritor do valor de retorno representa o tipo do valor retornado
por um método, com a seguinte gramática:
ReturnDescriptor: FieldType | V
O caracter V indica que o método não retorna valor, ou seja o tipo de
retorno é void.
4. O em comprimento da lista de parâmetros é calculado pela soma do
25
comprimento dos tipos dos seus parâmtros da seguinte maneira: tipos
long ou double medem duas unidades de comprimento e qualquer
outro tipo mede uma unidade.
Um descritor de método é válido se o comprimento da sua lista de
.
parâmetros é
5. O descritor do método Object mymethod(int i, double
d, Thread t) é:
(IDLjava/lang/Thread;)Ljava/lang/Object;
Note que são utilizados os nomes completos para as classes Thread
e Object.
6. Um descritor de método para mymethod é o mesmo tanto quando
for um método de classe quanto quando for um método de instância.
O fato de que uma referência para this ser passada para um método
de instância, (e não ser passada no caso de um método de classe) não
fica refletido no descritor do método. A referência para this é
26
passada implicitamente pela instrução da JVM que invoca métodos
de instância.
27
Download