Marcos Roriz $:

Pessoal, Geek, Anime, Jogos, Ciência da Computação e Open Source…

ClassLoader desmitificado.

with 3 comments

Até recentemente não dava muita importância no funcionamento da estrutura interna da plataforma Java até quando tive problemas que o Google não conhece. Para resolver meus problemas tive que mergulhar a fundo no interior da JVM. E apesar de ter vários materiais na rede é complicado compreender o linguajar e o funcionamento interno da mesma. Para começar entender o funcionamento do seu programa dentro da Máquina Virtual Java (Java Virtual Machine – JVM) é necessário compreender o coração da JVM  o ClassLoader (CL). De maneira simples, um CL é uma estrutura que tem como responsabilidade o carregamento dos seus bytecodes (.class). Cada ClassLoader contém o que ela pode carregar no seu classpath específico, isso é, o endereço (diretórios) das classes. Um classpath pode conter vários diretórios, sendo assim o CL irá procurar a classe no primeiro diretório especificado, se não achar irá pro segundo, assim suscetivamente até acabar a lista de diretórios do classpath. Porém o mais importante é entender como o CL carrega as suas classes e como ele é organizado internamente.

Antes de mais nada é necessário explicar que um tipo (classe) em java é identificado pelo seu nome e seu CL. Ou seja um objeto da classe Folha de CL 0x2bbd86 é diferente de um objeto da classe Folha de CL 0x813b2 pois apesar de serem da mesma classe esses possuem CL diferentes. O java faz isso para garantir a consistência de objetos internamente na JVM. Pois caso contrário poderia ocorrer um conflito entre um objeto Folha que representa uma árvore e sua atribuição a um objeto Folha que representa uma folha de pagamento. Ou seja, objetos originados de uma classe de mesmo nome pode não possuir estruturas idênticas.

Também é importante entender o processo chave de carregamento de classe, a delegação. Um ClassLoader possui ClassLoader pais e sempre irá fazer o seguinte:

  • A Classe sempre irá pedir a ClassLoader que o carregou para carregar suas dependências;
  • ClassLoader sempre irá delegar o carregamento da classe ao seu pai, para verificar se o mesmo quer carrregar a classe antes.

Agora iremos explicar a hierarquia dos ClassLoaders. Quando a JVM inicia ela não possui nenhuma classe carregada. A JVM usa o mecanismo de carregamento de classes conhecido como lazyness, preguiçoso, a JVM vai carregando as classes conforme é necessário. Porém existe um problema, o código que carrega uma classe é o ClassLoader que em si é uma classe em Java, se a JVM não possui nenhuma classe carregada como é que ela vai carregar as outras classes pois conforme a especificação Java uma classe só é carregada quando todas as suas dependências forem carregadas. Conforme novamente a especificação Java, ClassLoader herda de Object que em si deve ser carregado por uma ClassLoader o que leva a um dead-lock. A solução para isso é ter um ClassLoader que de fato não é escrito em Java e sim em código nativo. Esse CL nativo irá carregar as classes ClassLoader, AppClassLoader e ExtClassLoader. A árvore de CL é a hierarquia formada pelo agrupamento dos mesmos. Por padrão temos a seguinte hierarquia:

Hierarquia de ClassLoaders

O BootStrap é o CL nativo executado pela JVM o seu classpath encontra as classes do sistema, principalmente o runtime do Java. O ExtCL (Extension ClassLoader) tem como seu classpath frameworks ou aplicativos específicos do usuário, caso queira que esses sejam visíveis em toda a VM. O pai do ExtCL é o BootStrap. E por fim o AppClassLoader que é o CL que irá carregar sua aplicação e seu classpath é o especificado no manifest do jar ou o passado na linha de comando, e o pai desse é o ExtCL.

Faremos uma aplicação para demonstrar os conceitos vistos:

public class Info {
	public static void main(String args[]) {
		System.out.println("Boot class path: " + System.getProperty("sun.boot.class.path"));
		System.out.println("Extension class path: " + System.getProperty("java.ext.dirs"));
		System.out.println("AppClassPath: " + System.getProperty("java.class.path"));

		Info i = new Info();
		System.out.println("\nBoot CL: " + java.lang.Object.class.getClassLoader());
		System.out.println("App ClassLoader: " + i.getClass().getClassLoader());
	}
}

Resultado obtido:

[marcos@hades AUR]$ java Info
Boot class path: /opt/java/jre/lib/resources.jar:/opt/java/jre/lib/rt.jar:/opt/java/jre/lib/sunrsasign.jar:/opt/java/jre/lib/jsse.jar:/opt/java/jre/lib/jce.jar:/opt/java/jre/lib/charsets.jar:/opt/java/jre/classes
Extension class path: /opt/java/jre/lib/ext:/usr/java/packages/lib/ext
AppClassPath: .

Boot CL: null
App ClassLoader: sun.misc.Launcher$AppClassLoader@19134f4

Desse resultado podemos claramente ver o que foi explicado, o classpath do BootStrap é o “SDK” do Java, o do ExtClassLoader é o diretório de extensões e o da AppClassLoader é o passado no argumento na cli, caso não tenha sido passado ele assume que é o diretório local: ., ou lido do manifest do jar. E também vemos que quem carregou nossa classe principal foi o AppClassLoader. Também vemos que o bootstrap CL é representado por null.

Bem você já deve ter adivinhado por agora que somente esses três CL são pré-definidos sendo assim nós podemos adicionar e alterar classloaders nessa árvore. Isso garante flexibilidade e isolamento de código, algo muito utilizado em Servidores de Aplicação como o JBoss e Glassfish. O servidor vai isolar cada requisição criando classloader de modo que uma requisição não seja visível na “árvore” a outra, isso garante uma robustez e segurança muito grande a plataforma.

Outro grande exemplo da manipulação de classloaders é o escopo estático de uma variável. Uma variável tem seu escopo estático, isso é, visibilidade, do CL  que foi carregado pra baixo. Vejamos um exemplo abaixo:

import java.io.*;
import java.net.*;
public class EscopoEstatico {
	public static int instanceCount = 0;
	public EscopoEstatico() {
		System.out.println("Valor da instancia:" + ++instanceCount);
	}

	public static void main (String args[]) throws Exception {
		new EscopoEstatico();
		// Vamos criar um Cl novo e colocalo abaixo do bootstrap
		// Consequentemente a variavel static vai ser diferente lá
		// do que a especificada.
		URL urlArray = {new File(System.getProperty("user.dir")).toURL() };
		URLClassLoader cl = new URLClassLoader(urlArray, null);
		cl.loadClass("EscopoEstatico").newInstance();
	}
}

Resultado:

[marcos@hades AUR]$ java EscopoEstatico
Valor da instancia:1
Valor da instancia:1

O que fizemos pode ser visualizado na imagem abaixo:
O problema é que a variável estática não está definida no caminho definido pelo o outro CL, então é considerado um novo “escopo”.

Então um dos problemas que pode surgir é, dado que eu carrego uma requisição em classloaders diferente e de modo que os mesmos não tenham nenhum CL em Comum, todos sejam filhos de null como garantir a visibilidade dó código ao longo da JVM? Bem o que sabemos é que todos diretamente ou indiretamente tem um CL em comum, o CL bootstrap (null). Porém esse CL só carrega os runtimes da plataforma Java, por padrão. Poucos sabem que podemos editar, adicionar, sobrescrever o classpath do BootStrap. Você pode até mesmo sobrescrever a implementação de uma classe que não gosta do JDK, como a implementação da pilha, etc. Concluindo, a ideia é estender os jars visíveis a este adicionando os arquivos que você queria que seja visível na JVM inteira. Isso é feito utilizando as opções X (extended) da JVM.

[marcos@hades AUR]$ java -X
    -Xmixed           mixed mode execution (default)
    -Xint             interpreted mode execution only
    -Xbootclasspath:<directories and zip/jar files separated by :>
                      set search path for bootstrap classes and resources
    -Xbootclasspath/a:<directories and zip/jar files separated by :>
                      append to end of bootstrap class path
    -Xbootclasspath/p:<directories and zip/jar files separated by :>
.......

Como disse anteriormente você pode sobrescrever (-Xbootclasspath) ou adicionar no começo (/p de prefix) ou no fim (/a de append) jars ao classpath do Bootstrap. Você pode até mesmo sobrescrever classes do JDK. Vejamos um exemplo abaixo:

Copie o arquivo Integer.java do source do jdk (java/lang) e modifique seu construtor
Arquivo Integer.java:

public class Integer {
...
    public Integer(int value) {
		System.out.println("Vou dividir seu int por 2, pq sou mau =P");
		System.out.println("VALOR ANTES: " + value + " VALOR DEPOIS " + value/2 + "\n");
		this.value = value / 2;
    }
....
}

Crie um diretório temporário, como ~/temp e também crie ~/temp/java/lang. Note que ~ representa o diretório home do usuário.
Compile o arquivo Integer.java e copie o seus .class’s para o seguinte diretório, ~/temp/java/lang.
Agora vamos fazer um simples teste.
Arquivo Teste.java:

public class Teste {
	public static void main(String args[]) {
		System.out.println(System.getProperty("sun.boot.class.path"));
		new Integer(33);
	}
}

Compile e coloque esse arquivo no diretório ~/temp.
Agora execute a seguinte linha de comando:

[marcos@hades AUR]$ java -Xbootclasspath/p:. Teste
Vou dividir seu int por 2, pq sou mau =P
VALOR ANTES: 1 VALOR DEPOIS 0

Vou dividir seu int por 2, pq sou mau =P
VALOR ANTES: 2 VALOR DEPOIS 1

Vou dividir seu int por 2, pq sou mau =P
VALOR ANTES: 15 VALOR DEPOIS 7

.:/opt/java/jre/lib/resources.jar:/opt/java/jre/lib/rt.jar:/opt/java/jre/lib/sunrsasign.jar:/opt/java/jre/lib/jsse.jar:/opt/java/jre/lib/jce.jar:/opt/java/jre/lib/charsets.jar:/opt/java/jre/classes
Vou dividir seu int por 2, pq sou mau =P
VALOR ANTES: 33 VALOR DEPOIS 16

Vamos chamar agora sem modificar o classpath do bootstrap:

[marcos@hades AUR]$ java Teste
/opt/java/jre/lib/resources.jar:/opt/java/jre/lib/rt.jar:/opt/java/jre/lib/sunrsasign.jar:/opt/java/jre/lib/jsse.jar:/opt/java/jre/lib/jce.jar:/opt/java/jre/lib/charsets.jar:/opt/java/jre/classes

A própria JVM quando inicia executa algumas chamadas criando novos Integers, como podemos ver. Porém o grande resultado é que vemos que o diretório atual, representado por ., foi pré-fixado na classpath do BootStrap e não foi chamado o Integer normal do JDK. Isso garante uma flexibilidade enorme, pois podemos facilmente alterar qualquer implementação não desejada do JDK. Alguns hackers modificavam versões da classe System pois o método getEnv de pegar o PATH do usuário foi depreciado antigamente e não era possível chama-lo até recentemente (acho que adicionaram denovo no jdk 1.5-1.6). Porém a ideia principal é que podemos facilmente garantir uma classe escopo global na JVM colocando seu jar ou .class na lista de classpath do BootStrap.

No próximo tópico irei postar algumas modificações, manipulações e resultados de brincar com os classloaders.

Referências:
[1] Java Geek, Using the BootClasspath – http://www.javageeks.com/~tneward
[2] Inside the Java Virtual Machine – Bill Vernes – The McGraw-Hill Companies (1997)
[3] Inside Java™ 2 Platform Security: Architecture, API Design, and Implementation, Second Edition By Li Gong, Gary Ellison, Mary Dageforde

About these ads

Written by marcosroriz

December 16, 2009 at 8:00 pm

Posted in Ciência da Computação, Java

Tagged with ,

3 Responses

Subscribe to comments with RSS.

  1. Olá Marcos,

    Primeiramente, parabéns por este ótimo post, muito bem explicado. Alias me fez lembrar uma coisa, à alguns anos atrás fiz uma entrevista para uma grande empresa de fora do Brasil e uma das perguntas dele foi justamente sobre ClassLoader, lembro-me que a pergunta era mais ou menos assim: “Quantos ClassLoader tem o Tomcat?” (Padrão sem adicionar novos). Bom, na hora não respondi porque não sabia, logo depois da entrevista corri para o PC para ver a resposta.
    Mas isso tudo é só p/ exemplificar a importância de ter um conhecimento mais profundo sobre a ferramente/linguagem que vc usa no seu cotidiano.

    Mais uma vez parabéns!

    Marivaldo Cabral

    December 29, 2009 at 11:10 am

    • Valeu :)
      Sabia que meus posts um dia iriam ser lidos LOL.

      E sim, e bem legal aprender como funciona a ferramenta internamente.

      Marcos Roriz

      December 30, 2009 at 10:11 am

  2. This is the upcoming Fallout 4 Beta Keys. With this Fallout 4 Generator you will be capable
    to create beta keys to use on your Xbox 360|Ps3 |or even Computer.

    Providing you opportunity to enter the world of the new
    redesigned Fallout four : Neglected Purchase in a beta phase.
    We are a massive gaming local community with tons of keys on every single platform.
    Our keys are coming from various builders this kind of as Obsidian Amusement who by
    themselves require beta testers for this match ahead of launch
    date. Each and every beta keys coming from our Fallout four Beta Keys
    Generator will be created from our databases and discover the unused legitimate keys and
    offer them to you in a single click on. Every single keys. As we can constantly get beta keys and feed our database our purposes are always up-to date.

    Fallout 4 crack

    April 14, 2014 at 4:12 am


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: