 | Nível: Intermediário Michael Dawson, Advisory Software Developer, IBM Ottawa Lab Graeme Johnson, J9 Virtual Machine Development Manager, IBM Andrew Low, STSM, J9 Virtual Machine, IBM
07/Jul/2009 A Java™ Native Interface (JNI) é uma API Java padrão que possibilita a integração do código Java com o código gravado em outras linguagens de programação. JNI pode ser um elemento chave em seu kit de ferramentas se você quiser usar ativos de código existentes — por exemplo, em uma Arquitetura Orientada a Serviços (SOA) ou em um sistema baseado em nuvem. Mas quando usada sem o devido cuidado, JNI pode levar rapidamente a aplicativos com desempenho fraco e instável. Este artigo identifica as 10 principais armadilhas de programação JNI, fornece boas práticas para evitá-las e introduz as ferramentas disponíveis para implementar boas práticas.
O ambiente e a linguagem Java são seguros e eficientes para o desenvolvimento de aplicativos. No entanto, alguns aplicativos precisam executar tarefas que vão além do que pode ser feito a partir de um programa Java puro, como:
 |
Evolução de JNI
JNI faz parte da plataforma Java desde o release JDK 1.1 e foi estendida no release JDK 1.2. O release JDK 1.0 incluía uma interface de método nativo anterior que não possuía separação clara entre o código nativo e o Java. Nessa interface, os nativos atingiam diretamente as estruturas da JVM e, portanto, não podiam ter portabilidade entre implementações da JVM, plataformas ou, até mesmo, versões do JDK. Fazer upgrade de um aplicativo com um número significativo de códigos nativos usando o modelo JDK 1.0 era caro, assim como desenvolvê-los para que fossem executados com diversas implementações de JVM.
A introdução da JNI no release JDK 1.1 permitiu:
- Independência de versão
- Independência de plataforma
- Independência de VM
- Desenvolvimento de bibliotecas de classes de terceiros
É interessante observar que linguagens mais jovens, como PHP, ainda estão lutando com problemas como
esse de suporte a código nativo.
|
|
- Integrar-se ao código legado existente para evitar uma regravação.
- Implemente a funcionalidade ausente nas bibliotecas de classes disponíveis. Por exemplo, a funcionalidade Internet Control Message Protocol (ICMP) pode ser necessária se você estiver implementando
ping na linguagem Java, mas as bibliotecas de classes base não fornecem isso.
- Integrar-se ao código gravado em C/C++, para explorar o desempenho ou outras características do sistema específicas do ambiente.
- Aborde circunstâncias especiais que requeiram código não Java. Por exemplo, a implementação das bibliotecas de classes principais pode requerer chamadas entre pacotes ou a necessidade de ignorar outras verificações de segurança Java.
A JNI permite realizar essas tarefas. Ela fornece uma separação clara entre a execução do código Java e do código nativo (como C/C++), definindo uma API clara para comunicação entre os dois. Na maior parte, evita referência de memória por código nativo na JVM, assegurando que os nativos possam ser gravados uma vez e que funcionem em diferentes implementações ou versões de JVM.
Com JNI, o código nativo está livre para interagir com objetos Java, obter e configurar valores de campos e chamar métodos sem muitas das restrições que se aplicam às mesmas funções no código Java. Essa liberdade é uma faca de dois gumes: troca a segurança da linguagem Java pela capacidade de realizar as tarefas listadas anteriormente. Usar JNI em seu aplicativo fornece acesso poderoso de nível inferior aos recursos da máquina (memória, E/S, etc.), portanto, você está trabalhando sem a rede de segurança geralmente fornecida a desenvolvedores de Java. A flexibilidade e o poder de JNI introduzem o risco de práticas de programação que podem levar a desempenho fraco, erros e, até mesmo, travamentos do programa. Deve-se tomar cuidado com o código incluído em seu aplicativo e usar boas práticas para proteger a integridade geral do aplicativo.
Este artigo aborda os 10 erros de codificação e design mais comuns que usuários da JNI cometem.
O objetivo é ajudá-lo a reconhecer e evitar os mesmos, de forma que você possa gravar código JNI seguro e efetivo que execute bem desde o início.
Este artigo também introduz ferramentas e técnicas disponíveis para localizar esses problemas em código novo ou existente e mostra como aplicá-las de forma eficiente.
As armadilhas de programação JNI encontram-se em duas categorias:
- Desempenho: O código executa a função projetada, mas faz isso tão lentamente ou de maneira que faça com que o programa todo fique mais lento.
- Correção: O código funciona às vezes, mas não fornece de forma confiável a função necessária; no pior caso, trava ou sofre interrupção.
Armadilhas de Desempenho
As cinco principais armadilhas de desempenho para programadores que estão usando JNI são:
Não Armazenar em Cache os IDs de Métodos, IDs de Campos e Classes
Para acessar campos de objetos Java e chamar seus métodos, o código nativo deve fazer chamadas a FindClass(), GetFieldID(), GetMethodId() e GetStaticMethodID(). No caso de GetFieldID(), GetMethodID()e GetStaticMethodID(), os IDs retornados para uma determinada classe não são alterados durante o processo da JVM. Mas a chamada para obter o campo ou o método pode precisar de trabalho significativo na JVM, pois campos e métodos podem ter sido herdados de superclasses, fazendo com que a JVM suba na hierarquia de classes para localizá-las. Como os IDs são os mesmos para uma determinada classe, devem ser verificados uma vez e, então, reutilizados. De forma semelhante, consultar objetos de classes pode ser caro, portanto, devem ser armazenados em cache também.
Por exemplo, a Lista 1 mostra o código JNI necessário para chamar um método estático:
Lista 1. Chamando um Método Estático com JNI
int val=1;
jmethodID method;
jclass cls;
cls = (*env)->FindClass(env, "com/ibm/example/TestClass");
if ((*env)->ExceptionCheck(env)) {
return ERR_FIND_CLASS_FAILED;
}
method = (*env)->GetStaticMethodID(env, cls, "setInfo", "(I)V");
if ((*env)->ExceptionCheck(env)) {
return ERR_GET_STATIC_METHOD_FAILED;
}
(*env)->CallStaticVoidMethod(env, cls, method,val);
if ((*env)->ExceptionCheck(env)) {
return ERR_CALL_STATIC_METHOD_FAILED;
}
|
Consultar a classe e o ID do método toda vez que quisermos chamar o método resultará em seis chamadas nativas em vez de duas que seriam necessárias se tivéssemos armazenado em cache a classe e o ID do método na primeira vez que foram necessários.
O armazenamento em cache cria um impacto significativo no tempo de execução de seu aplicativo. Considere as duas versões a seguir de um método, que acabam fazendo a mesma coisa. A versão na Lista 2 usa IDs de campo armazenados em cache:
Lista 2. Usando IDs de Campo Armazenados em Cache
int sumValues2(JNIEnv* env, jobject obj, jobject allValues){
jint avalue = (*env)->GetIntField(env, allValues, a);
jint bvalue = (*env)->GetIntField(env, allValues, b);
jint cvalue = (*env)->GetIntField(env, allValues, c);
jint dvalue = (*env)->GetIntField(env, allValues, d);
jint evalue = (*env)->GetIntField(env, allValues, e);
jint fvalue = (*env)->GetIntField(env, allValues, f);
return avalue + bvalue + cvalue + dvalue + evalue + fvalue;
}
|
 |
Dica de Desempenho Nº 1
Consultar e armazenar em cache, de forma global, classes, IDs de campos e IDs de métodos comumente usados.
|
|
A Lista 3 não usa IDs de campo armazenados em cache:
Lista 3. IDs de Campo Não Armazenados em Cache
int sumValues2(JNIEnv* env, jobject obj, jobject allValues){
jclass cls = (*env)->GetObjectClass(env,allValues);
jfieldID a = (*env)->GetFieldID(env, cls, "a", "I");
jfieldID b = (*env)->GetFieldID(env, cls, "b", "I");
jfieldID c = (*env)->GetFieldID(env, cls, "c", "I");
jfieldID d = (*env)->GetFieldID(env, cls, "d", "I");
jfieldID e = (*env)->GetFieldID(env, cls, "e", "I");
jfieldID f = (*env)->GetFieldID(env, cls, "f", "I");
jint avalue = (*env)->GetIntField(env, allValues, a);
jint bvalue = (*env)->GetIntField(env, allValues, b);
jint cvalue = (*env)->GetIntField(env, allValues, c);
jint dvalue = (*env)->GetIntField(env, allValues, d);
jint evalue = (*env)->GetIntField(env, allValues, e);
jint fvalue = (*env)->GetIntField(env, allValues, f);
return avalue + bvalue + cvalue + dvalue + evalue + fvalue
}
|
A versão na Lista 2 leva 3.572 min para executar 10.000.000 de vezes. A versão da Lista 3 leva 86.217 min — 24 vezes mais longa.
Acionar Cópias da Array
JNI fornece uma interface clara entre o código Java e o código nativo. Para manter essa separação, arrays são passadas como identificadores opacos e o código nativo deve chamar de volta a JVM para manipular elementos de array usando chamadas de configuração e obtenção. A especificação Java deixa por conta da implementação da JVM se essas chamadas fornecem acesso direto às arrays ou retornam uma cópia da array. Por exemplo, a JVM pode retornar uma cópia quando tiver otimizado arrays de forma que não as armazene de modo contíguo. (Consulte Recursos para obter uma descrição de tal JVM.)
Essas chamadas podem, então, causar a cópia dos elementos que estão sendo manipulados. Por exemplo, se você chamar GetLongArrayElements() em uma array com 1.000 elementos, poderá causar a alocação e cópia de pelo menos 8.000 bytes (1.000 elementos * 8 bytes para cada long). Ao atualizar o conteúdo da array então com ReleaseLongArrayElements() outra cópia de 8.000 bytes poderá ser necessária para atualizar a array. Mesmo ao usar o GetPrimitiveArrayCritical() mais novo, a especificação ainda permite que a JVM faça cópias da array integral.
 |
Dica de Desempenho Nº 2
Obtenha e atualize somente as partes de uma array necessárias para o nativo. Use as chamadas de API apropriadas para evitar a cópia de toda a array quando somente parte da mesma é necessária.
|
|
Os métodos GetTypeArrayRegion() e SetTypeArrayRegion() permitem que você obtenha e atualize uma região de uma array, em vez de toda a array. Usando esses métodos para acessar arrays maiores é possível assegurar que você copie somente a região da array que o nativo usará realmente.
Por exemplo, considere duas versões do mesmo método, mostradas na Lista 4:
Lista 4. Duas Versões do Mesmo Método
jlong getElement(JNIEnv* env, jobject obj, jlongArray arr_j,
int element){
jboolean isCopy;
jlong result;
jlong* buffer_j = (*env)->GetLongArrayElements(env, arr_j, &isCopy);
result = buffer_j[element];
(*env)->ReleaseLongArrayElements(env, arr_j, buffer_j, 0);
return result;
}
jlong getElement2(JNIEnv* env, jobject obj, jlongArray arr_j,
int element){
jlong result;
(*env)->GetLongArrayRegion(env, arr_j, element,1, &result);
return result;
}
|
A primeira versão pode causar duas cópias integrais da array, enquanto que a segunda não efetua nenhuma cópia. Executar o primeiro método 10.000.000 de vezes com uma array de 1.000 bytes leva 12.055 min; a segunda versão leva somente 1.421 min. A primeira versão leva 8,5 vezes mais tempo!
 |
Dica de Desempenho Nº 3
Obtenha ou atualize o máximo possível de uma array em uma única chamada de API. Não itere os elementos da array um a um quando é possível obter e atualizar um array em blocos maiores.
|
|
Por outro lado, usar GetTypeArrayRegion() para obter cada um dos elementos da array um a um também não terá um bom desempenho se você for terminar obtendo todos os elementos da array de qualquer forma.
Para um melhor desempenho, assegure que os elementos da array sejam obtidos e atualizados nos blocos sensíveis maiores. Se for iterar através de todos os elementos em uma array, nenhum dos dois métodos getElement() na Lista 4 é adequado. Em vez disso, é possível obter uma parte de tamanho razoável da array em uma chamada e, então, iterar todos esses elementos, repetindo até cobrir a array integral.
Buscar Atrás em vez de Passar Parâmetros
Ao chamar um método, frequentemente, é possível escolher entre passar um único objeto que possui diversos campos e passar os campos individualmente. Com designs orientados a objetos, passar o objeto frequentemente fornece melhor encapsulação, de forma que as mudanças nos campos de objetos não requeiram mudanças na assinatura do método. No entanto, no caso de JNI, um nativo deve buscar atrás na JVM através de uma ou mais chamadas de JNI para obter o valor para cada campo individual necessário. Essas chamadas adicionais incluem gasto adicional extra, pois a transição do código nativo para o Java é mais cara do que uma chamada de método normal. Para JNI, portanto, fazer com que os nativos busquem muitos campos individuais nos objetos passados a eles leva a um desempenho mais fraco.
Considere dois métodos na Lista 5, o segundo dos quais supõe que armazenamos em cache os IDs de campos:
Lista 5. Duas Versões do Método
int sumValues(JNIEnv* env, jobject obj, jint a, jint b,jint c, jint d, jint e, jint f){
return a + b + c + d + e + f;
}
int sumValues2(JNIEnv* env, jobject obj, jobject allValues){
jint avalue = (*env)->GetIntField(env, allValues, a);
jint bvalue = (*env)->GetIntField(env, allValues, b);
jint cvalue = (*env)->GetIntField(env, allValues, c);
jint dvalue = (*env)->GetIntField(env, allValues, d);
jint evalue = (*env)->GetIntField(env, allValues, e);
jint fvalue = (*env)->GetIntField(env, allValues, f);
return avalue + bvalue + cvalue + dvalue + evalue + fvalue;
}
|
 |
Dica de Desempenho Nº 4
Quando possível, passe parâmetros individuais para nativos JNI de forma que o nativo chame a JVM para obter os dados necessários para fazer seu trabalho.
|
|
O método sumValues2() requer seis retornos de chamada de JNI e leva 3.572 min para executar 10.000.000 de vezes. É seis vez mais lento que o sumValues(), que leva somente 596 min. Ao passar os dados necessários pelo método JNI, sumValues() evita uma quantidade significativa de gasto adicional de JNI.
Escolher o Limite Errado entre o Código Nativo e o Java
Depende do desenvolvedor definir o limite entre o código nativo e o Java. A opção do limite pode ter um impacto significativo no desempenho geral do aplicativo. O custo de chamar do código Java para nativos e de nativos para o código Java é significativamente mais alto do que uma chamada de método Java normal. Além disso, a transição pode interferir com a capacidade da JVM de otimizar a execução do código. Por exemplo, o compilador Just-in-time pode ser menos efetivo à medida que o número de transições entre o código Java e o código nativo aumenta. Sabemos, depois de medir, que chamar do código Java para um nativo pode levar cinco vezes mais tempo do que um método regular. De forma semelhante, as chamadas de um nativo para o código Java podem levar um tempo significativo.
 |
Dica de Desempenho Nº 5
Defina a divisão entre Java e nativos para minimizar as transições de Java para nativos e retornos de chamada de nativos para Java.
|
|
A divisão entre o código Java e os nativos deve, portanto, ser projetada para minimizar as transições entre o código Java e o nativo. As transições devem ser realizadas somente quando necessário e devem realizar trabalho suficiente em um nativo para amortizar o custo da transição. Um elemento chave para minimizar transições é assegurar que os dados sejam mantidos do lado correto do limite Java/nativo. Se os dados residirem do lado errado, as transições constantes serão acionadas pela necessidade do outro lado de buscar esses dados.
Por exemplo, se quisermos fornece uma interface para uma porta serial usando JNI, poderíamos obter duas interfaces diferentes. Uma versão está na Lista 6:
Lista 6. Interface para uma Porta Serial: Versão 1
/**
* Initializes the serial port and returns a java SerialPortConfig objects
* that contains the hardware address for the serial port, and holds
* information needed by the serial port such as the next buffer
* to write data into
*
* @param env JNI env that can be used by the method
* @param comPortName the name of the serial port
* @returns SerialPortConfig object to be passed ot setSerialPortBit
* and getSerialPortBit calls
*/
jobject initializeSerialPort(JNIEnv* env, jobject obj, jstring comPortName);
/**
* Sets a single bit in an 8 bit byte to be sent by the serial port
*
* @param env JNI env that can be used by the method
* @param serialPortConfig object returned by initializeSerialPort
* @param whichBit value from 1-8 indicating which bit to set
* @param bitValue 0th bit contains bit value to be set
*/
void setSerialPortBit(JNIEnv* env, jobject obj, jobject serialPortConfig,
jint whichBit, jint bitValue);
/**
* Gets a single bit in an 8 bit byte read from the serial port
*
* @param env JNI env that can be used by the method
* @param serialPortConfig object returned by initializeSerialPort
* @param whichBit value from 1-8 indicating which bit to read
* @returns the bit read in the 0th bit of the jint
*/
jint getSerialPortBit(JNIEnv* env, jobject obj, jobject serialPortConfig,
jint whichBit);
/**
* Read the next byte from the serial port
*
* @param env JNI env that can be used by the method
*/
void readNextByte(JNIEnv* env, jobject obj);
/**
* Send the next byte
*
* @param env JNI env that can be used by the method
*/
void sendNextByte(JNIEnv* env, jobject obj);
|
Na Lista 6, todos os dados de configuração para a porta serial estão armazenados no objeto Java retornado pelo método
initializeSerialPort() e o código Java está no controle integral da configuração de cada bit individual no hardware. Diversos problemas na versão da Lista 6 levarão a um desempenho mais fraco do que na versão da Lista 7:
Lista 7. Interface para uma Porta Serial: Versão 2
/**
* Initializes the serial port and returns an opaque handle to a native
* structure that contains the hardware address for the serial port
* and holds information needed by the serial port such as
* the next buffer to write data into
*
* @param env JNI env that can be used by the method
* @param comPortName the name of the serial port
* @returns opaque handle to be passed to setSerialPortByte and
* getSerialPortByte calls
*/
jlong initializeSerialPort2(JNIEnv* env, jobject obj, jstring comPortName);
/**
* sends a byte on the serial port
*
* @param env JNI env that can be used by the method
* @param serialPortConfig opaque handle for the serial port
* @param byte the byte to be sent
*/
void sendSerialPortByte(JNIEnv* env, jobject obj, jlong serialPortConfig,
jbyte byte);
/**
* Reads the next byte from the serial port
*
* @param env JNI env that can be used by the method
* @param serialPortConfig opaque handle for the serial port
* @returns the byte read from the serial port
*/
jbyte readSerialPortByte(JNIEnv* env, jobject obj, jlong serialPortConfig);
|
 |
Dica de Desempenho Nº 6
Estruture os dados do aplicativo de forma que exista do lado direito do limite e possa ser acessado pelo código que os usa sem exigir muitas transições entre o limite Java/nativo.
|
|
O problema mais óbvio é que a interface na Lista 6
requer uma chamada JNI para cada bit configurado ou recuperado, assim como uma chamada JNI para ler um byte ou gravar um byte na porta serial.
Isso leva para nove vezes o número de chamadas JNI para cada byte lido ou gravado.
O segundo problema é que a Lista 6 armazena as informações de configuração para a porta serial em um objeto Java que está do lado errado do limite Java/nativo com relação a onde os dados são usados.
Precisamos desses dados de configuração somente do lado nativo; armazená-los do lado Java causará diversos retornos de chamada do nativo para o Java para configurar/obter essas informações de configuração. A Lista 7 armazena as informações de configuração em uma estrutura nativa (por exemplo, um struct C) e retorna um identificador opaco para código Java, que pode ser retornado em chamadas subsequentes. Isso significa que quando um nativo está em execução, pode atingir a estrutura diretamente sem precisar de um retorno de chamada ao código Java para obter informações, como o endereço do hardware da porta serial ou o próximo buffer disponível. O desempenho de uma implementação usando a Lista 7 irá, portanto, ser muito melhor.
Usar Muitas Referências Locais sem Informar a JVM
As referências locais são criadas para qualquer objeto retornado por uma função JNI. Por exemplo, ao chamar GetObjectArrayElement(), uma referência local ao objeto na array é retornado. Considere quantas referências locais são usadas quando o código na Lista 8 é executado em uma array muito grande:
Lista 8. Criando Referências Locais
void workOnArray(JNIEnv* env, jobject obj, jarray array){
jint i;
jint count = (*env)->GetArrayLength(env, array);
for (i=0; i < count; i++) {
jobject element = (*env)->GetObjectArrayElement(env, array, i);
if((*env)->ExceptionOccurred(env)) {
break;
}
/* do something with array element */
}
}
|
Toda vez que GetObjectArrayElement() for chamado, uma referência local será criada para o elemento e não será liberada até o nativo ser concluído. Quanto maior a array, mais referências locais serão criadas.
 |
Dica de Desempenho Nº 7
Quando um nativo causa a criação de um número maior de referências locais, exclua cada referência quando não for mais necessário.
|
|
Essas referências locais são liberadas automaticamente quando o método nativo é encerrado. A especificação JNI requer que cada nativo seja capaz de criar pelo menos 16 referências locais. Embora isso seja adequado para muitos métodos, alguns métodos precisam acessar mais durante sua duração. Nesse caso, deve-se excluir referências que não sejam mais necessárias, usando a chamada JNI DeleteLocalRef(), ou informando a JVM que será usado um número maior de referências locais.
A Lista 9 inclui uma chamada em DeleteLocalRef() no exemplo da
Lista 8, informando a JVM que a referência local não é mais necessária e limitando o número de referências locais existentes em um dado momento para um número razoável, independentemente do tamanho da array:
Lista 9. Incluindo DeleteLocalRef()
void workOnArray(JNIEnv* env, jobject obj, jarray array){
jint i;
jint count = (*env)->GetArrayLength(env, array);
for (i=0; i < count; i++) {
jobject element = (*env)->GetObjectArrayElement(env, array, i);
if((*env)->ExceptionOccurred(env)) {
break;
}
/* do something with array element */
(*env)->DeleteLocalRef(env, element);
}
}
|
 |
Dica de Desempenho Nº 8
Se um nativo for ter um grande número de referências locais de forma simultânea, chame o método JNI EnsureLocalCapacity() para informar à JVM e permitir que a manipulação de referências locais seja otimizada para esse caso.
|
|
É possível chamar o método JNI EnsureLocalCapacity() para informar à JVM que você estará usando mais de 16 referências locais. Isso permite que a JVM otimize a manipulação de referências locais para esse nativo. Falha em informar à JVM pode levar a um FatalError se as referências locais necessárias não puderem ser criadas ou desempenho fraco que se deve a uma incompatibilidade entre o gerenciamento de referência local implementado pela JVM e o número de referências locais usadas.
Armadilhas de Correção
As cinco principais armadilhas de correção de JNI são:
Usar o JNIEnverrado
Um thread executando código nativo usa um JNIEnv para fazer chamadas de métodos JNI. Mas o JNIEnv é usado para mais do que apenas efetuar dispatch dos métodos solicitados. A especificação JNI determina que cada JNIEnv seja local para um thread. Uma JVM pode depender dessa suposição, armazenando informações adicionais locais do thread no JNIEnv. Uso do JNIEnv a partir de um thread por outro thread pode levar a erros repentinos e travamentos que são difíceis de depurar.
 | |
Um thread pode obter um JNIEnv chamando GetEnv() usando a interface de chamada JNI através de um objeto JavaVM. O próprio objeto JavaVM pode ser obtido, chamando o metodo JNI GetJavaVM() usando um objeto JNIEnv e pode ser armazenado em cache e compartilhado entre threads. Armazenar em cache uma cópia do objeto JavaVM ativa qualquer thread com acesso ao objeto armazenado em cache para obter acesso a seu próprio JNIEnv quando necessário. Para um desempenho otimizado, no entanto, um thread deve passar o JNIEnv que é recebido quando for chamado pelos métodos que chama, pois consultá-los pode requerer trabalho significativo.
Não Verificar Exceções
Muitos dos métodos JNI que nativos podem chamar podem levantar exceções no thread em execução. Quando o código Java é executado, essas exceções causam uma mudança no fluxo de execução, de forma que o caminho do código de manipulação de exceção seja chamado automaticamente. Quando um nativo faz uma chamada a um método JNI, uma exceção pode ser levantada, mas depende do nativo verificar as exceções e executar a ação apropriada. Uma armadilha de programação JNI comum é chamar um método JNI e prosseguir sem verificar exceções assim que a chamada for concluída. Isso pode levar a um código cheio de erros e a travamentos.
Por exemplo, considere o código que chama GetFieldID(), que resulta em
NoSuchFieldError se o campo solicitado não puder ser localizado. Se o código nativo continuar sem verificar a exceção e usar o ID do campo que acreditou ter sido retornado, pode ocorrer o travamento. O código na Lista 10, por exemplo, pode causar um travamento — em vez de emitir um NoSuchFieldError — se a classe Java for modificada de forma que o campo charField não exista mais:
Lista 10. Falha ao Verificar Exceções
jclass objectClass;
jfieldID fieldID;
jchar result = 0;
objectClass = (*env)->GetObjectClass(env, obj);
fieldID = (*env)->GetFieldID(env, objectClass, "charField", "C");
result = (*env)->GetCharField(env, obj, fieldID);
|
É muito mais fácil incluir o código para verificar a exceção do que tentar depurar um travamento posteriormente. Frequentemente, é possível simplesmente verificar se uma exceção ocorreu e, caso tenha ocorrido, retornar imediatamente ao código Java, de forma que a exceção seja emitida. Será então, manipulada ou exibida usando o processo de manipulação de exceção Java normal. Por exemplo, a Lista 11 verifica se há exceções:
Lista 11. Verificando Exceções
jclass objectClass;
jfieldID fieldID;
jchar result = 0;
objectClass = (*env)->GetObjectClass(env, obj);
fieldID = (*env)->GetFieldID(env, objectClass, "charField", "C");
if((*env)->ExceptionOccurred(env)) {
return;
}
result = (*env)->GetCharField(env, obj, fieldID);
|
Não verificar e limpar exceções pode resultar em um comportamento inesperado. Você consegue ver o que há de errado neste código?
fieldID = (*env)->GetFieldID(env, objectClass, "charField", "C");
if (fieldID == NULL){
fieldID = (*env)->GetFieldID(env, objectClass,"charField", "D");
}
return (*env)->GetIntField(env, obj, fieldID);
|
O problema é que, apesar de o código tratar do caso em que o GetFieldID() inicial não retorna o ID do campo, não limpa a exceção que essa chamada configuraria. O retorno do nativo irá, portanto, causar a emissão imediata de uma exceção.
Não Verificar Valores de Retorno
Muitos métodos JNI têm um valor de retorno que indica se a chamada foi bem-sucedida ou não. Uma armadilha comum, semelhante à que não verifica as exceções, é não verificar o valor de retorno e o código continuar a supor que a chamada tenha sido bem-sucedida. Para a maioria dos métodos JNI, o valor de retorno e o status da exceção serão ambos configurados, de forma que verificar o status da exceção ou o valor de retorno indicará ao aplicativo se o método foi executado corretamente ou não.
 |
Dica de Correção Nº 3
Sempre verifique o valor de retorno de um método JNI e inclua caminhos de código para tratar de erros.
|
|
Você consegue ver o que há de errado com o código a seguir?
clazz = (*env)->FindClass(env, "com/ibm/j9//HelloWorld");
method = (*env)->GetStaticMethodID(env, clazz, "main",
"([Ljava/lang/String;)V");
(*env)->CallStaticVoidMethod(env, clazz, method, NULL);
|
Os problemas são que se a classe HelloWorld não for localizada ou se o método main() não existir, o nativo causará um travamento.
Usar métodos de array incorretamente
Os métodos GetXXXArrayElements() e ReleaseXXXArrayElements() permitem solicitar elementos da array. De forma semelhante, GetPrimitiveArrayCritical(), ReleasePrimitiveArrayCritical(), GetStringCritical() e ReleaseStringCritical() permitem solicitar elementos da array ou bytes de cadeia de caracteres para maximizar a probabilidade de que obterão um ponteiro para a matriz ou cadeia de caracteres. Duas armadilhas comuns estão associadas ao uso desses métodos. A primeira é esquecer de consolidar mudanças na chamada ao método ReleaseXXX(). Não há nenhuma garantia de que você irá realmente obter um ponteiro direto para a array ou cadeia de caracteres, mesmo ao usar as versões Critical. Algumas JVMs sempre retornarão uma cópia e, nessas JVMs, as mudanças feitas na array não serão copiadas novamente se você especificar JNI_ABORT na chamada a ReleaseXXX() ou esquecer de chamar ReleaseXXX().
Por exemplo, considere este código:
void modifyArrayWithoutRelease(JNIEnv* env, jobject obj, jarray arr1) {
jboolean isCopy;
jbyte* buffer = (*env)-> (*env)->GetByteArrayElements(env,arr1,&isCopy);
if ((*env)->ExceptionCheck(env)) return;
buffer[0] = 1;
}
|
 |
Dica de Correção Nº 4
Não se esqueça de chamar ReleaseXXX() com um modo 0 (copiar novamente e liberar a memória) para cada chamada GetXXX().
|
|
Em uma JVM que forneça um ponteiro direto para a array, a array será atualizada; no entanto, em uma JVM que retorna uma cópia, não será. Isso pode levar a casos em que seu código parece funcionar em algumas JVMs, mas não funciona em outras. Deve-se sempre incluir uma chamada de liberação, conforme mostrado na Lista 12:
Lista 12. Incluindo uma Chamada de Liberação
void modifyArrayWithRelease(JNIEnv* env, jobject obj, jarray arr1) {
jboolean isCopy;
jbyte* buffer = (*env)-> (*env)->GetByteArrayElements(env,arr1,&isCopy);
if ((*env)->ExceptionCheck(env)) return;
buffer[0] = 1;
(*env)->ReleaseByteArrayElements(env, arr1, buffer, JNI_COMMIT);
if ((*env)->ExceptionCheck(env)) return;
}
|
A segunda armadilha é não aceitar as restrições colocadas pela especificação no código que é executado entre GetXXXCritical() e ReleaseXXXCritical(). O nativo não pode fazer nenhuma chamada JNI entre os métodos e não pode bloquear por nenhuma razão. Não honrar essas restrições pode levar a um conflito intermitente no aplicativo ou na JVM como um todo.
Por exemplo, o código a seguir pode parecer correto:
void workOnPrimitiveArray(JNIEnv* env, jobject obj, jarray arr1) {
jboolean isCopy;
jbyte* buffer = (*env)->GetPrimitiveArrayCritical(env, arr1, &isCopy);
if ((*env)->ExceptionCheck(env)) return;
processBufferHelper(buffer);
(*env)->ReleasePrimitiveArrayCritical(env, arr1, buffer, 0);
if ((*env)->ExceptionCheck(env)) return;
}
|
 |
Dica de Correção Nº 5
Assegure que o código não faça nenhuma chamada JNI nem bloqueie, por qualquer razão, entre chamadas a GetXXXCritical() e ReleaseXXXCritical().
|
|
No entanto, é necessário validar que todo o código que pode ser executado quando processBufferHelper() for chamado não viole nenhuma das restrições. Essas restrições se aplicam a todo o código executado entre as chamadas Get e Release,
sendo parte do próprio nativo ou não.
Usar Referências Globais Incorretamente
Os nativos podem criar referências globais de forma que objetos não sejam coletados como lixo até que não sejam mais necessários. Armadilhas comuns são esquecer de excluir referências globais que foram criadas ou perder o controle das mesmas completamente. Considere um nativo que cria uma referência global, mas não exclui nem armazena a mesma em lugar algum:
lostGlobalRef(JNIEnv* env, jobject obj, jobject keepObj) {
jobject gref = (*env)->NewGlobalRef(env, keepObj);
}
|
 |
Dica de Correção Nº 6
Sempre controle as referências globais e assegure que sejam excluídas quando o objeto não for mais necessário.
|
|
Quando a referência global for criada, a JVM inclui a mesma em uma lista que exclui esse objeto da coleta de lixo. Quando o nativo retorna, ele não somente não liberou a referência global, como o aplicativo também não terá mais como obter a referência para liberá-la posteriormente — de forma que o objeto viverá para sempre. Não liberar as referências globais causa problemas não apenas porque elas mantêm o próprio objeto vivo, mas também porque mantêm vivos todos os objetos que possam ser atingidos através do objeto. Em alguns casos, isso pode resultar em uma fuga de memória significativa.
Evitando as Armadilhas Comuns
Suponhamos que você acabou de gravar algum código JNI novo ou que herdou algum código JNI de outro lugar.
Como é possível assegurar que tenha evitado as armadilhas comuns ou localizá-las em seu código herdado?
A Tabela 1 identifica as técnicas que podem ser usadas para eliminar pela raiz as instâncias das armadilhas comuns:
Tabela 1. Lista de Verificação para Identificar Armadilhas da Programação JNI
| Não armazenar em cache | Acionar cópias da array | Limite errado | Buscar muito atrás | Usar muitas referências locais | Usar JNIEnv incorreto | Não verificar exceções | Não verificar valores de retorno | Usar arrays incorretamente | Usar referências globais incorretamente |
|---|
| Validação com relação à especificação | | | | | | X | X | | X | | | Rastreio de método | X | X | X | X | | | X | | X | X | | Dumps | | | | | | | | | | X | -verbose:jni | | | | | X | | | | | | | Revisão de código | X | X | X | X | X | X | X | X | X | X |
É possível identificar muitas das armadilhas comuns no início do ciclo de desenvolvimento:
Validando Novo Código com Relação à Especificação de JNI
É uma boa prática manter uma lista dos limitadores impostos pela especificação e revisar nativos para conformidade com a lista, seja manualmente ou através de análise de código automática. Provavelmente, será usado muito menos esforço assegurando conformidade do que depurando as falhas sutis e intermitentes que podem ocorrer quando os limitadores não são observados. Segue uma lista inicial de verificações de conformidade com a especificação a serem realizadas para novo código desenvolvido (ou código que é novo para você):
- Valide que um
JNIEnv seja usado somente com o thread ao qual está associado.
- Valide que os Métodos JNI não sejam chamados na seção
ReleaseXXXCritical() do GetXXXCritical().
- Para um método que entra em uma seção crítica, valide que o método não retorne antes de ser liberado.
- Valide que haja uma verificação para uma exceção após todas as chamadas JNI que podem emitir uma exceção.
- Assegure que todas as chamadas
Get/Release sejam correspondidas em cada método JNI.
A implementação da JVM da IBM inclui uma opção que ativa verificações automáticas de JNI, a um custo de execução mais lenta. Em conjunto com bons testes de unidade para seu código, essa é uma ferramenta poderosa. É possível executar o aplicativo ou os testes de unidade uma vez para realizar uma verificação de conformidade ou quando são encontrados erros para os quais suspeita-se que nativos sejam a causa. Além de realizar as verificações de conformidade com as especificações listadas acima, a opção também assegura que:
- Os parâmetros passados a métodos JNI sejam de tipos corretos.
- O código JNI não leia além do final das arrays.
- Somente ponteiros válidos sejam passados aos métodos JNI.
Nem todas as descobertas dos relatórios de verificação de JNI são necessariamente erros no código. Elas incluem sugestões para código que deve ser revisado de perto para assegurar que faça o que foi pretendido.
A opção de verificação JNI é ativada com a seguinte linha de comando:
Usage: -Xcheck:jni:[option[,option[,...]]]
all check application and system classes
verbose trace certain JNI functions and activities
trace trace all JNI functions
nobounds do not perform bounds checking on strings and arrays
nonfatal do not exit when errors are detected
nowarn do not display warnings
noadvice do not display advice
novalist do not check for va_list reuse
valist check for va_list reuse
pedantic perform more thorough, but slower checks
help print this screen
|
Usar a opção -Xcheck:jni da IBM JVM como parte do processo padrão de desenvolvimento pode ajudar a localizar erros de codificação de forma muito mais fácil. Especificamente, pode ajudar a eliminar pela raiz as armadilhas de usar JNIEnv com o thread errado e usar regiões críticas incorretamente.
Sun JVMs recentes fornecem uma opção denominada de forma semelhante -Xcheck:jni. Ela opera de forma diferente da versão da IBM e fornece informações diferentes, mas a finalidade é a mesma. Emite avisos ao ver código que não está em conformidade com a especificação e pode ajudá-lo a localizar instâncias das armadilhas comuns de JNI.
Analisando o Rastreio de Método
Gerar um rastreio dos nativos que são chamados, juntamente com retornos de chamada JNI que esses nativos realizam, pode ser útil para eliminar pela raiz diversas armadilhas comuns. Problemas a serem observados incluem:
- Uma abundância de chamadas
GetFieldID() e
GetMethodID()— especialmente, se as chamadas forem para os mesmos campos e métodos — indica que os campos e métodos não estão sendo armazenados em cache.
- Instâncias de chamadas a
GetTypeArrayElements() em vez de a GetTypeArrayRegion() podem indicar cópias desnecessárias.
- Alternar rapidamente entre código Java e nativos (conforme indicado por registros de data e hora) pode indicar o limite errado entre o código Java e os nativos, levando a um desempenho fraco.
- O padrão em que cada chamada de uma função do nativo é seguida por diversas chamadas
GetFieldID() pode indicar que, em vez de passar os parâmetros necessários, você está forçando os nativos a buscar atrás os dados necessários para concluir seu trabalho.
- A falta de chamadas a
ExceptionOccurred() ou ExceptionCheck() após chamadas a métodos JNI que podem emitir exceções pode indicar que os nativos não estão verificando corretamente as exceções.
- Uma incompatibilidade entre o número de chamadas dos métodos
GetXXX() e ReleaseXXX() pode indicar ausência de liberações.
- Chamadas a métodos JNI entre as chamadas
GetXXXCritical() e ReleaseXXXCritical() indicam que os limitadores impostos pela especificação não estão sendo observados.
- Se o tempo decorrido entre as chamadas a
GetXXXCritical() e ReleaseXXXCritical() for longo, isso pode indicar que a restrição imposta pela especificação de não fazer chamadas de bloqueio não está sendo observada.
- Um grande desequilíbrio entre chamadas a
NewGlobalRef() e DeleteGlobalRef() pode indicar uma falha em liberar referências globais quando não são mais necessárias.
Algumas implementações de JVM fornecem um mecanismo através do qual um rastreio de método pode ser gerado. Também é possível gerar um rastreio através de ferramentas externas, como criadores de perfis e ferramentas de cobertura de código.
A implementação da IBM JVM fornece diversas maneiras de gerar informações de rastreio. A primeira é usar a opção -Xcheck:jni:trace. Isso gera um rastreio dos métodos nativos chamados, assim como os retornos de chamada JNI que realizam. A Lista 13 mostra um trecho de um rastreio (com algumas linhas divididas somente para capacidade de leitura):
Lista 13. Rastreio de Método Gerado pela Implementação da IBM JVM
Call JNI: java/lang/System.getPropertyList()[Ljava/lang/String; {
00177E00 Arguments: void
00177E00 FindClass("java/lang/String")
00177E00 FindClass("com/ibm/oti/util/Util")
00177E00 Call JNI: com/ibm/oti/vm/VM.useNativesImpl()Z {
00177E00 Arguments: void
00177E00 Return: (jboolean)false
00177E00 }
00177E00 Call JNI: java/security/AccessController.initializeInternal()V {
00177E00 Arguments: void
00177E00 FindClass("java/security/AccessController")
00177E00 GetStaticMethodID(java/security/AccessController, "doPrivileged",
"(Ljava/security/PrivilegedAction;)Ljava/lang/Object;")
00177E00 GetStaticMethodID(java/security/AccessController, "doPrivileged",
"(Ljava/security/PrivilegedExceptionAction;)Ljava/lang/Object;")
00177E00 GetStaticMethodID(java/security/AccessController, "doPrivileged",
"(Ljava/security/PrivilegedAction;Ljava/security/AccessControlContext;)
Ljava/lang/Object;")
00177E00 GetStaticMethodID(java/security/AccessController, "doPrivileged",
"(Ljava/security/PrivilegedExceptionAction;
Ljava/security/AccessControlContext;)Ljava/lang/Object;")
00177E00 Return: void
00177E00 }
00177E00 GetStaticMethodID(com/ibm/oti/util/Util, "toString",
"([BII)Ljava/lang/String;")
00177E00 NewByteArray((jsize)256)
00177E00 NewObjectArray((jsize)118, java/lang/String, (jobject)NULL)
00177E00 SetByteArrayRegion([B@0018F7D0, (jsize)0, (jsize)30, (void*)7FF2E1D4)
00177E00 CallStaticObjectMethod/CallStaticObjectMethodV(com/ibm/oti/util/Util,
toString([BII)Ljava/lang/String;, (va_list)0007D758) {
00177E00 Arguments: (jobject)0x0018F7D0, (jint)0, (jint)30
00177E00 Return: (jobject)0x0018F7C8
00177E00 }
00177E00 ExceptionCheck()
|
O trecho de rastreio na Lista 13 mostra o nativo sendo chamado (por exemplo, AccessController.initializeInternal()V) e, então, os retornos de chamada JNI feitos pelo nativo.
Usando a Opção -verbose:jni
As JVMs da Sun e da IBM também fornecem uma opção -verbose:jni. Para a JVM
da IBM, a ativação dessa opção fornece informações sobre quais retornos de chamada JNI estão sendo realizados. A Lista 14 mostra um exemplo:
Lista 14. Listando Retornos de Chamada JNI com a JVM da IBM -verbose:jni
<JNI GetStringCritical: buffer=0x100BD010>
<JNI ReleaseStringCritical: buffer=100BD010>
<JNI GetStringChars: buffer=0x03019C88>
<JNI ReleaseStringChars: buffer=03019C88>
<JNI FindClass: java/lang/String>
<JNI FindClass: java/io/WinNTFileSystem>
<JNI GetMethodID: java/io/WinNTFileSystem.<init> ()V>
<JNI GetStaticMethodID: com/ibm/j9/offload/tests/HelloWorld.main ([Ljava/lang/String;)V>
<JNI GetMethodID: java/lang/reflect/Method.getModifiers ()I>
<JNI FindClass: java/lang/String>
|
Para a Sun JVM, a ativação da opção -verbose:jni não fornece informações sobre as chamadas que estão sendo feitas, mas fornece informações adicionais sobre os nativos usados. A Lista 15 mostra um exemplo:
Lista 15. Usando a JVM da Sun -verbose:jni
[Dynamic-linking native method java.util.zip.ZipFile.getMethod ... JNI]
[Dynamic-linking native method java.util.zip.Inflater.initIDs ... JNI]
[Dynamic-linking native method java.util.zip.Inflater.init ... JNI]
[Dynamic-linking native method java.util.zip.Inflater.inflateBytes ... JNI]
[Dynamic-linking native method java.util.zip.ZipFile.read ... JNI]
[Dynamic-linking native method java.lang.Package.getSystemPackage0 ... JNI]
[Dynamic-linking native method java.util.zip.Inflater.reset ... JNI]
|
A ativação dessa opção também faz com que a JVM emita avisos quando muitas referências locais são usadas sem informar à JVM. Por exemplo, a IBM JVM gera mensagens como esta:
JVMJNCK065W Aviso de JNI em FindClass: capacidade do quadro de referência local cresceu automaticamente de 16 para 48.
|-------10--------20--------30--------40--------50--------60--------70--------80--------9|
|-------- Erro XML: A linha anterior não é maior do que o máximo de 90 caracteres ---------|
17 referências estão em uso.
Use EnsureLocalCapacity ou PushLocalFrame para aumentar o quadro explicitamente.
|
Apesar de as opções -verbose:jni e -Xcheck:jni:trace facilitarem a obtenção das informações necessárias, revisar essas informações manualmente pode exigir uma boa parcela de esforço. É uma boa ideia criar scripts ou utilitários que podem processar os arquivos de rastreio que estão sendo gerados por sua JVM e procurar os sinais de aviso.
Gerando Dumps
Dumps gerados a partir de um processo Java em execução contêm uma riqueza de informações sobre o estado de uma JVM.
Para muitas JVMs, eles incluem informações sobre referências globais. Por exemplo, as Sun JVMs recentes incluem a seguinte linha nas informações de dump:
Referências globais de JNI: 73
|
Ao gerar dumps anteriores e posteriores, é possível avaliar se estão sendo criadas referências globais que não estão sendo liberadas quando deveriam.
É possível solicitar um dump em ambientes UNIX® emitindo kill -3 ou kill -QUIT no processo java. No Windows®, use Ctrl+Break.
Para a IBM JVM, use estas etapas para obter informações sobre referências globais:
- Inclua
-Xdump:system:events=user na linha de comandos. Isso pede à JVM para gerar um dump do sistema quando kill -3 é chamado em uma variante no UNIX ou Ctrl+Break no Windows.
- Quando seu programa estiver em execução, gere dumps subsequentes.
- Execute
jextract -nozip core.XXX output.xml, que extrai informações de dump para um formato legível em output.xml.
- Procure entradas
JNIGlobalReference em output.xml, que fornecem informações sobre referências globais atuais, conforme mostrado na Lista 16:
Lista 16. Entradas JNIGlobalReference em output.xml
<rootobject type="Thread" id="0x10089990" reachability="strong" />
<rootobject type="Thread" id="0x10089fd0" reachability="strong" />
<rootobject type="JNIGlobalReference" id="0x100100c0" reachability="strong" />
<rootobject type="JNIGlobalReference" id="0x10011250" reachability="strong" />
<rootobject type="JNIGlobalReference" id="0x10011840" reachability="strong" />
<rootobject type="JNIGlobalReference" id="0x10011880" reachability="strong" />
<rootobject type="JNIGlobalReference" id="0x10010af8" reachability="strong" />
<rootobject type="JNIGlobalReference" id="0x10010360" reachability="strong" />
<rootobject type="JNIGlobalReference" id="0x10081f48" reachability="strong" />
<rootobject type="StringTable" id="0x10010be0" reachability="weak" />
<rootobject type="StringTable" id="0x10010c70" reachability="weak" />
<rootobject type="StringTable" id="0x10010d00" reachability="weak" />
<rootobject type="StringTable" id="0x10011018" reachability="weak" />
|
Observando os números relatados em dumps Java subsequentes, é possível avaliar se referências globais estão vazando.
Consulte Recursos para obter informações adicionais sobre como usar os arquivos de dump e jextract com a IBM JVM.
Executando Revisões de Código
As revisões de código podem, frequentemente, ser efetivas para identificar armadilhas comuns e podem ser realizadas em diversos níveis.
Ao herdar código novo, uma varredura rápida pode revelar problemas que levariam muito mais tempo para depurar posteriormente.
Em alguns casos, uma revisão é a única maneira de identificar uma instância de uma armadilha, como o código não verificar os valores de retorno.
Por exemplo, o problema com este código provavelmente seria fácil de identificar através de uma revisão de código, mas muito mais difícil através de depuração:
int calledALot(JNIEnv* env, jobject obj, jobject allValues){
jclass cls = (*env)->GetObjectClass(env,allValues);
jfieldID a = (*env)->GetFieldID(env, cls, "a", "I");
jfieldID b = (*env)->GetFieldID(env, cls, "b", "I");
jfieldID c = (*env)->GetFieldID(env, cls, "c", "I");
jfieldID d = (*env)->GetFieldID(env, cls, "d", "I");
jfieldID e = (*env)->GetFieldID(env, cls, "e", "I");
jfieldID f = (*env)->GetFieldID(env, cls, "f", "I");
}
jclass getObjectClassHelper(jobject object){
/* use globally cached JNIEnv */
return cls = (*globalEnvStatic)->GetObjectClass(globalEnvStatic,allValues);
}
|
Uma revisão de código provavelmente identificaria que o primeiro método não está armazenando em cache corretamente os IDs de campos, mesmo que os mesmos IDs sejam usados repetidamente, e que o segundo método está usando um JNIEnv em threads diferentes daquele no qual o JNIEnv deve ser usado.
Conclusão
Agora, você está ciente das 10 principais armadilhas de programação JNI e aprendeu algumas boas práticas para identificá-las em código existente ou novo. Aplique essas práticas de forma diligente para aumentar a probabilidade de seu código JNI estar correto e de seu aplicativo poder atingir os níveis de desempenho necessários.
A capacidade de integrar ativos de código existente de forma eficiente é essencial para o sucesso com duas tecnologias que estão ganhando notoriedade: Arquitetura Orientada a Serviços (SOA) e computação baseada em nuvem. JNI é uma tecnologia chave para integrar o código e componentes legados não Java a uma plataforma baseada em Java como um bloco de construção para um sistema baseado em SOA ou em nuvem. O uso apropriado de JNI pode acelerar o processo de ativação por serviço desses componentes e permitir tirar o maior proveito de investimentos existentes.
Recursos Aprender
Obter produtos e tecnologias
Discutir
Sobre os autores  | 
|  | Michael Dawson formou-se pela University of Waterloo em 1989 como bacharel em engenharia de computação e, em 1991, na Queens University, obteve seu mestrado em engenharia elétrica, especializando-se em criptografia. Depois disso, passou diversos anos como consultor em segurança, desenvolvendo produtos em empresas que iam de empresas novas até a IBM. Teve funções de liderança em equipes de desenvolvimento de aplicativos de e-commerce e na entrega dos mesmos como serviços, incluindo serviços de comunicação EDI, processamento de cartão de crédito, leilões on-line, faturamento eletrônico e JVMs. As tecnologias usadas variavam de C/C++ a Java e plataformas e componentes Java EE através de uma variedade de sistemas operacionais. Michael entrou na IBM em 2006. Ele trabalha na equipe J9 JVM que implementa bibliotecas de classe Java e componentes principais de JVM. |
 | 
|  | Graeme Johnson é um gerente de desenvolvimento e líder técnico da equipe J9 Virtual Machine da IBM. Ele desenvolve máquinas virtuais e depuradores desde que entrou na IBM (anteriormente a Object Technology International) em 1994 e trabalhou nos tempos de execução do VisualAge para Java e do IBM/OTI Smalltalk. Recentemente, Graeme está trabalhando no projeto Apache Harmony e no suporte ao tempo de execução Java/PHP para a IBM Project Zero. Graeme é um palestrante regular em conferências sobre diversos tópicos, incluindo: Apache Harmony na JavaOne 2006, desenvolvimento com C em multiplataforma na EclipseCon 2007 e um exame do tempo de execução PHP na International PHP 2006. |
 | 
|  | Andrew Low entrou na Object Technology International Inc. (OTI) em 1994 como formado na universidade após ter trabalhado como um aluno estagiário por diversos semestres. Ele permaneceu na empresa quando a IBM adquiriu a OTI em 1996. Seu trabalho sempre esteve associado à tecnologia de VM, desde as muito pequenas (telefones celulares e PDAs) até as muito grandes (sistemas de mainframe IBM zSeries). Atualmente, é líder técnico da equipe J9 VM no laboratório da IBM em Ottawa, ajudando a formar a tecnologia principal por trás dos tempos de execução Java da IBM. Andrew teve uma função chave no desenvolvimento da J9 VM e é um especialista reconhecido em sistemas embarcados e no segmento de mercado de Java ME. Recentemente, está envolvido em diversos esforços estratégicos para colocar a tecnologia de tempo de execução Java no mundo da Web 2.0. |
Avalie esta página
|  |