IBM i 上如何通过 JNI 创建和使用 J9 JVM

本文首先简单介绍了 IBM i 上 JNI 的基本概念及其两大组成部分,接着介绍了常用的一些用于创建和初始化 JVM 的 JNI 函数,并在此基础上分析了非 Java 程序调用 Java 程序的具体过程。最后,结合具体的示例程序重点讲解了如何在 C、RPG,以及 COBOL 语言中创建和使用 JVM 及常见的一些问题。

高 丽, 软件工程师, IBM

高丽,来自 IBM 中国系统与科技研发中心 IBM i J9 团队,目前主要负责 IBM i 上 JVM 的开发工作。



张 干, IBM i J9 Team Lead, IBM

张干,来自IBM 中国系统与科技研发中心 IBM i J9 团队,具有多年的 C/C++/Java 从业经验,目前主要负责 IBM i 上 J9 VM的开发和维护工作。



张 大伟, 软件工程师, IBM

张大伟,软件工程师。2009 至 2012 年负责在 IBM i 上 J9 VM 的开发和技术支持工作。同时在电信业务,移动 IP 与类 Unix 平台开发方面有较多经验。



2013 年 7 月 18 日

引言

随着计算机技术的不断发展,多种编程语言之间的相互调用变得越来越常见,如 C/C++、RPG、COBOL, 以及 Java 语言之间的相互调用。JNI(Java Native Interface,也称 java 本机接口)是一系列标准的 Java API。它支持 Java 语言和其它编程语言之间的相互调用。但是,JNI 是一把双刃剑,为开发人员带来极大的便利的同时也引入了潜在的威胁。如果使用不当,就会导致应用程序性能降低甚至整个应用程序都会崩溃。因此,如何正确有效的使用 JNI 技术变得越来越重要。

IBM i 将 Java 环境和 ILE(Integrated language environment)环境分离开来,java 不属于一种 ILE 语言,它不能被 bind 到 ILE 对象模型里去创建 program 或者 service program。要实现 java 和其他 ILE 语言之间的相互调用,就需要使用 JNI 提供的接口函数。

JNI 主要包含两大部分:Native methods 和 Java Invocation API。本文将重点介绍后者。

Native methods 是指通过非 java 语言实现的 java method,native method 可以访问一些系统级别的函数和 API,而这些函数是不能在 java 中直接调用的。但是,调用这些 API 会降低 Java 应用程序的可移植性。

Java Invocation API 提供了一系列接口函数,允许非 Java 代码创建和初始化 JVM\、加载 Class、调用 Java method 和销毁 JVM 等。Java 提供的这些接口函数可以使非 Java 多线程程序充分利用同一个 JVM 中的 java classes。

IBM i 上 Java Invocation API 主要提供给两类调用者:

  • ILE program 或者 service program, 这些程序需要支持 teraspace storage 模式。
  • IBM i PASE 可执行程序。

Java Invocation API

非 Java 程序要调用 Java 程序,必须首先创建 JVM,然后才能由 JVM 解释执行 class 文件。JNI 提供了一系列的接口函数,通过这些函数可以方便地创建和初始化 JVM。下面简单介绍常见的一些 Java 调用接口函数。

  1. 获得当前 job 已经创建的所有的 JVM
    1. 函数: jint JNI_GetCreatedJavaVMs(JavaVM **vmBuf,jsize bufLen,jsize *nVMs);
    2. 参数说明:vmBuf 是一个输出参数,存储指向 JavaVM 结构的指针,长度由参数 bugLen 决定。每一个 JVM 都有一个对应的 JavaVM 结构。nVMs 返回已经创建的 JVM 的数量,但 IBM i 上同一个 Job 只允许存在一个 JVM,因此 nVMs 的最大值是 1.
  2. 创建和初始化 JVM
    1. 函数: jint JNI_CreateJavaVM(JavaVM **p_vm,void **p_env,void *vm_args);
    2. 参数说明:p_vm 新创建的 JVM 指针,指向结构体 JavaVM, 其它的 JNI invocation API 会使用这个变量去识别 JVM。 p_env 新创建的 JVM 的 JNI 环境变量指针,它指向一个 JNI 函数表,贯穿于整个调用过程中,JNI 函数表中的函数调用都需要这个参数。vm_args 是一个结构体,包含 JVM 初始化的一些参数。当然,JVM 的参数有多种不同设置方式,本文不做具体介绍,更多相关信息,请参考(DW link)
    3. 注意: IBM i 上,同一个 job 或 process 里,Java 只允许同时存在一个 JVM。
  3. 销毁 JVM
    1. 函数: jint DestroyJavaVM(JavaVM *vm)
    2. 参数说明:vm 是指向要销毁的 JVM。
    3. 注意:在确认这个 JVM 已经不再被任何线程使用的情况下再去销毁 JVM。当 Job 结束的时候,JVM 会自动销毁,同时释放占用的资源并回收内存。
  4. Attach 已经存在的 JVM
    1. 函数: jint AttachCurrentThread(JavaVM *vm,void **p_env,void *thr_args);
    2. 参数说明:vm 是表示某个 JVM 的 JavaVM 指针,它指向当前 thread 要 attach 到哪一个 JVM。p_env 当前 thread 的 JNI 环境指针。thr_args 包含 thread 特有的参数信息。
  5. Detach 已经 attach 的 JVM
    1. 函数: jint DetachCurrentThread(JavaVM *vm);
    2. 参数说明: vm 是指向要 detach 的 JVM。

非 Java 程序如何创建和使用 JVM

在了解 Java 调用接口的基础上,要正确使用这些接口函数,还需要弄清楚非 Java 程序调用 Java 程序的基本步骤。下面是一个简单的步骤:

  1. 通过 JNI_CreateJavaVM() 创建 JVM 或者通过 AttachCurrentThread() attach 到已经存在的 JVM 上。
  2. 通过 JVM 找到想要运行的类 (class)。
  3. 找到类 (class) 中要调用函数的 methodID。
  4. 通过 methodID 调用这个函数。

在非 Java 程序中创建 JVM 有两种方法,一种是通过 JNI 调用来创建 JVM,另一种是由编程语言运行时环境帮助创建 JVM。当用户调用 java method 的时候,运行时环境会自动检测当前 job 有没有已经创建的 JVM,如果没有则调用 JNI_CreateJavaVM() 来创建 JVM,如果有则直接 attach 上去。RPG 语言运行时环境就提供了这种支持(详见"RPG call java"小节)。

IBM i 上,同一个 Job 只允许同时存在一个 JVM,因此 JNI_CreateJavaVM() 在同一个 job 中只能被成功的调用一次。如果当用户的应用程序需要调用 Java 方法的时候,当前 Job 没有已经创建的 JVM,那么需要显示(用户自己调用)或隐式(通过不同语言的 Runtime )的调用 JNI_CreateJavaVM() 来启动 JVM,这时当前 job 已经存在一个 JVM,如果其它的线程还需要调用 Java 方法,就不能再重新创建 JVM,只能调用 AttachCurrentThread() 去 attach 到当前 job 中的 JVM 上即可继续调用 Java 程序。

注意: 当用户创建 *PGM 或 *SRVPGM 的时候,QJVAJNI *SRVPGM 提供了 API 函数 JNI_CreateJavaVM() 的接口,该函数负责创建 JVM。 但是,QJVAJNI 没有初始化 API 函数 AttachCurrentThread() ,因此,像调用 JNI_CreateJavaVM() 一样调用 AttachCurrentThread() 会出错,这是通过 JNI 调用方式创建 JVM 时经常出现的一个问题。AttachCurrentThread() 函数需要通过 JavaVM 结构指针来调用,该结构存放在 QSYSINC 的 JNI 头文件里。下一小节会给出正确的调用示例。


RPG,C 和 COBOL 调用 java

在前面理论介绍和分析的基础上,下面分别简单介绍几种不同的 ILE 语言(如 RPG、COBOL 和 C)调用 Java 的示例。

RPG call java

如果当你的 RPG 程序已经准备好要调用 java method 的时候,当前 job 没有已经创建的 JVM,RPG 运行时环境会自动为你创建和初始化 JVM。RPG 运行时环境在创建 JVM 的时候,会使用默认的 classpath 和通过环境变量 CLASSPATH 设置的 classpath。当 RPG 启动 JVM 的时候,还可以通过下面几种方式设置一些 option 来控制 JVM 如何启动。

  • 通过 SystemDefault.properties 文件来设置 Java 选项。
  • 通过环境变量 CLASSPATH 设置 classpath。
  • 通过环境变量 QIBM_RPG_JAVA_PROPERTIES 来设置 Java 选项。通过这个环境变量设置的 Java 选项的优先级高于 SystemDefault.properties 的设置方式。

注意: 如果你设置了非法的 Java 选项,JVM 会拒绝这些选项的设置,同时 joblog 中会显示哪些 Java 选项是非法的,RPG 会尝试重新启动 JVM,但除了通过环境变量 CLASSPATH 设置的 java.class.path 选项外不再设置任何 Java 选项。更多关于 Java 属性设置的问题,请参考 灵活设置IBM i Java 的系统属性

下面示例是一段正确调用 JNI_CreateJavaVM(), AttachCurrentThread() 和 DeleteLocalRef() 的例子,粗体标注的是需要注意的地方。从这个例子中我们可以看出,在调用 AttachCurrentThread() 之前需要设置 Java 基础指针 (basing pointer) JavaVM_P,调用 JNI 函数,如 deleteLocalRef() 之前需要设置 JNI 环境指针 (JNI environment pointer) JNIEnv_P。

清单 1. RPG 调用 Java 示例
      **************************************************************** 
      * GetJavaEnvPtr – Get java environment pointer 
      **************************************************************** 
     P GetJavaEnvPtr... 
     P                 B                   Export 
     D GetJavaEnvPtr   PI              * 
     D RtnCode         S             10I 0 
     D JVMPtr          S               *   Dim( 1 ) 
     D EnvPtr          S               * 
     D BufferLength    S             10I 0 Inz( %Elem( JVMPtr ) ) 
     D NbrJVMs         S             10I 0 
     D JDKInitArgs     DS                  LikeDS(JavaVMInitArgs) 
     D JDKAttachArgs   DS                  LIKEDS(JavaVMAttachArgs) 
      /Free 
          RtnCode = JNI_GetCreatedJavaVMs(JVMPtr : BufferLength : NbrJVMs); 
          If (RtnCode = *Zero And NbrJVMs > *Zero); 
              JDKAttachArgs = *AllX'00'; 
              JDKAttachArgs.version = JNI_VERSION_1_2; 
              JavaVM_P = jvmPtr(1); 
              RtnCode = AttachCurrentThread(JVMPtr(1) : EnvPtr :%Addr(JDKAttachArgs)); 
          Else ; 
             JDKInitArgs = *AllX'00' ; 
             JDKInitArgs.version = JNI_VERSION_1_2; 
             RtnCode = JNI_GetDefaultJavaVMInitArgs(%Addr(JDKInitArgs)); 
             If (RtnCode = *Zero); 
                  RtnCode = JNI_CreateJavaVM(JVMPtr(1) : EnvPtr : %Addr(JDKInitArgs)); 
             EndIf ; 
          EndIf ; 
          If (RtnCode = *Zero); 
              Return EnvPtr; 
          Else; 
              Return *Null; 
          EndIf; 
      /End-Free 
     P GetJavaEnvPtr   E 

      **************************************************************** 
      * ReleaseJavaObject – Release java object by calling Jni 
 function     DeleteLocalRef 
      **************************************************************** 
     P ReleaseJavaObject... 
     P                 B                   Export 
     D ReleaseJavaObject... 
     D                 PI 
     D    Object                       O   Class(*Java : 
     D                                     'java.lang.Object') 
     D                                     Value 
     D EnvPtr          S               *   Inz(*Null) 
     D                                     Static 
      /Free 
         If (Object = *Null); 
            Return; 
         Endif; 
         If (EnvPtr = *Null); 
            EnvPtr = GetJavaEnvPtr(); 
         Endif; 
         JNIEnv_P = EnvPtr; 
         DeleteLocalRef(EnvPtr : Object); 
      /End-Free 
     P ReleaseJavaObject... 
     P                 E

常见问题: 在调用 QSYSINC 中提供的 JNI 函数之前没有正确设置基础指针 JavaVM_P 和 JNI 环境指针 JNIEnv_P,导致调用失败。这种问题经常出现在从 Classic JVM 切换到 J9 JVM 时。因为,在没有设置 Java 基础指针 JavaVM_P 时,直接调用 AttachCurrentThread() 函数,实际上调用的是 QJVAJNI *SRVPGM 中提供的 JNI 函数,但是 J9 JVM 没有初始化 QJVAJNI service program 提供的函数 AttachCurrentThread(),需要调用 QSYSINC 下 jni 头文件里定义的结构体 JavaVM 中指定的成员函数 AttachCurrentThread()。

C call java

C 程序要调用 Java 程序,必须先加载 JVM,然后由 JVM 解释执行 class 文件。在 C 程序中,我们一般通过结构体指针 JavaVM 来调用 AttachCurrentThread(),结构体 JavaVM 中的 AttachCurrentThread 指针域是通过调用 JNI_CreateJavaVM() 来填充的,具体调用形式如下两句所示,这样指向 AttachCurrentThread() 的函数指针就被正确赋值了,当用户通过这个接口调用 AttachCurrentThread() 时,可以正确执行。

JNI_CreateJavaVM(&myJVM, (void **)&myEnv, (void *)&initArgs);

(*myJVM)->AttachCurrentThread(myJVM,(void **)&myEnv, &args);

下面是一段调用 JNI_CreateJavaVM() 的示例程序。

清单 2. C 调用 Java 示例
 int main() 
 { 
 JavaVMOption options[1]; 
 JNIEnv *env; 
 JavaVM *jvm; 
 JavaVMInitArgs vm_args; 
 long status; 
 jclass cls; 
 jmethodID mid; 
 jint square; 
 jboolean not; 

 options[0].optionString = "-Djava.class.path=."; 
 memset(&vm_args, 0, sizeof(vm_args)); 
 vm_args.version = JNI_VERSION_1_2; 
 vm_args.nOptions = 1; 
 vm_args.options = options; 
 status = JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args); 

 if (status != JNI_ERR) 
 { 
 cls = (*env)->FindClass(env, "Sample2"); 
 if(cls !=0) 
 { mid = (*env)->GetStaticMethodID(env, cls, "intMethod", "(I)I"); 
 if(mid !=0) 
 { square = (*env)->CallStaticIntMethod(env, cls, mid, 5); 
 printf("Result of intMethod: %d\n", square); 
 } 

    mid = (*env)->GetStaticMethodID(env, cls, "booleanMethod", "(Z)Z"); 
 if(mid !=0) 
 { not = (*env)->CallStaticBooleanMethod(env, cls, mid, 1); 
 printf("Result of booleanMethod: %d\n", not); 
 } 
 } 
 (*jvm)->DestroyJavaVM(jvm); 
 return 0; 
 } 
 else 
 return -1; 
 }

COBOL call java

下面示例 3 是一段 COBOL 调用 Java 的示例程序,粗体是需要注意的地方。从这个例子中我们可以看出, JNI_GetCreatedJavaVMs() 和 JNI_CreateJavaVM() 是直接调用 C 提供的函数,而 AttachCurrentThread() 调用 COBOL 自定义的函数指针 ATTACH-CURRENT-THREAD。而 ATTACH-CURRENT-THREAD 是通过"SET ADDRESS OF JNI-INVOKE-INTERFACE TO JAVA-VM-PTR"赋值的。要获得 AttachCurrentThread() 函数指针,不能简单指向通过 JNI_GetCreatedJavaVMs() 或 JNI_CreateJavaVM() 获得的 JVM 结构指针,因为它是一个指针的指针,而不是一个结构体的指针,所以我们需要通过"SET ADDRESS OF JAVA-VM-PTR TO JVM-PTR"获得 JAVA-VM-PTR,然后通过"SET ADDRESS OF JNI-INVOKE-INTERFACE TO JAVA-VM-PTR"来获得更深一层的结构地址。GET-ENV 用来获得 JNI 环境指针,因为之后的很多 JNI 函数的调用都需要这个函数返回的 JNI 环境指针。

清单 3. Cobol 调用 Java 示例
       DATA DIVISION. 
       FILE SECTION. 
       WORKING-STORAGE SECTION. 
       01 VM-INIT-ARGS. 
          05 VERSION PIC S9(9) BINARY VALUE 65538. 
          05 NUMBER-OF-OPTIONS PIC S9(9) BINARY. 
          05 OPTIONS-PTR USAGE POINTER. 
          05 FILLER PIC X(1). 

       01 VM-OPTIONS. 
          05 OPTIONS-STRING-PTR USAGE POINTER. 
          05 EXTRA-INFO-PTR USAGE POINTER. 

       01 JNI-VERSION CONSTANT as x'00010002'. 
       01 THR-ARGS. 
         05 VERSION PIC S9(9) BINARY VALUE 65538. 
         05 CHR-VERSION REDEFINES VERSION PIC X(4). 
         05 FILLER PIC X(12) VALUE X'000000000000000000000000'. 
         05 NAME-PTR USAGE POINTER VALUE NULL. 
         05 GROUP-1 PIC S9(9) BINARY VALUE 0. 
         05 FILLER PIC X(12) VALUE X'000000000000000000000000'. 

       01 JVM-PTR USAGE POINTER. 
       01 ENV-PTR USAGE POINTER. 
       
       01 RC1 PIC S9(9) BINARY VALUE 1. 
       01 RC2 PIC S9(9) BINARY VALUE 1. 
       01 RC3 PIC S9(9) BINARY VALUE 1. 

       01 CLASSPATH PIC X(500). 
       01 BUFLEN PIC S9(9) BINARY VALUE 0. 
       01 NVMS PIC S9(9) BINARY. 


       LINKAGE SECTION. 
       COPY JNI. 
       01 JNI-INVOKE-INTERFACE. 
          05 FILLER                           USAGE POINTER. 
          05 FILLER                           USAGE POINTER. 
          05 FILLER                           USAGE POINTER. 
          05 DESTROY-JAVA-VM                  USAGE PROCEDURE-POINTER. 
          05 ATTACH-CURRENT-THREAD            USAGE PROCEDURE-POINTER. 
          05 DETACH-CURRENT-THREAD            USAGE PROCEDURE-POINTER. 
          05 GET-ENV                          USAGE PROCEDURE-POINTER. 
       01 JAVA-VM-PTR USAGE POINTER. 
       01 INTERFACE-PTR USAGE POINTER. 

       PROCEDURE DIVISION. 
       MAIN-LINE SECTION. 
       MAIN-PROGRAM-LOGIC. 
  
           STRING FUNCTION UTF8STRING("-Djava.class.path=/home/test") 
            DELIMITED BY SIZE 
           X"00" DELIMITED BY SIZE 
           INTO CLASSPATH 

           SET OPTIONS-STRING-PTR TO ADDRESS OF CLASSPATH. 
           MOVE 1 TO NUMBER-OF-OPTIONS. 
           SET OPTIONS-PTR TO ADDRESS OF VM-OPTIONS. 

           MOVE 1 TO BUFLEN. 
           CALL PROCEDURE "JNI_GetCreatedJavaVMs"
           USING BY REFERENCE JVM-PTR 
                 BY VALUE BUFLEN 
                 BY REFERENCE NVMS 
           RETURNING INTO RC2. 

           IF NVMS > 0 
              SET ADDRESS OF JAVA-VM-PTR TO JVM-PTR 
              SET ADDRESS OF JNI-INVOKE-INTERFACE TO JAVA-VM-PTR 
              CALL GET-ENV 
                 USING BY VALUE JVM-PTR 
                       BY REFERENCE ENV-PTR 
                       BY VALUE VERSION OF VM-INIT-ARGS 
              RETURNING INTO RC3 
              IF RC3 NOT = 0 
                 GOBACK 
              END-IF   
              MOVE All x'00' TO THR-ARGS 
              MOVE JNI-VERSION to CHR-VERSION 
              CALL ATTACH-CURRENT-THREAD 
                 USING BY VALUE JVM-PTR 
                       BY REFERENCE ENV-PTR 
                       BY REFERENCE THR-ARGS 
              RETURNING INTO RC3 
              IF RC3 NOT = 0 
                 GOBACK 
              END-IF 
           ELSE 
              CALL PROCEDURE "JNI_CreateJavaVM"
              USING JVM-PTR 
                    ENV-PTR 
                    VM-INIT-ARGS 
              RETURNING INTO RC1 
           END-IF. 

           SET ADDRESS OF INTERFACE-PTR TO ENV-PTR. 
           SET ADDRESS OF JNI-NATIVE-INTERFACE TO INTERFACE-PTR.

结束语

本文简单的介绍了 IBM i 上 JNI 的概念及如何正确实现非 Java 程序对 Java 程序的调用。同时,给出了 C、RPG,以及 COBOL 调用 Java 的具体示例程序,希望能对 IBM i Java 开发人员带来帮助。JNI 是一个看似简单,实则非常复杂的话题。本文只是简单介绍了其中一小部分,要熟练掌握 JNI,还需要更进一步深入的学习和研究。

参考资料

学习

讨论

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=IBM i
ArticleID=937636
ArticleTitle=IBM i 上如何通过 JNI 创建和使用 J9 JVM
publish-date=07182013