Java JVM、JNI、Native Function Interface、Create New Process Native Function API Analysis
目錄
1. JAVA JVM 2. Java JNI: Java Native Interface 3. Java Create New Process Native Function API Analysis In Linux 4. Java Create New Process Native Function API Analysis In Windows?
1. JAVA JVM
0x1: JVM架構簡介
JVM是Java Virtual Machine(Java虛擬機)的縮寫,JVM是一種用于計算設備的規范,它是一個虛構出來的計算機,是通過在實際的計算機上仿真模擬各種計算機功能來實現的
Java語言的一個非常重要的特點就是與平臺的無關性。而使用Java虛擬機是實現這一特點的關鍵。一般的高級語言如果要在不同的平臺上運行,至少需要編譯成不同的目標代碼。而引入Java語言虛擬機后,Java語言在不同平臺上運行時不需要重新編譯。Java語言使用Java虛擬機屏蔽了與具體平臺相關的信息,使得Java語言編譯程序只需生成在Java虛擬機上運行的目標代碼(字節碼),就可以在多種平臺上不加修改地運行。Java虛擬機在執行字節碼時,把字節碼解釋成具體平臺上的機器指令執行。這就是Java的能夠"一次編譯,到處運行"的原因
JVM的設計目標是提供一個基于抽象規格描述的計算機模型,為解釋程序開發人員提很好的靈活性,同時也確保Java代碼可在符合該規范的任何系統上運行。JVM對其實現的某些方面給出了具體的定義
JVM是JAVA的核心和基礎,在JAVA編譯器和OS平臺之間的虛擬處理器。它是一種基于下層的操作系統和硬件平臺并利用軟件方法來實現的抽象的計算機,可以在上面執行JAVA的字節碼程序
JAVA編譯器只需面向JVM,生成JVM能理解的代碼或字節碼文件。Java源文件經編譯器,編譯成字節碼程序,通過JVM將每一條指令翻譯成不同平臺機器碼,通過特定平臺運行
JRE(Java Runtime Environment)包含JVM的java程序的運行環境,即JVM是整個JRE的一部分
JVM是Java程序運行的容器,但是他同時也是操作系統的一個進程,因此他也有他自己的運行的生命周期,也有自己的代碼和數據空間
JVM在整個JDK中處于最底層,負責與操作系統的交互,用來屏蔽操作系統環境,提供一個完整的Java運行環境,因此也就虛擬計算機。操作系統裝入JVM是通過JDK中Java.exe來完成,通過下面4步來完成JVM環境(windows)
Relevant Link:
http://baike.baidu.com/view/160708.htm http://zh.wikipedia.org/wiki/Java%E8%99%9A%E6%8B%9F%E6%9C%BA http://en.wikipedia.org/wiki/Java_virtual_machine?
2. Java JNI: Java Native Interface
0x1: 本地方法棧(Native Method Stacks)
本地方法棧(Native Method Stacks)與虛擬機棧所發揮的作用很類似,區別是
1. 虛擬機棧為虛擬機執行Java方法(字節碼)服務 2. 本地方法棧為虛擬機使用到的Native方法服務虛擬機規范中對本地方法棧中的方法使用的語言、使用方式與數據結構并沒有強制規定,因此具體的虛擬機可以自由實現它。甚至有的虛擬機(例如Sun HotSpot虛擬機)直接就把本地方法棧和虛擬機棧合二為一,與虛擬機一樣,本地方法棧區域也會拋出StackOverflowError和OutOfMemoryError異常
0x2:?Java本地接口
Java本地接口(JNI)是一個"編程框架"使得運行在Java虛擬機上的Java程序調用或者被調用特定于本機硬件與操作系統的用其它語言(C、C++或匯編語言等)編寫的程序
1. JNI允許用本地代碼(和具體操作系統平臺相關的代碼,例如用glibc編譯、和用CRT編譯的平臺相關代碼)來解決純粹用Java編程不能解決的平臺相關的特性。也用于改造已存在的其它語言寫的應用程序,供Java程序訪問。許多使用了JNI的標準庫提供了文件I/O與其它功能。標準庫中性能敏感或平臺敏感的API實現允許所有Java應用程序安全且平臺獨立地訪問這些功能2. JNI框架使得本地方法可以訪問Java對象,就如同Java程序訪問這些本地對象。本地方法可以創建Java對象,然后檢查、使用這些對象執行任務。本地方法也可以檢查并使用由Java程序創建的對象3. Java開發人員稱JNI為逃生門("escape hatch"),因為JINI允許增加標準Java API不能提供的功能。也可以用于時間關鍵的計算或者如解復雜數學方程,因為本地方法的運算比JVM更快。也可以在安卓上重用已存在的C/C++編寫的庫
0x3: 使用JNI存在的問題
1. 使用JNI的精細的錯誤會使JVM不穩定,并且難以再現與調試 2. 僅有應用程序與signed applet可以調用JNI3. 依賴于JNI的應用程序失去了Java的平臺移植性(部分解決之道是對每一平臺寫專門的JNI代碼,然后Java檢測操作系統并載入當前的JNI代碼)4. JNI框架對本地端執行代碼分配的非JVM資源,不提供任何自動的垃圾收集(例如C/C++代碼)。因此,本地端代碼負責釋放任何在本地獲取的資源5. Linux與Solaris平臺,如果本地代碼登記了自身作為信號處理器,會攔截原本發給JVM的信號。解決方法是應該使用信號鏈式的本地代碼更好地與JVM合作 6. Windows平臺上,結構化異常處理(SEH)可用于把本地代碼包裹在SEH try/catch塊中捕捉機器(CPU/FPU)生成的軟中斷(如: 空指針訪問沖突、除0操作等),在這些中斷傳播到JVM之前就得到處理,使JVM無法正常捕獲到這些錯誤異常0x4: JNI Code Example
在JNI框架柱,本地函數一般在單獨的.c或.cpp文件中實現。當JVM調用這些函數,就傳遞
1. JNIEnv指針 2. jobject的指針env指向一個結構包含了到JVM的界面,包含了所有必須的函數與JVM交互、訪問Java對象。基本上,Java程序可以做的任何事情都可以用JNIEnv做。下面代碼把Java字符串轉化為本地字符串
//C++ code extern "C" JNIEXPORT void JNICALL Java_ClassName_MethodName (JNIEnv *env, jobject obj, jstring javaString) {//Get the native string from javaStringconst char *nativeString = env->GetStringUTFChars(javaString, 0);//Do something with the nativeString//DON'T FORGET THIS LINE!!!env->ReleaseStringUTFChars(javaString, nativeString); }/*C code*/ JNIEXPORT void JNICALL Java_ClassName_MethodName (JNIEnv *env, jobject obj, jstring javaString) {/*Get the native string from javaString*/const char *nativeString = (*env)->GetStringUTFChars(env, javaString, 0);/*Do something with the nativeString*//*DON'T FORGET THIS LINE!!!*/(*env)->ReleaseStringUTFChars(env, javaString, nativeString); }/*Objective-C code*/ JNIEXPORT void JNICALL Java_ClassName_MethodName (JNIEnv *env, jobject obj, jstring javaString) {/*DON'T FORGET THIS LINE!!!*/JNF_COCOA_ENTER(env);/*Get the native string from javaString*/NSString* nativeString = JNFJavaToNSString(env, javaString);/*Do something with the nativeString*//*DON'T FORGET THIS LINE!!!*/JNF_COCOA_EXIT(env); }本地數據類型與Java數據類型可以互相映射。對于復合數據類型,如對象,數組,字符串,就必須用JNIEnv中的方法來顯示地轉換
Relevant Link:
?
3. Java Create New Process Native Function API Analysis In Linux
0x1: Java創建新進程涉及到的類、方法
1. java.lang.Runtime.exec(String[] cmdarray, String[] envp);
package com.tutorialspoint;public class RuntimeDemo {public static void main(String[] args) {try {// create a new array of 2 stringsString[] cmdArray = new String[2];// first argument is the program we want to opencmdArray[0] = "ls -l";// second argument is a parametercmdArray[1] = ".";// print a messageSystem.out.println("listing the current directory");Process process = Runtime.getRuntime().exec(cmdArray, null);// print another messageSystem.out.println("list directory is ok");} catch (Exception ex) {ex.printStackTrace();}} }Relevant Link:
http://docs.oracle.com/javase/7/docs/api/java/lang/Runtime.html#exec(java.lang.String[],%20java.lang.String[],%20java.io.File) http://www.tutorialspoint.com/java/lang/runtime_exec_envp.htm2. Process p = new ProcessBuilder("myCommand", "myArg").start();
Relevant Link:
http://examples.javacodegeeks.com/core-java/lang/processbuilder/java-lang-processbuilder-example/ http://m.alvinalexander.com/java/java-exec-processbuilder-process-2 http://stackoverflow.com/questions/3468987/executing-another-application-from-java0x2: difference between ProcessBuilder and Runtime.exec()
The various overloads of Runtime.getRuntime().exec(...) take either an array of strings or a single string. The single-string overloads of exec() will tokenise the string into an array of arguments, before passing the string array onto one of the exec() overloads that takes a string array. The ProcessBuilder constructors, on the other hand, only take a varargs array of strings or a List of strings, where each string in the array or list is assumed to be an individual argument. Either way, the arguments obtained are then joined up into a string that is passed to the OS to execute.
java下創建新進程的2種不同的方法的區別簡單來說是這樣的
1. exec Runtime.getRuntime().exec("C:\DoStuff.exe -arg1 -arg2");2. ProcessBuilder rocessBuilder b = new ProcessBuilder("C:\DoStuff.exe", "-arg1", "-arg2");List<String> params = java.util.Arrays.asList("C:\DoStuff.exe", "-arg1", "-arg2"); ProcessBuilder b = new ProcessBuilder(params); /* 1. ProcessBuilder.start() 和 Runtime.exec()傳遞的參數有所不同1) Runtime.exec()可接受一個單獨的字符串,這個字符串是通過空格來分隔可執行命令程序和參數的,也可以接受字符串數組參數2) ProcessBuilder的構造函數是一個字符串列表或者數組。列表中第一個參數是可執行命令程序,其他的是命令行執行是需要的參數 2. 每個ProcessBuilder實例管理一個進程屬性集。ProcessBuilder的start()方法利用這些屬性創建一個新的Process實例。start()方法可以從同一實例重復調用,以利用相同或者相關的屬性創建新的子進程3. ProcessBuilder還提供了一些額外的功能,例如1) 重復執行2) 設置環境變量 */Relevant Link:
http://www.java-tips.org/java-se-tips/java.util/from-runtime.exec-to-processbuilder.html http://www.scala-lang.org/old/node/7146 http://stackoverflow.com/questions/6856028/difference-between-processbuilder-and-runtime-exec http://lavasoft.blog.51cto.com/62575/15662/0x3: Runtime.exec()源代碼分析
/* ProcessBuilder#start()} is now the preferred way to start a process with a modified environment. */ public Process exec(String[] cmdarray, String[] envp, File dir) throws IOException {return new ProcessBuilder(cmdarray).environment(envp).directory(dir).start(); }在JDK的實現中,Runtime.exec是只是一個包裝函數,最終是通過調用ProcessBuilder實現的新進程創建
Relevant Link:
http://www.docjar.com/html/api/java/lang/Runtime.java.html0x4: ProcessBuilder源代碼分析
繼續跟進ProcessImpl.start
// Only for use by ProcessBuilder.start() static Process start(String[] cmdarray,java.util.Map<String,String> environment,String dir,ProcessBuilder.Redirect[] redirects,boolean redirectErrorStream) throws IOException {...return new UNIXProcess(toCString(cmdarray[0]),argBlock, args.length,envBlock, envc[0],toCString(dir),std_fds,redirectErrorStream);... }繼續跟進UNIXProcess
//構造函數 UNIXProcess(final byte[] prog,final byte[] argBlock, final int argc,final byte[] envBlock, final int envc,final byte[] dir,final int[] fds,final boolean redirectErrorStream)throws IOException { pid = forkAndExec(prog,argBlock, argc,envBlock, envc,dir,fds,redirectErrorStream);try {AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() {public Void run() throws IOException {initStreams(fds);return null;}});} catch (PrivilegedActionException ex) {throw (IOException) ex.getException();} }繼續跟進forkAndExec
/** * Create a process using fork(2) and exec(2). * * @param fds an array of three file descriptors. * Indexes 0, 1, and 2 correspond to standard input, * standard output and standard error, respectively. On * input, a value of -1 means to create a pipe to connect * child and parent processes. On output, a value which * is not -1 is the parent pipe fd corresponding to the * pipe which has been created. An element of this array * is -1 on input if and only if it is <em>not</em> -1 on * output. * @return the pid of the subprocess */ private native int forkAndExec(byte[] prog,byte[] argBlock, int argc,byte[] envBlock, int envc,byte[] dir,int[] fds,boolean redirectErrorStream) throws IOException;/** * The thread factory used to create "process reaper" daemon threads. */ private static class ProcessReaperThreadFactory implements ThreadFactory {private final static ThreadGroup group = getRootThreadGroup();private static ThreadGroup getRootThreadGroup() {return AccessController.doPrivileged(new PrivilegedAction<ThreadGroup> () {public ThreadGroup run() {ThreadGroup root = Thread.currentThread().getThreadGroup();while (root.getParent() != null)root = root.getParent();return root;}});}public Thread newThread(Runnable grimReaper) {// Our thread stack requirement is quite modest.Thread t = new Thread(group, grimReaper, "process reaper", 32768);t.setDaemon(true);// A small attempt (probably futile) to avoid priority inversion t.setPriority(Thread.MAX_PRIORITY);return t;} }forkAndExec是一個JNI函數,即它的實現代碼是通過外部的.so文件實現的
//本機上是這個 ./usr/lib/jvm/java-1.7.0-openjdk-1.7.0.75.x86_64/jre/lib/amd64/libjava.so./usr/lib/jvm/java-1.8.0-openjdk-1.8.0.31-1.b13.el6_6.x86_64/jre/lib/amd64/libjava.so ./usr/lib/jvm/java-1.6.0-openjdk-1.6.0.34.x86_64/jre/lib/amd64/libjava.soobjdump -tT /usr/lib/jvm/java-1.7.0-openjdk-1.7.0.75.x86_64/jre/lib/amd64/libjava.so | grep forkAndExec
0000000000018330 g DF .text 00000000000006fd SUNWprivate_1.1 Java_java_lang_UNIXProcess_forkAndExec\openjdk\jdk\src\solaris\native\java\lang\UNIXProcess_md.c
JNIEXPORT jint JNICALL Java_java_lang_UNIXProcess_forkAndExec(JNIEnv *env,jobject process,jbyteArray prog,jbyteArray argBlock, jint argc,jbyteArray envBlock, jint envc,jbyteArray dir,jintArray std_fds,jboolean redirectErrorStream) {...//創建新的子進程resultPid = startChild(c);assert(resultPid != 0);... }跟進resultPid = startChild(c);
static pid_t startChild(ChildStuff *c) { #if START_CHILD_USE_CLONE ...return clone(childProcess, c->clone_stack + START_CHILD_CLONE_STACK_SIZE, CLONE_VFORK | CLONE_VM | SIGCHLD, c); #elsevolatile pid_t resultPid = vfork(); #else//fork從當前進程中復制出子進程,fork是glibc庫提供的包裝函數,底層實現了fork()系統調用pid_t resultPid = fork(); #endifif (resultPid == 0)//新創建的子進程需要繼續調用exec() API系列 childProcess(c);assert(resultPid != 0); /* childProcess never returns */return resultPid; #endif /* ! START_CHILD_USE_CLONE */ }繼續跟進childProcess(c);
/*** Child process after a successful fork() or clone().* This function must not return, and must be prepared for either all* of its address space to be shared with its parent, or to be a copy.* It must not modify global variables such as "environ".*/ static int childProcess(void *arg) {...JDK_execvpe(p->argv[0], p->argv, p->envv);... }繼續跟進JDK_execvpe(p->argv[0], p->argv, p->envv);
/*** 'execvpe' should have been included in the Unix standards,* and is a GNU extension in glibc 2.10.** JDK_execvpe is identical to execvp, except that the child environment is* specified via the 3rd argument instead of being inherited from environ.*/ static void JDK_execvpe(const char *file, const char *argv[], const char *const envp[]) {if (envp == NULL || (char **) envp == environ) {//調用glibc的execvp,將新的進程文件載入內存,進行執行execvp(file, (char **) argv);return;}... }總結一下
1. Runtime.getRuntime().exec(cmd)的執行流程: java.lang.Runtime.exec(cmd); --java.lang.ProcessBuilder.start(); ----java.lang.ProcessImpl.start(); ------Java_java_lang_UNIXProcess_forkAndExec() in j2se/src/solaris/native/java/lang/UNIXProcess_md.c --------1). fork(); --------2). execvp();2. Process p = new ProcessBuilder("myCommand", "myArg").start();的執行流程 --java.lang.ProcessBuilder.start(); ----java.lang.ProcessImpl.start(); ------Java_java_lang_UNIXProcess_forkAndExec() in j2se/src/solaris/native/java/lang/UNIXProcess_md.c --------1). fork(); --------2). execvp();/* 需要注意的是,fork產生的子進程需要復制父進程在內存中的所有數據內容(代碼段、數據段、堆棧段),由于全部復制開銷較大,因此Linux已經采用copy-on-write機制,即只是復制頁表,共享內容,在有改變的時候再去申請內存和復制數據 */Relevant Link:
https://searchcode.com/codesearch/view/17990470/ http://www.docjar.com/html/api/java/lang/ProcessBuilder.java.html https://searchcode.com/codesearch/view/17994182/ https://searchcode.com/codesearch/view/17966870/ http://blog.csdn.net/bigdatahappy/article/details/40115077?
4. Java Create New Process Native Function API Analysis In Windows
待研究
Relevant Link:
http://www.ibm.com/developerworks/cn/java/j-lo-processthread/ http://docs.oracle.com/javase/7/docs/api/java/lang/ProcessBuilder.html http://www.ibm.com/developerworks/cn/java/j-lo-processthread/ http://openjdk.java.net/groups/hotspot/?
Copyright (c) 2014 LittleHann All rights reserved
?
轉載于:https://www.cnblogs.com/LittleHann/p/4326828.html
總結
以上是生活随笔為你收集整理的Java JVM、JNI、Native Function Interface、Create New Process Native Function API Analysis的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: [BOI2007] Mokia
- 下一篇: 第 7 章 异常处理结构、代码测试与调试