Entender o ambiente de execução e conhecer os valores dos parâmetros de sistema como informações sobre a rede e a memória permite resolvermos diversos problemas rotineiros.

Muitas informações úteis são acessíveis através de classes disponibilizadas pela API do Java. No entanto, as informações sobre recursos físicos e hardware são limitados, pois Java é multiplataforma e uma linguagem de alto nível, o que dificulta o acesso às bibliotecas do sistema operacional. Mesmo assim diversos elementos importantes do ambiente estão disponíveis como veremos no restante do artigo, e caso seja necessário, podemos utilizar outros recursos da linguagem para que possamos ter acesso às informações de mais baixo nível.

Propriedades de Sistema

As propriedades de sistema são pares formados por nome e valor que fornecem informações sobre o ambiente de execução. Entre as informações disponíveis nas propriedades de sistema temos os dados sobre o sistema operacional, o nome do usuário atualmente logado, os dados sobre o fornecedor, qual a versão da JVM (Java Virtual Machine), caminhos para classes e carregamento de bibliotecas, os diretórios iniciais e atuais do usuário, além de diversas outras informações.

A classe java.lang.System possui a documentação completa sobre as propriedades que podemos acessar. A documentação pode ser encontrada aqui.

Segue na Listagem 1 um exemplo de como podemos imprimir todas as propriedades de sistema.


import java.util.Enumeration;
import java.util.Properties;
 
 
public class PropSistemas {
 
         
   public void imprimePropriedadesSistema() {
             
     StringBuffer buffer = new StringBuffer(1000);
     Properties props = System.getProperties();
     
     for (Enumeration keys = props.keys(); keys.hasMoreElements();) {
        String key = (String)keys.nextElement();
        buffer.append(key);
        buffer.append("=");
        buffer.append(System.getProperty(key));
        buffer.append('\n');
     }
     
     System.out.println(buffer.toString());
   }
   
   
   public static void main (String args[]) {
     PropSistemas propSistemas = new PropSistemas();
     propSistemas.imprimePropriedadesSistema();
   }
         
}
Listagem 1. Obtendo informações sobre as propriedades do sistema

A execução deste código retornará uma saída semelhante a presente na Listagem 2.


java.vm.vendor.url.bug=http://download.oracle.com/docs/cd/E15289_01/go2troubleshooting.html
java.runtime.name=Java(TM) SE Runtime Environment
java.vm.version=R28.2.5-20-152429-1.6.0_37-20120927-1915-windows-x86_64
sun.boot.library.path=C:\Program Files\Java\jrockit-jdk1.6.0_37-R28.2.5-4.1.0\jre\bin
java.vm.vendor=Oracle Corporation
java.vendor.url=http://www.oracle.com/
path.separator=;
java.vm.name=Oracle JRockit(R)
file.encoding.pkg=sun.io
sun.java.launcher=SUN_STANDARD
java.vm.vendor.url=http://www.oracle.com/
user.country=BR
sun.os.patch.level=Service Pack 1
java.vm.specification.name=Java Virtual Machine Specification
user.dir=C:\eclipse\eclipse-jee-kepler-SR1-win32\workspace\TestaCriptografia
java.runtime.version=1.6.0_37-b06
java.awt.graphicsenv=sun.awt.Win32GraphicsEnvironment
java.endorsed.dirs=C:\Program Files\Java\jrockit-jdk1.6.0_37-R28.2.5-4.1.0\jre\lib\endorsed
os.arch=amd64
java.io.tmpdir=C:\Users\higor\AppData\Local\Temp  line.separator=
java.vm.specification.vendor=Sun Microsystems Inc.
user.variant=
os.name=Windows 7
sun.jnu.encoding=Cp1252
java.library.path=C:\Program 
Files\Java\jrockit-jdk1.6.0_37-R28.2.5-4.1.0\bin;C:\Windows\system32;
C:\Windows;C:\Program Files (x86)\EasyPHP-DevServer-14.1VC9\binaries\php\php_runningversion;
C:\oracle\product\10.2.0\client_2\bin\;C:\Windows\system32;C:\Windows;
C:\Windows\System32\Wbem;C:\Program Files\Aladdin\eToken\PKIClient\x32;
C:\Program Files\Aladdin\eToken\PKIClient\x64;
C:\Windows\System32\Windows System Resource Manager\bin;C:\Windows\idmu\common;
C:\Program Files\TortoiseSVN\bin;
C:\Program Files\Microsoft\Web Platform Installer\;
C:\Program Files (x86)\Microsoft ASP.NET\ASP.NET Web Pages\v1.0\;
C:\Program Files (x86)\Windows Kits\8.0\Windows Performance Toolkit\;
C:\Program Files\Microsoft SQL Server\110\Tools\Binn\;
C:\Program Files\Java\jrockit-jre1.6.0_37-R28.2.5\bin;
C:\Windows\System32\WindowsPowerShell\v1.0\;C:\apache-ant-1.9.4-bin\apache-ant-1.9.4/bin;.
java.specification.name=Java Platform API Specification
java.class.version=50.0
sun.management.compiler=Oracle JRockit(R) Optimizing Compiler
os.version=6.1
user.home=C:\Users\higor
user.timezone=
java.awt.printerjob=sun.awt.windows.WPrinterJob
file.encoding=Cp1252
java.specification.version=1.6
java.class.path=C:\eclipse\eclipse-jee-kepler-SR1-win32\workspace\ user.name=higor
java.vm.specification.version=1.0
sun.java.command=PropSistemas
java.home=C:\Program Files\Java\jrockit-jdk1.6.0_37-R28.2.5-4.1.0\jre
sun.arch.data.model=64
user.language=pt
java.specification.vendor=Sun Microsystems Inc.
awt.toolkit=sun.awt.windows.WToolkit
java.vm.info=compiled mode
java.version=1.6.0_37
kernel.download.enabled=false
java.ext.dirs=C:\Program Files\Java\jrockit-jdk1.6.0_37-R28.2.5-4.1.0\jre\lib\ext
sun.boot.class.path=C:\Program 
Files\Java\jrockit-jdk1.6.0_37-R28.2.5-4.1.0\jre\lib\resources.jar;C:\Program 
Files\Java\jrockit-jdk1.6.0_37-R28.2.5-4.1.0\jre\lib\rt.jar;
C:\Program Files\Java\jrockit-jdk1.6.0_37-R28.2.5-4.1.0\jre\lib\sunrsasign.jar;
C:\Program Files\Java\jrockit-jdk1.6.0_37-R28.2.5-4.1.0\jre\lib\jsse.jar;
C:\Program Files\Java\jrockit-jdk1.6.0_37-R28.2.5-4.1.0\jre\lib\jce.jar;C:\Program 
Files\Java\jrockit-jdk1.6.0_37-R28.2.5-4.1.0\jre\lib\charsets.jar;
C:\Program Files\Java\jrockit-jdk1.6.0_37-R28.2.5-4.1.0\jre\classes
java.vendor=Oracle Corporation
file.separator=  java.vendor.url.bug=http://download.oracle.com/docs/cd/
E15289_01/go2troubleshooting.html
sun.io.unicode.encoding=UnicodeLittle
sun.cpu.endian=little
sun.desktop=windows
sun.cpu.isalist=amd64
Listagem 2. Saída do código da Listagem 1

Podemos verificar a grande quantidade de informações que temos acesso apenas verificando as informações de propriedades do sistema.

Para alterar as propriedades do sistema podemos utilizar o método System.setProperty(), conforme mostra o exemplo da Listagem 3.


public class TestaPropriedades {
   public static void main(String[] a) {
      setPropriedadesDoSistema();
      imprime();
   }
 
   public static void setPropriedadesDoSistema() {
      // Modificando uma propriedade do sistema
      System.setProperty("java.io.tmpdir","c:\\var\\tmp");
      // Adicionando minhas propriedades
      System.setProperty("meuprograma.nome","Teste de Propriedades");
      System.setProperty("meuprograma.versao","1.01");
   }
   public static void imprime() {
      String nomePrograma = System.getProperty("meuprograma.nome");
      String versaoPrograma = System.getProperty("meuprograma.versao");
      String dirTempIO = System.getProperty("java.io.tmpdir");
      System.out.println("Bem-vindo ao programa \""+ nomePrograma +", "
         + versaoPrograma +"\".");
      System.out.println("O diretório temporário do Java I/O está em " + dirTempIO + " .");
   }
}
Listagem 3. Exemplo de como configurar as propriedades do sistema

Informações do Sistema

As informações retornadas pelas propriedades do sistema são importantes, no entanto, as informações relacionadas ao sistema nos ajuda a sabermos informações importantes sobre o ambiente que o sistema irá operar. Entre as informações disponíveis podemos saber se um gerenciador de segurança está instalado, qual class loader foi utilizado para carregar a classe, quantas CPUs estão disponíveis, entre outros.

Segue na Listagem 4 um código que obtém algumas das informações do sistema.


public class InfosSistema {

   
   public void infosSistema() {
             
     StringBuffer buffer = new StringBuffer(200);
     buffer.append("Gerenciador de Segurança Instalado: ");
     buffer.append(System.getSecurityManager() == null ?
              "Nenhum gerenciador de segurança instalado." : 
              System.getSecurityManager().getClass().getName());
     buffer.append('\n');
     buffer.append("Class loader para esta classe: ");
     ClassLoader classLoader = this.getClass().getClassLoader();
     buffer.append(classLoader == null ?
              "Nenhum Class loader encontrado." : classLoader.getClass().getName());
     buffer.append('\n');
     buffer.append("Número de processadores disponíveis para a JVM: ");
     buffer.append(Runtime.getRuntime().availableProcessors());
     buffer.append('\n');
     
     System.out.println(buffer.toString());
             
   }

   
   public static void main (String args[]) {
             
     InfosSistema infosSistema = new InfosSistema();
     infosSistema.infosSistema();
             
   }
   
   
}
Listagem 4. Obtendo informações sobre o sistema

A execução deste código retornará uma saída semelhante a seguir:


Gerenciador de Segurança Instalado: Nenhum gerenciador de segurança instalado.
Class loader para esta classe: sun.misc.Launcher$AppClassLoader
Número de processadores disponíveis para a JVM: 8

Informações da Memória

Uma das funções da JVM é gerenciar memória disponível para as aplicações Java. Quando a aplicação solicita mais memória para a JVM, a JVM primeiramente faz uma verificação se ela pode atender a essa solicitação usando a memória livre já alocada ao processo, caso não houver memória livre suficiente, a JVM pode forçar a realização de uma coleta de lixo, tentando assim liberar a memória utilizada por objetos nulos. Se a coleta de lixo não conseguir liberar memória suficiente para o processo, a JVM tenta obter mais memória no sistema operacional, aumentando assim a memória total alocada. A JVM continua obtendo mais memória do sistema operacional até o limite máximo permitido que é 16MB por padrão, mas este valor pode ser alterado através do parâmetro -Xmx. Um exemplo de utilização do parâmetro “xmx” seria a linha de comando "java -server -Xmx1500m ...".

O gerenciamento de memória é bastante importante para o desempenho das aplicações Java, por isso é sempre interessante monitorarmos o uso de memória da aplicação, isso pode ser feito via um profiler como o JProbe ou o OptimizeIt. Vale ressaltar que essas ferramentas exigem que as aplicações sejam executadas em modo de depuração, o que é algo inaceitável para sistemas em produção. Porém, outras ferramentas como Wily Introscope e Borland Server Trace podem monitorar e exibir o uso de memória de qualquer aplicação em execução com o mínimo de overhead.

Segue na Listagem 5 um exemplo de código que imprime o perfil atual de memória da JVM.


public class InfosMemoria {
  
   public void infosMemoria() {
             
       StringBuffer buffer = new StringBuffer(200);
       buffer.append("Memória máxima permitida para a JVM:");
       final long RAM_MAX = Runtime.getRuntime().maxMemory();
       final long RAM_MAX_MB = RAM_MAX / 1024 / 1024;
       buffer.append(RAM_MAX_MB);
       buffer.append(" Mb\n");

       buffer.append("Memória atualmente alocada na JVM:");
       final long RAM_TOTAL = Runtime.getRuntime().totalMemory();
       final long RAM_TOTAL_MB = RAM_TOTAL / 1024 / 1024;
       buffer.append(RAM_TOTAL_MB);
       buffer.append(" Mb\n");
       
       buffer.append("Memória atualmente livre na JVM:");
       final long RAM_FREE = Runtime.getRuntime().freeMemory();
       final long RAM_FREE_MB = RAM_FREE  / 1024 / 1024;
       buffer.append(RAM_FREE_MB);
       buffer.append(" Mb\n");
       
       buffer.append("Memória atualmente usada na JVM:");
       final long RAM_USED = RAM_TOTAL - RAM_FREE;
       final long RAM_USED_MB = (long) RAM_USED / 1024 / 1024;
       buffer.append(RAM_USED_MB);
       buffer.append(" Mb\n");
       
       buffer.append("Porcentagem de RAM utilizada na JVM: ");
       final double RAM_USED_PERCENTAGE = ((double) RAM_USED / RAM_TOTAL) * 100;
       buffer.append(RAM_USED_PERCENTAGE);
       buffer.append(" %");
       
       System.out.println(buffer.toString());
             
   }

   
   public static void main (String args[]) {
             
       InfosMemoria infosMemoria = new InfosMemoria();
       infosMemoria.infosMemoria();
             
   }
   
   
}
Listagem 5. Obtendo informações sobre a memória

A execução deste código retornará uma saída semelhante a seguir:


Memória máxima permitida para a JVM:3072 Mb
Memória atualmente alocada na JVM:64 Mb
Memória atualmente livre na JVM:60 Mb
Memória atualmente usada na JVM:3 Mb
Porcentagem de RAM utilizada na JVM: 5.474817752838135 %
Listagem 1. NOME

Informações de Rede

Informações sobre a rede necessitam de acesso a drivers de baixo nível do sistema operacional, o que não é muito prudente para uma linguagem de alto nível e multiplataforma conforme já discutimos em outros momentos. Além disso, não há muito que coletar ou aprender sobre um ambiente de rede que uma aplicação Java está utilizando. Praticamente as únicas informações realmente úteis que podemos obter são o hostname local e o endereço IP. Se precisarmos de informações adicionais sobre a configuração da rede podemos utilizar a JNI (Java Native Interface) ou JNA (Java Native API) que fazem chamadas nativas ao sistema operacional. Para obter a documentação completa da JNI podemos visitar o site http://docs.oracle.com/javase/7/docs/technotes/guides/jni/ ou para JNA podemos encontrar todas as informações pertinentes em https://github.com/twall/jna. Essas APIs também permitem executar um método de uma biblioteca já existente em outra linguagem, como uma biblioteca em C conforme veremos mais adiante no artigo.

Dessa forma, JNI é um padrão de programação que permite que um código Java que esteja executando em uma máquina virtual Java (JVM) chame ou então seja chamado por aplicações nativas (programas específicos para uma plataforma de hardware e sistema operacional) e bibliotecas escritas em outras linguagens como, por exemplo, C, C++ e Assembly. Assim, a mágica por trás do JNI é habilitar os programadores a escreverem métodos nativos para tratar situações em que uma aplicação não pode ser escrita inteiramente na linguagem Java, ou seja, quando a biblioteca padrão de classes Java não suporta bibliotecas ou características específicas da plataforma.

Segue na Listagem 6 um exemplo de código que obtém informações sobre o host, nesse caso não estamos utilizando JNI ainda.


import java.net.InetAddress;
import java.net.UnknownHostException;

public class InfosRede {

   
   public void infosRede() {
             
       StringBuffer buffer = new StringBuffer(200);
       InetAddress localhost = null;
       
       try {
                
          localhost = java.net.InetAddress.getLocalHost();
          buffer.append("Nome do Host local: ");
          buffer.append(localhost.getHostName());
          buffer.append('\n');
          buffer.append("Endereço IP do Host local: ");
          buffer.append(localhost.getHostAddress());
                
       } catch(UnknownHostException ex) {
                
          buffer.append("Falha para detectar propriedades da rede.");
          buffer.append(ex.getMessage());
                
       }

       System.out.println(buffer.toString());
   }

   
   public static void main (String args[]) {
             
       InfosRede infosRede = new InfosRede();
       infosRede.infosRede();
             
   }
   
   
}
Listagem 6. Obtendo informações sobre a rede

A execução deste código retornará uma saída semelhante a seguir:


Nome do Host local: W0318951
Endereço IP do Host local: 10.200.0.173

Para exemplificarmos o uso do JNI devemos primeiramente criar a assinatura em Java conforme exemplo da Listagem 7.


public class CalcExemploJNI {
  public native int multiplica(int num1, int num2);

  //Bloco estático para carregar a biblioteca "multiplicador" que está em C
  static{
      System.loadLibrary("multiplicador");
  }
}
Listagem 7. Criando a assinatura em Java

Podemos notar o uso do modificador "native". O modificador “native” especifica que o método é implementado na linguagem nativa do computador, que pode ter sido compilado em C, C++ ou Pascal, por exemplo, ao invés de ter sido implementado em Java. Também devemos pedir para que a JVM carregue a biblioteca que contém o código em C através do bloco estático, isso é feito usando o método loadLibrary da classe System.

Para o JNI conseguir chamar o código existente em C é necessário que a assinatura do método em C seja equivalente a do método declarado como native em Java. Também precisamos usar um conjunto de tipos específicos do JNI para que possamos fazer a conversão entre as duas linguagens.

O JDK possui uma ferramenta chamada "javah"que já gera essa assinatura para não nos preocuparmos com esses detalhes, basta passar como parâmetro a classe que possui o método nativo como o a seguir:


javah CalcExemploJNI

Dessa forma, será gerado um arquivo chamado CalcExemploJNI.h, que contém a assinatura do método e os tipos equivalentes conforme código da Listagem 8.


/* DO NOT EDIT THIS FILE - it is machine generated */
#include <stdio.h>
/* Header for class CalcExemploJNI */
 
#ifndef _Included_CalcExemploJNI
#define _Included_CalcExemploJNI
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: CalcExemploJNI
* Method: multiplica
* Signature: (II)I
*/
JNIEXPORT jint JNICALL Java_CalcExemploJNI_multiplica(JNIEnv *, jobject, jint, jint);
 
#ifdef __cplusplus
}
#endif
#endif
Listagem 8. Arquivo CalcExemploJNI

No arquivo multiplicadorJNI.c colocamos a nossa implementação da calculadora importando o arquivo criado anteriormente.

Segue na Listagem 9 um exemplo de como ficaria nosso código em C.


#include <stdio.h>
#include "CalcExemploJNI.h"
 
/*
 * Método que executa a soma
 */
int multiplica(int num1, int num2){
    int resultado = num1 * num2;
    return resultado;
}
 
/*
 * Método com a mesma assinatura do CalcExemploJNI.h
 */
JNIEXPORT jint JNICALL Java_Calculadora_multiplica(JNIEnv * env, 
jobject jobj, jint num1, jint num2){
    return multiplica(num1, num2);
}
Listagem 9. Arquivo de cabeçalho gerado automaticamente pelo javah

Agora já podemos compilar o código anterior usando o compilador gcc (GNU Compiler Collection), por exemplo. Não podemos esquecer de incluir o arquivo de cabeçalho acima no momento da compilação.

Por fim, devemos criar uma classe em Java para testar nossa calculadora, como mostra o código da Listagem 10.


public class TestaCalcExemploJNI {
  public static void main(String[] args) {
      CalcExemploJNI calc = new CalcExemploJNI();

      int num1 = Integer.parseInt(args[0]);
      int num2 = Integer.parseInt(args[1]);

      int resultado = calc.multiplica(num1, num2);
      System.out.println("A multiplicação resultante é: " + resultado);
  }
}
Listagem 10. Testando acesso a calculadora

Para compilar o código, devemos executar o comando javac conforme o a seguir:


javac CalcExemploJNI.java
javac TestaCalcExemploJNI.java

Serão gerados os arquivos CalcExemploJNI.class e TestaCalcExemploJNI.class.

Para executarmos os códigos compilados usamos o comando Java a seguir passando os parâmetros "2" e "3":


java TestaCalcExemploJNI 2 3

Dessa forma, já podemos criar um código utilizando a linguagem C que obtém informações específicas do sistema e, após isso, disponibilizar essas informações no código em Java.

Informações de Variáveis de Ambiente

As variáveis de ambiente fornecem uma maneira alternativa para que possamos parametrizar uma aplicação, sem precisarmos recorrer a arquivos externos, por exemplo. Um exemplo disso é quando precisamos saber o diretório de instalação da aplicação que talvez seja diferente entre os hosts. Como sabemos não existe uma maneira confiável de descobrir onde a aplicação foi efetivamente instalada. Por isso que uma prática comum é definirmos a variável de ambiente APP_HOME e passá-la para a aplicação Java. Uma alternativa recomendável ao invés das variáveis de ambiente são os arquivos com o formato de propriedades (arquivos .proprerties) ou arquivos XML e uma variável de ambiente que indique a localização dos arquivos de configuração.