第七章之刻意练习
练习1: 学会在C代码中创建虚拟机,复习类、方法ID的查找
在native侧,使用random函数生成一个包含1000个整型的数组,然后在native侧调用Java编程语言中的Arrays.sort(int [])方法对数组进行排序,然后打印数组的前100个数。
** 下面是博主自己写的答案 **
首先直接给上代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
#include <jni.h> #include <math.h> #include <stdio.h> #include <stdlib.h> #include <time.h> int main(int argc, char **argv) { srand(time(0)); jint res = 0; JavaVM *jvm; JNIEnv *env; JavaVMInitArgs vm_args; // 设置JavaVM的参数 JavaVMOption *options = (JavaVMOption *)malloc(sizeof(JavaVMOption) * 1); // java.class.path需要换成java.util.Arrays类所在的路径,也就是Java在主机的安装路径了 // 博主的JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64 options[0].optionString = "-Djava.class.path=$JAVA_HOME/"; vm_args.version = JNI_VERSION_1_8; vm_args.nOptions = 1; vm_args.options = options; vm_args.ignoreUnrecognized = JNI_FALSE; // 创建JavaVM res = JNI_CreateJavaVM(&jvm, (void **)&env, &vm_args); if(res < 0) { printf("Creat Java VM failed!\n"); return -1; } free(options); // 查找对应的class jclass cls = (*env)->FindClass(env, "java/util/Arrays"); if(cls == NULL) { printf("FindClass return NULL!\n"); return -1; } // 查找sort的方法ID jmethodID mid = (*env)->GetStaticMethodID(env, cls, "sort", "([I)V"); if(mid == NULL) { printf("GetStaticMethodID return NULL!\n"); return -1; } // 产生1000个int型的数组 int arr[1000]; for(int i = 0; i < 1000; i++) { arr[i] = random() % 10000; } // 上面的数组不能直接使用,需要创建jintArray数组,然后将上面的数组复制到jintArray中 jintArray jarr = (*env)->NewIntArray(env, 1000); (*env)->SetIntArrayRegion(env, jarr, 0, 1000, arr); // 调用Java编程语言的Arrays.sort方法,对数组元素排序 (*env)->CallStaticVoidMethod(env, cls, mid, jarr); // 将排序后的元素复制回arr数组中 (*env)->GetIntArrayRegion(env, jarr, 0, 1000, arr); for(int i = 0; i < 100; i++) { printf("%4d ", arr[i]); if((i + 1) % 10 == 0) printf("\n"); } (*jvm)->DestroyJavaVM(jvm); return 0; } |
上面的代码主要是学会在C程序中创建JavaVM以及调用Java编程语言中的方法。自己写过一两次应该也就会了。然后就是编译了,下面是博主的编译指令,博主使用的是Ubuntu 16.04 LTS版本。首先将编译指令:
1 |
gcc -g -I$JAVA_HOME/include -I$JAVA_HOME/include/linux -L$JAVA_HOME/jre/lib/amd64/server Parctice1.c -o Practice1 -ljvm |
其中-I$JAVA_HOME/include -I$JAVA_HOME/include/linux
主要是为了提供头文件的包含目录,而-L$JAVA_HOME/jre/lib/amd64/server
提供Java虚拟机动态库的链接目录,最后-ljmv
表示链接到libjvm.so库。奇怪的是,-ljvm
必须放到最后,编译才能成功,而放到其他位置的话都会报错:Parctice1.c:26: undefined reference to 'JNI_CreateJavaVM'
。大家要想编译通过记得将-ljvm
放最后就好了。
同样的JAVA_HOME需要改成自己的环境变量,博主已经在.bashrc中添加了自己的JAVA_HOME环境:
1 |
export JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64 |
编译通过后,直接通过./Practice1
运行就好了,不需要做特殊的设置,C程序就自动会根据代码中设置JavaVMOption路径查找到需要的库的了。
练习2: 学会多线程中使用JavaVM
编写一个线程函数,在函数内打印被调用的次数并且调用Java的java.util.Date类的方法,打印调用的时间按照hh:mm:ss方式打印,main函数就创建Java虚拟机环境,循环调用前面设计的打印时间线程函数,每次循环都使用random来产生1-5秒之间的数来决定休眠时间。
下面是我写的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 |
#include <jni.h> #include <stdio.h> #include <unistd.h> // for sleep() #include <pthread.h> #include <time.h> #include <stdlib.h> // 文件全局变量 static JavaVM *jvm = NULL; static unsigned call_count = 0; void *thread_fun(void *arg) { jint res = 0; jclass clazz; jmethodID dateConstructorID; jmethodID getHoursID; jmethodID getMinutesID; jmethodID getSecondsID; JNIEnv *env; // JavaVM还没有初始化 if(jvm == NULL) { printf("Obtain JavaVM error!\n"); goto detach; } printf("thread_fun have been called %d times!\n", ++call_count); // 将线程附加到JavaVM上 res = (*jvm)->AttachCurrentThread(jvm, (void **)&env, NULL); if(res < 0) { printf("AttachCurrentThread errir!\n"); goto detach; } // 下面开始查找Date类以及获取时间的方法ID clazz = (*env)->FindClass(env, "java/util/Date"); if(clazz == NULL) { printf("FindClass error!\n"); goto detach; } getHoursID = (*env)->GetMethodID(env, clazz, "getHours", "()I"); if(getHoursID == NULL) { printf("getHoursID error!\n"); goto detach; } getMinutesID = (*env)->GetMethodID(env, clazz, "getMinutes", "()I"); if(getMinutesID == NULL) { printf("getMinutesID error!\n"); goto detach; } getSecondsID = (*env)->GetMethodID(env, clazz, "getSeconds", "()I"); if(getSecondsID == NULL) { printf("getSecondsID error!\n"); goto detach; } // 获取构造函数方法ID dateConstructorID = (*env)->GetMethodID(env, clazz, "<init>", "()V"); if(dateConstructorID == NULL) { printf("dateConstructorID error!\n"); goto detach; } // 调用Date的构造函数来创建一个对象 jobject dateObject = (*env)->NewObject(env, clazz, dateConstructorID); if(dateObject == NULL) { printf("call constructor error!\n"); goto detach; } // 调用方法获取时间值 jint hours = (*env)->CallIntMethod(env, dateObject, getHoursID); jint minutes = (*env)->CallIntMethod(env, dateObject, getMinutesID); jint seconds = (*env)->CallIntMethod(env, dateObject, getSecondsID); detach: // 下面需要括号括起来,不然会报错:a label can only be part of a statement and a declaration is not a statemen { jthrowable exc = (*env)->ExceptionOccurred(env); if(exc) { (*env)->ExceptionDescribe(env); (*env)->ExceptionClear(env); (*jvm)->DetachCurrentThread(jvm); pthread_exit((void *)-1); } } printf("Now Time: %02d:%02d:%02d\n", hours, minutes, seconds); pthread_exit((void *)0); } int main(int argc, char **argv) { JNIEnv *env; int i = 0; jint res = 0; pthread_t tid; srand(time(0)); // 初始化虚拟机参数 JavaVMInitArgs vm_args; JavaVMOption options[1]; options[0].optionString = "-Djava.class.path=$JAVA_HOME/"; vm_args.version = JNI_VERSION_1_8; vm_args.options = options; vm_args.nOptions = 1; vm_args.ignoreUnrecognized = JNI_FALSE; // 创建虚拟机 res = JNI_CreateJavaVM(&jvm, (void **)&env, &vm_args); if(res < 0) { printf("Can't create Java VM\n"); return -1; } for(i = 0; i < 10; i++) { res = pthread_create(&tid, NULL, thread_fun, NULL); if(res != 0) { printf("#%d, pthread_create error!\n", i); continue; } // 随机休眠1-5秒 unsigned sleepTime = 1 + (random() % 4); printf("go to sleep %d seconds!\n", sleepTime); sleep(sleepTime); } return 0; } |
最后再按照Practice 1中的编译方法编译就好了。其实这个还可以改进下,提高执行的性能,大家发现没上面的每次创建线程并执行的时候都要再次查找工作,我们完全可以建立缓存,将方法ID保存起来提高性能的。这个就让大家自己试一试了。
** 此文为博主原创文章,转载请注明出处 **