参照

参照とポインタは同じ概念のはずだが,JNIでは明確に区別する必要がある.
Javaにはポインタがない
JNIで何を考慮すべきか
ローカル参照
グローバル参照
参照についてのまとめ

Javaにはポインタがない

「JavaにはポインタがないからCより簡単だ」これはJavaの入門書などでよくみかける.確かにJavaにはポインタと呼ばれるものはないが,参照があるので同じことだ.まともなプログラミング言語ならポインタに相当するものは大抵あるものだ.ないとなるとその言語の表現力に大きく影響してしまう.で,Cにおけるポインタとは何か.「オブジェクトを指し示すもの」である.多くの場合それはアドレスで実装されるが,別にC言語の仕様ではない(ハズ)が,ここではアドレスで実装するものと考える.Javaにおける参照とは何か.これもやはり「オブジェクトを指し示すもの」である.実装についての仕様はよくわからないが,以下を踏まえておく必要がある.
JVMはGCでオブジェクトを削除する.
JVMはGCでオブジェクトを再配置する.
オブジェクトのアドレスがJVMによって動的に変化するから,参照は直接,または短絡的にアドレスで実装しいるわけではないと思う.オブジェクトへの参照と実際のアドレスはJVMによって管理される.ポインタと参照にはこのような違いがあるが,ポインタと参照は同じ「オブジェクトを指し示すもの」であるので,プログラマから見ればC,Javaそれぞれ単独でコーディングする上では両者の違いはほとんどない(「ポインタ演算」相当の「参照演算」なんてものはないが).が,JNIで両者が出会うとき,ポインタと参照の違いは明確に意識しておく必要がある.

JNIで何を考慮すべきか

ポインタと参照の違いは簡単に言うと,「プログラマが責任をもって管理するか,JVMがいろいろと操作をするか」である.そしてこの違いはCとJavaが出会うJNIのネイティブコードでは特に意識する必要がある.ネイティブコードはCもしくはC++である.いうまでもなく,ポインタは従来どおりのポインタとして扱える.問題は参照である.概念的にはポインタと参照は同じだが,参照はJVMにより管理,操作される.したがって,たとえ参照がポインタと同じように見えてもプログラマが好き勝手に保持,操作してはいけない.

ローカル参照

JVMはネイティブコードに渡した参照に関する情報をテーブルに保持する.これにより,JVMは「ネイティブコードでこの参照が使用されているからGCのときに注意しなければならない(削除や移動をしてはいけない)」ことがわかる.このテーブルに保持される参照にはネイティブメソッドの引数にある全ての参照と返り値のための参照が含まれる.このテーブルに含まれる参照はローカル参照と呼ばれる.ローカル参照はそのネイティブメソッドの呼び出しの間でだけ有効である.すなわちネイティブコードのグローバル変数で参照を保持しておいて,複数のネイティブメソッドで使用することはできない.ネイティブメソッドの呼び出しが異なれば,テーブルに保持されている参照は無効になる.したがって,別のネイティブメソッドでその参照を使用すると結果は保証されない.やってはいけない例を以下に示す.まず不正なネイティブメソッドを呼び出すJavaクラス.

    static{
	System.loadLibrary("InvalidLocalReference");
    }

    /* �������Ϥ��줿���֥������ȤΥϥå��女���ɤ�ɽ������ */
    static native void methodA(InvalidLocalReference obj);

    /* methodA���Ϥ������֥������Ȥؤλ��Ȥ�ͥ��ƥ��֥����ɤ��ݻ���,
       ���Υϥå��女���ɤ�ɽ������ */
    static native void methodB();

    public static void main(String args[]){
	InvalidLocalReference obj = new InvalidLocalReference();
	System.err.println("call methodA");
	methodA(obj);
	System.err.println("call methodB");
	methodB();
    }
}

上のクラスファイルから生成されるヘッダ.

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class InvalidLocalReference */

#ifndef _Included_InvalidLocalReference
#define _Included_InvalidLocalReference
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     InvalidLocalReference
 * Method:    methodA
 * Signature: (LInvalidLocalReference;)V
 */
JNIEXPORT void JNICALL Java_InvalidLocalReference_methodA
  (JNIEnv *, jclass, jobject);

/*
 * Class:     InvalidLocalReference
 * Method:    methodB
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_InvalidLocalReference_methodB
  (JNIEnv *, jclass);

#ifdef __cplusplus
}
#endif
#endif

そして不正なネイティブコード.

#include "InvalidLocalReference.h"

/* �����ʻ��Ȥ��ݻ����� */
jobject invalidLocalReference;

JNIEXPORT void JNICALL Java_InvalidLocalReference_methodA
(JNIEnv *env, jclass thisClazz, jobject obj){
  jclass clazz;
  jmethodID mid;
  jint hashCode;

  clazz = (*env)->GetObjectClass(env, obj);
  mid = (*env)->GetMethodID(env, clazz, "hashCode", "()I");
  hashCode = (*env)->CallIntMethod(env, obj, mid);
  fprintf(stderr, "MethodA : hasoCode = %ld\n", hashCode);
  /* ���Ȥ򥰥����Х��ѿ����ݻ�.���ƤϤ����ʤ� */
  invalidLocalReference = obj;
}

JNIEXPORT void JNICALL Java_InvalidLocalReference_methodB
(JNIEnv *env, jclass thisClazz){
  jclass clazz;
  jmethodID mid;
  jint hashCode;

  fprintf(stderr, "Now, access to invalid reference\n");
  /* �������Х��ѿ����ݻ����Ƥ��������ʻ��Ȥ˥����������� */
  clazz = (*env)->GetObjectClass(env, invalidLocalReference);
  mid = (*env)->GetMethodID(env, clazz, "hashCode", "()I");
  hashCode = (*env)->CallIntMethod(env, invalidLocalReference, mid);
  fprintf(stderr, "MethodB : hasoCode = %ld\n", hashCode);
}

上のネイティブコードでは,methodAでオブジェクトへの参照である引数objをグローバル変数invalidLocalReferenceに格納してmethodBでそれを使用している.これを実行すると私の環境では以下のようになった.

call methodA
MethodA : hasoCode = 8026323
call methodB
Now, access to invalid reference

Unexpected Signal : 11 occurred at PC=0x4013c75b
Function name=(N/A)
Library=/usr/lib/j2re1.3/lib/i386/client/libjvm.so

...省略...

Local Time = Mon Jun  2 02:18:34 2003
Elapsed Time = 0
#
# HotSpot Virtual Machine Error : 11
# Error ID : 4F530E43505002CC
# Please report this error at
# http://java.sun.com/cgi-bin/bugreport.cgi
#
# Java VM: Java HotSpot(TM) Client VM (Blackdown-1.3.1-FCS mixed mode)
#
# An error report file has been saved as hs_err_pid3952.log.
# Please refer to the file for further information.
#
アボートしました

なんかアボートした.黙ってそのまま実行されるよりはましだ.

グローバル参照

先のサンプルは,ネイティブコードにおいてローカル参照をグローバル変数に格納する間違った例であった.しかし,JNIでは参照をネイティブコードでグローバル変数に格納する手段を提供されている.このとき,その参照はグローバル参照と呼ばれる.具体的な方法は,JVMにあるローカル参照をネイティブコードにおいてグローバルに参照する(グローバル参照とする)ことを伝えるものである.それはJNIEnvの関数であり,jni.hでは以下のように定義されている.
jobject (JNICALL *NewGlobalRef)
(JNIEnv *env, jobject lobj);
引数にlobjとあるのはローカル参照ということだろう.そして返り値をグローバル参照としてグローバル変数に格納すればよい.この関数を使用して先の間違ったサンプルを修正してみた.まず,グローバル参照を使用するネイティブコードを呼び出すJavaコード.

public class GlobalReference{
    static{
	System.loadLibrary("GlobalReference");
    }

    /* �������Ϥ��줿���֥������ȤΥϥå��女���ɤ�ɽ������ */
    static native void methodA(GlobalReference obj);

    /* methodA���Ϥ������֥������Ȥؤλ��Ȥ�ͥ��ƥ��֥����ɤ��ݻ���,
       ���Υϥå��女���ɤ�ɽ������ */
    static native void methodB();

    public static void main(String args[]){
	GlobalReference obj = new GlobalReference();
	System.err.println("call methodA");
	methodA(obj);
	System.err.println("call methodB");
	methodB();
    }
}

そして上のJavaコードから生成されるヘッダ.

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class GlobalReference */

#ifndef _Included_GlobalReference
#define _Included_GlobalReference
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     GlobalReference
 * Method:    methodA
 * Signature: (LGlobalReference;)V
 */
JNIEXPORT void JNICALL Java_GlobalReference_methodA
  (JNIEnv *, jclass, jobject);

/*
 * Class:     GlobalReference
 * Method:    methodB
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_GlobalReference_methodB
  (JNIEnv *, jclass);

#ifdef __cplusplus
}
#endif
#endif

そしてグローバル参照を使用するネイティブコード.

#include "GlobalReference.h"

/* �������Х뻲�Ȥ��ݻ����� */
jobject globalReference;

JNIEXPORT void JNICALL Java_GlobalReference_methodA
(JNIEnv *env, jclass thisClazz, jobject obj){
  jclass clazz;
  jmethodID mid;
  jint hashCode;

  clazz = (*env)->GetObjectClass(env, obj);
  mid = (*env)->GetMethodID(env, clazz, "hashCode", "()I");
  hashCode = (*env)->CallIntMethod(env, obj, mid);
  fprintf(stderr, "MethodA : hasoCode = %ld\n", hashCode);
  /* �������뻲�Ȥ�����˥������Х뻲�Ȥ���� */
  globalReference = (*env)->NewGlobalRef(env, obj);
}

JNIEXPORT void JNICALL Java_GlobalReference_methodB
(JNIEnv *env, jclass thisClazz){
  jclass clazz;
  jmethodID mid;
  jint hashCode;

  fprintf(stderr, "Now, access to globalReference\n");
  /* �������Х뻲�Ȥ˥������� */
  clazz = (*env)->GetObjectClass(env, globalReference);
  mid = (*env)->GetMethodID(env, clazz, "hashCode", "()I");
  hashCode = (*env)->CallIntMethod(env, globalReference, mid);
  fprintf(stderr, "MethodB : hasoCode = %ld\n", hashCode);

  /* �������Х뻲�Ȥ������Ƥ��饢���������Ƥߤ� */
  fprintf(stderr, "Now, call DeleteGlobalRef and access to globalReference\n");
  (*env)->DeleteGlobalRef(env, globalReference);
  /* �����¹Ԥ��������֤��Ե����褤�� */
  /* globalReference = NULL; */
  clazz = (*env)->GetObjectClass(env, globalReference);
  mid = (*env)->GetMethodID(env, clazz, "hashCode", "()I");
  hashCode = (*env)->CallIntMethod(env, globalReference, mid);
  fprintf(stderr, "MethodB : hasoCode = %ld\n", hashCode);
}

This article was written by Fujiko