Linux下开发JNI程序

  随着近几年分布式程序的发展,Java在该领域扮演着越来越重要的角色,特别是Hadoop生态圈的兴起,更是让Java成为很多互联网公司主要的开发语言。但是对于Java而言,虽然我们在构建高层应用时优势得天独厚,但是在底层资源的控制方面却远不及C/C++,特别是Linux在服务器市场处于决定性的领先地位。为了使Java分布式平台能够更好的结合底层资源,很多互联网公司都采取了“高+低”的方式,即“高层Java应用+底层Linux接口调用”的方式,而JNI则是这两者之间的通道。腾讯盖娅资源调度平台就采用了Yarn+(JNI)+CGroup的方式。

这里以Ubuntu为例,介绍一下Linux下开发JNI程序的步骤:

一、正常编写自己的Java业务,并将需要调用底层资源的方法声明为native本地方法。

package org.study.jni;

public class TestJni {

	static {
		//要加载的动态链接库,由C或C++代码编译而成,JVM会自动查找指定Library路径下的
		//动态链接库并加载。这里编译后的链接库名字为libTestJni.so。
		//(Linux下的动态链接库一般用lib作为前缀,链接库的名字是内嵌到.so文件的,
		//物理文件的前缀不会影响这里的loadLibrary)
		System.loadLibrary("TestJni");
	}

	//声明要用到的本地方法原型,native方法可以像abstract方法一样只是一个声明,
	//native方法的具体实现是在C/C++中实现的
	public native void sayHello(String name);
	//这里增加一个操作JavaBean的方法,来增强复杂度
	public native void changePersonAge(JniPerson person);

	public static void main(String[] args) {
		TestJni jni = new TestJni();
		jni.sayHello("Join");
		
		JniPerson person = new JniPerson();
		person.setAge(10);
		jni.changePersonAge(person);
		System.out.println("After: " + person.getAge());
	}

}


这里还有一个辅助的JavaBean对象:JniPerson.java

package org.study.jni;

import java.io.Serializable;

public class JniPerson implements Serializable {

	private static final long serialVersionUID = 1554409252901146643L;
	
	private String name = null;
	private int age = 20;
	
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	
}


二、通过javah命令,从编译后的字节码文件中提取本地方法信息,并自动生成头文件。

在Java项目的字节码文件夹下(eclipse的话一般是项目下的bin目录),执行“javah org.study.jni.TestJni” Shell命令,会自动生成一个org_study_jni_TestJni.h,内容如下:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include 
/* Header for class org_study_jni_TestJni */

#ifndef _Included_org_study_jni_TestJni
#define _Included_org_study_jni_TestJni
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     org_study_jni_TestJni
 * Method:    sayHello
 * Signature: (Ljava/lang/String;)V
 */
JNIEXPORT void JNICALL Java_org_study_jni_TestJni_sayHello
  (JNIEnv *, jobject, jstring);

/*
 * Class:     org_study_jni_TestJni
 * Method:    changePersonAge
 * Signature: (Lorg/study/jni/JniPerson;)V
 */
JNIEXPORT void JNICALL Java_org_study_jni_TestJni_changePersonAge
  (JNIEnv *, jobject, jobject);

#ifdef __cplusplus
}
#endif
#endif

三、编写C/C++的业务实现,我们可以借助包含CDT的Eclipse来开发。这里我用Eclipse新建一个C类型的项目(如果要采用C++的话也一样),将生成的org_study_jni_TestJni.h头文件放到项目下,并建立一个代码实现文件,这里起名为org_study_jni_TestJni.c。

Eclipse项目结构:

Linux下开发JNI程序_第1张图片

org_study_jni_TestJni.c实现代码如下:

#include 
#include 
#include 
#include "../header/org_study_jni_TestJni.h"

JNIEXPORT void JNICALL Java_org_study_jni_TestJni_sayHello
  (JNIEnv *env, jobject obj, jstring name) {
    // 从 instring 字符串取得指向字符串 UTF 编码的指针,注意C语言必须“(*env)->”,C++可以“env->”
    const jbyte *pname = (const jbyte *) (*env)->GetStringUTFChars(env, name, JNI_FALSE);
    printf("Hello, %s\n", pname);
    // 通知虚拟机本地代码不再需要通过 str 访问 Java 字符串。
    (*env)->ReleaseStringUTFChars(env, name, (const char *) pname);
}




JNIEXPORT void JNICALL Java_org_study_jni_TestJni_changePersonAge
  (JNIEnv *env, jobject obj, jobject person) {
    //获取person对象的类型信息,在native方法中操作Java对象的方式有点类似于反射
    jclass person_clazz = (*env)->GetObjectClass(env, person);
    //这里不通过调用setAge()方法获取
    jfieldID age_fid = (*env)->GetFieldID(env, person_clazz, "age", "I");
    printf("GetIntFiled: %d \n", (*env)->GetIntField(env, person, age_fid));
    (*env)->SetIntField(env, person, age_fid, 123);
}

四、将C/C++代码编译为动态链接库。如果使用gcc命令行编译,可以到代码目录下执行“gcc -fPIC -shared -I /usr/local/jdk1_7/include/ -I /usr/local/jdk1_7/include/linux/ -o libTestJni.so org_study_jni_TestJni.c”命令来生成libTestJni.so。如果使用Eclipse编译,则有几个选项需要设定一下:

1、将JDK的JNI头文件引用到项目路径下。对于JDK1.7,JNI头文件放在$JAVA_HOME/include和$JAVA_HOME/include/linux两个文件夹下。

Linux下开发JNI程序_第2张图片

2、将项目编译选项设定为动态链接库,而非可执行文件。

Linux下开发JNI程序_第3张图片

3、为gcc编译器增加-fPIC编译选项,这里建的是C项目,所以更改C编译器的设置即可,如果编译时出错,可以将其它几个编译器的Miscellaneous选项也加上“-fPIC”。

Linux下开发JNI程序_第4张图片

4、按下Ctrl+B来进行编译,如果编译成功可以在项目的Debug或Release目录下生成libTestJni.so文件。


五、终于到了激动人心的时刻,现在可以运行我们的Java程序。将动态链接库libTestJni.so文件复制到Java项目的bin目录下,运行“java -Djava.library.path='.'  org.study.jni.TestJni”,就出现如下运行结果:
Hello, Join
GetIntFiled: 10 
After: 123
至此,我们的JNI程序就调用成功了,有了JNI的连通,Java就可以借助C/C++的帮助实现对底层资源的获取和干预,特别是对内核的操作。

你可能感兴趣的:(java,jni)