`
林奇峰
  • 浏览: 42353 次
  • 性别: Icon_minigender_1
  • 来自: 濮阳
社区版块
存档分类
最新评论

打开进程

    博客分类:
  • j2se
阅读更多

 打开javac命令,下相当于在cmd命令行里执行javac命令

try {
 Runtime rt = Runtime.getRuntime();
 Process p = rt.exec("javac");

 InputStream error = p.getErrorStream();//获取错误信息的输入流,下面两句是包装类
 InputStreamReader isr = new InputStreamReader(error);
 BufferedReader br = new BufferedReader(isr);
 
 String line = null;
 
 System.out.println("<ERROR>");
 while((line = br.readLine())!= null){
  System.out.println(line);
 }
 System.out.println("</ERROR>");
 
 int exitValue = p.exitValue();
 System.out.println(exitValue);
} catch (IOException e) {
 e.printStackTrace();
}

 打开360浏览器

try {
 Runtime rt = Runtime.getRuntime();
 Process p = rt.exec("C:\\Program Files\\Internet Explorer\\iexplore.exe");
 
 //打开IE浏览器,即打开一个进程 
 int exitValue = p.waitFor();//阻塞方法,等待所打开进程关闭
 System.out.println(exitValue);
} catch (IOException e) {
 e.printStackTrace();
}

 

Java 中的进程与线程

 

盛 江涛, 软件工程师, IBM
刘 冠群, 软件工程师, IBM

 

简介: 进程与线程在程序开发中无疑占有极其重要的地位,而 Java 语言为了能提供统一的、与平台无关的关于进程和线程的编程接口,必然要对操作系统提供的相关功能做进一步封装。本文主要介绍 Java 中关于进程与线程的相关封装类,揭示如何创建 Java 进程与线程,Java 封装类和实际的系统本地进程和线程是如何对应的,以及使用 Java 进程和线程的一些限制。

发布日期: 2013 年 6 月 03 日 
访问情况 : 5656 次浏览 
评论:  (查看 | 添加评论 - 登录)

平均分 4 星 共 20 个评分 平均分 (20个评分)
为本文评分

 

概述

进程与线程,本质意义上说, 是操作系统的调度单位,可以看成是一种操作系统 “资源” 。Java 作为与平台无关的编程语言,必然会对底层(操作系统)提供的功能进行进一步的封装,以平台无关的编程接口供程序员使用,进程与线程作为操作系统核心概念的一部分无疑亦是如此。在 Java 语言中,对进程和线程的封装,分别提供了 Process 和 Thread 相关的一些类。本文首先简单的介绍如何使用这些类来创建进程和线程,然后着重介绍这些类是如何和操作系统本地进程线程相对应的,给出了 Java 虚拟机对于这些封装类的概要性的实现;同时由于 Java 的封装也隐藏了底层的一些概念和可操作性,本文还对 Java 进程线程和本地进程线程做了一些简单的比较,列出了使用 Java 进程、线程的一些限制和需要注意的问题。

 

Java 进程的建立方法

在 JDK 中,与进程有直接关系的类为 Java.lang.Process,它是一个抽象类。在 JDK 中也提供了一个实现该抽象类的 ProcessImpl 类,如果用户创建了一个进程,那么肯定会伴随着一个新的 ProcessImpl 实例。同时和进程创建密切相关的还有 ProcessBuilder,它是在 JDK1.5 中才开始出现的,相对于 Process 类来说,提供了便捷的配置新建进程的环境,目录以及是否合并错误流和输出流的方式。

Java.lang.Runtime.exec 方法和 Java.lang.ProcessBuilder.start 方法都可以创建一个本地的进程,然后返回代表这个进程的 Java.lang.Process 引用。

Runtime.exec 方法建立一个本地进程

该方法在 JDK1.5 中,可以接受 6 种不同形式的参数传入。

 Process exec(String command) 
 Process exec(String [] cmdarray) 
 Process exec(String [] cmdarrag, String [] envp) 
 Process exec(String [] cmdarrag, String [] envp, File dir) 
 Process exec(String cmd, String [] envp) 
 Process exec(String command, String [] envp, File dir) 

他们主要的不同在于传入命令参数的形式,提供的环境变量以及定义执行目录。

ProcessBuilder.start 方法来建立一个本地的进程

如果希望在新创建的进程中使用当前的目录和环境变量,则不需要任何配置,直接将命令行和参数传入 ProcessBuilder 中,然后调用 start 方法,就可以获得进程的引用。

 Process p = new ProcessBuilder("command", "param").start(); 

也可以先配置环境变量和工作目录,然后创建进程。

 ProcessBuilder pb = new ProcessBuilder("command", "param1", "param2"); 
 Map<String, String> env = pb.environment(); 
 env.put("VAR", "Value"); 
 pb.directory("Dir"); 
 Process p = pb.start(); 

可以预先配置 ProcessBuilder 的属性是通过 ProcessBuilder 创建进程的最大优点。而且可以在后面的使用中随着需要去改变代码中 pb 变量的属性。如果后续代码修改了其属性,那么会影响到修改后用 start 方法创建的进程,对修改之前创建的进程实例没有影响。

JVM 对进程的实现

在 JDK 的代码中,只提供了 ProcessImpl 类来实现 Process 抽象类。其中引用了 native 的 create, close, waitfor, destory 和 exitValue 方法。在 Java 中,native 方法是依赖于操作系统平台的本地方法,它的实现是用 C/C++ 等类似的底层语言实现。我们可以在 JVM 的源代码中找到对应的本地方法,然后对其进行分析。JVM 对进程的实现相对比较简单,以 Windows 下的 JVM 为例。在 JVM 中,将 Java 中调用方法时的传入的参数传递给操作系统对应的方法来实现相应的功能。如表 1


表 1. JDK 中 native 方法与 Windows API 的对应关系
JDK 中调用的 native 方法名 对应调用的 Windows API
create CreateProcess,CreatePipe
close CloseHandle
waitfor WaitForMultipleObjects
destroy TerminateProcess
exitValue GetExitCodeProcess

以 create 方法为例,我们看一下它是如何和系统 API 进行连接的。

在 ProcessImple 类中,存在 native 的 create 方法,其参数如下:

 private native long create(String cmdstr, String envblock, 
 String dir, boolean redirectErrorStream, FileDescriptor in_fd, 
 FileDescriptor out_fd, FileDescriptor err_fd) throws IOException; 

在 JVM 中对应的本地方法如代码清单 1 所示 。


清单 1
 JNIEXPORT jlong JNICALL 
 Java_Java_lang_ProcessImpl_create(JNIEnv *env, jobject process, 
	 jstring cmd, 
	 jstring envBlock, 
	 jstring dir, 
	 jboolean redirectErrorStream, 
	 jobject in_fd, 
	 jobject out_fd, 
	 jobject err_fd) 
 { 
     /* 设置内部变量值 */ 
	 ……
     /* 建立输入、输出以及错误流管道 */ 
	 if (!(CreatePipe(&inRead,  &inWrite,  &sa, PIPE_SIZE) && 
		 CreatePipe(&outRead, &outWrite, &sa, PIPE_SIZE) && 
		 CreatePipe(&errRead, &errWrite, &sa, PIPE_SIZE))) { 
		 throwIOException(env, "CreatePipe failed"); 
			 goto Catch; 
		 } 
     /* 进行参数格式的转换 */ 
		 pcmd = (LPTSTR) JNU_GetStringPlatformChars(env, cmd, NULL); 
		 ……
     /* 调用系统提供的方法,建立一个 Windows 的进程 */ 
		 ret = CreateProcess( 
		 0,           /* executable name */ 
		 pcmd,        /* command line */ 
		 0,           /* process security attribute */ 
		 0,           /* thread security attribute */ 
		 TRUE,        /* inherits system handles */ 
		 processFlag, /* selected based on exe type */ 
		 penvBlock,   /* environment block */ 
		 pdir,        /* change to the new current directory */ 
		 &si,     /* (in)  startup information */ 
		 &pi);     /* (out) process information */ 
		…
     /* 拿到新进程的句柄 */ 
		 ret = (jlong)pi.hProcess; 
		…
     /* 最后返回该句柄 */ 
		 return ret; 
 } 

可以看到在创建一个进程的时候,调用 Windows 提供的 CreatePipe 方法建立输入,输出和错误管道,同时将用户通过 Java 传入的参数转换为操作系统可以识别的 C 语言的格式,然后调用 Windows 提供的创建系统进程的方式,创建一个进程,同时在 JAVA 虚拟机中保存了这个进程对应的句柄,然后返回给了 ProcessImpl 类,但是该类将返回句柄进行了隐藏。也正是 Java 跨平台的特性体现,JVM 尽可能的将和操作系统相关的实现细节进行了封装,并隐藏了起来。

同样,在用户调用 close、waitfor、destory 以及 exitValue 方法以后, JVM 会首先取得之前保存的该进程在操作系统中的句柄,然后通过调用操作系统提供的接口对该进程进行操作。通过这种方式来实现对进程的操作。

在其它平台下也是用类似的方式实现的,不同的是调用的对应平台的 API 会有所不同。

 

Java 进程与操作系统进程

通过上面对 Java 进程的分析,其实它在实现上就是创建了操作系统的一个进程,也就是每个 JVM 中创建的进程都对应了操作系统中的一个进程。但是,Java 为了给用户更好的更方便的使用,向用户屏蔽了一些与平台相关的信息,这为用户需要使用的时候,带来了些许不便。

在使用 C/C++ 创建系统进程的时候,是可以获得进程的 PID 值的,可以直接通过该 PID 去操作相应进程。但是在 JAVA 中,用户只能通过实例的引用去进行操作,当该引用丢失或者无法取得的时候,就无法了解任何该进程的信息。

当然,Java 进程在使用的时候还有些要注意的事情:

  1. Java 提供的输入输出的管道容量是十分有限的,如果不及时读取会导致进程挂起甚至引起死锁。
  2. 当创建进程去执行 Windows 下的系统命令时,如:dir、copy 等。需要运行 windows 的命令解释器,command.exe/cmd.exe,这依赖于 windows 的版本,这样才可以运行系统的命令。
  3. 对于 Shell 中的管道 ‘ | ’命令,各平台下的重定向命令符 ‘ > ’,都无法通过命令参数直接传入进行实现,而需要在 Java 代码中做一些处理,如定义新的流来存储标准输出,等等问题。

总之,Java 中对操作系统的进程进行了封装,屏蔽了操作系统进程相关的信息。同时,在使用 Java 提供创建进程运行本地命令的时候,需要小心使用。

一般而言,使用进程是为了执行某项任务,而现代操作系统对于执行任务的计算资源的配置调度一般是以线程为对象(早期的类 Unix 系统因为不支持线程,所以进程也是调度单位,但那是比较轻量级的进程,在此不做深入讨论)。创建一个进程,操作系统实际上还是会为此创建相应的线程以运行一系列指令。特别地,当一个任务比较庞大复杂,可能需要创建多个线程以实现逻辑上并发执行的时候,线程的作用更为明显。因而我们有必要深入了解 Java 中的线程,以避免可能出现的问题。本文下面的内容即是呈现 Java 线程的创建方式以及它与操作系统线程的联系与区别。

 

Java 创建线程的方法

实际上,创建线程最重要的是提供线程函数(回调函数),该函数作为新创建线程的入口函数,实现自己想要的功能。Java 提供了两种方法来创建一个线程:

  1. 继承 Thread 类
 class MyThread extends Thread{ 
	 public void run() { 	
		 System.out.println("My thread is started."); 
	 } 
 } 		 
			

实现该继承类的 run 方法,然后就可以创建这个子类的对象,调用 start 方法即可创建一个新的线程:

 MyThread myThread = new MyThread(); 
	 myThread.start(); 

  1. 实现 Runnable 接口
 class MyRunnable implements Runnable{ 
	 public void run() { 
	     System.out.println("My runnable is invoked."); 
	 } 
 } 

实现 Runnable 接口的类的对象可以作为一个参数传递到创建的 Thread 对象中,同样调用 Thread#start 方法就可以在一个新的线程中运行 run 方法中的代码了。

 Thread myThread = new Thread( new MyRunnable()); 
	 myThread.start(); 

可以看到,不管是用哪种方法,实际上都是要实现一个 run 方法的。 该方法本质是上一个回调方法。由 start 方法新创建的线程会调用这个方法从而执行需要的代码。 从后面可以看到,run 方法并不是真正的线程函数,只是被线程函数调用的一个 Java 方法而已,和其他的 Java 方法没有什么本质的不同。

Java 线程的实现

从概念上来说,一个 Java 线程的创建根本上就对应了一个本地线程(native thread)的创建,两者是一一对应的。 问题是,本地线程执行的应该是本地代码,而 Java 线程提供的线程函数是 Java 方法,编译出的是 Java 字节码,所以可以想象的是, Java 线程其实提供了一个统一的线程函数,该线程函数通过 Java 虚拟机调用 Java 线程方法 , 这是通过 Java 本地方法调用来实现的。

以下是 Thread#start 方法的示例:

 public synchronized void start() { 
     …
     start0(); 
     …
 } 

可以看到它实际上调用了本地方法 start0, 该方法的声明如下:

private native void start0();

Thread 类有个 registerNatives 本地方法,该方法主要的作用就是注册一些本地方法供 Thread 类使用,如 start0(),stop0() 等等,可以说,所有操作本地线程的本地方法都是由它注册的 . 这个方法放在一个 static 语句块中,这就表明,当该类被加载到 JVM 中的时候,它就会被调用,进而注册相应的本地方法。

 private static native void registerNatives(); 
  static{ 
       registerNatives(); 
  }

本地方法 registerNatives 是定义在 Thread.c 文件中的。Thread.c 是个很小的文件,定义了各个操作系统平台都要用到的关于线程的公用数据和操作,如代码清单 2 所示。


清单 2
 JNIEXPORT void JNICALL 
 Java_Java_lang_Thread_registerNatives (JNIEnv *env, jclass cls){ 
   (*env)->RegisterNatives(env, cls, methods, ARRAY_LENGTH(methods)); 
 } 
 static JNINativeMethod methods[] = { 
    {"start0", "()V",(void *)&JVM_StartThread}, 
    {"stop0", "(" OBJ ")V", (void *)&JVM_StopThread}, 
	 {"isAlive","()Z",(void *)&JVM_IsThreadAlive}, 
	 {"suspend0","()V",(void *)&JVM_SuspendThread}, 
	 {"resume0","()V",(void *)&JVM_ResumeThread}, 
	 {"setPriority0","(I)V",(void *)&JVM_SetThreadPriority}, 
	 {"yield", "()V",(void *)&JVM_Yield}, 
	 {"sleep","(J)V",(void *)&JVM_Sleep}, 
	 {"currentThread","()" THD,(void *)&JVM_CurrentThread}, 
	 {"countStackFrames","()I",(void *)&JVM_CountStackFrames}, 
	 {"interrupt0","()V",(void *)&JVM_Interrupt}, 
	 {"isInterrupted","(Z)Z",(void *)&JVM_IsInterrupted}, 
	 {"holdsLock","(" OBJ ")Z",(void *)&JVM_HoldsLock}, 
	 {"getThreads","()[" THD,(void *)&JVM_GetAllThreads}, 
	 {"dumpThreads","([" THD ")[[" STE, (void *)&JVM_DumpThreads}, 
 };

到此,可以容易的看出 Java 线程调用 start 的方法,实际上会调用到 JVM_StartThread 方法,那这个方法又是怎样的逻辑呢。实际上,我们需要的是(或者说 Java 表现行为)该方法最终要调用 Java 线程的 run 方法,事实的确如此。 在 jvm.cpp 中,有如下代码段:

 JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread)) 
	…
	 native_thread = new JavaThread(&thread_entry, sz); 
	…

这里JVM_ENTRY是一个宏,用来定义JVM_StartThread 函数,可以看到函数内创建了真正的平台相关的本地线程,其线程函数是 thread_entry,如清单 3 所示。


清单 3
 static void thread_entry(JavaThread* thread, TRAPS) { 
    HandleMark hm(THREAD); 
	 Handle obj(THREAD, thread->threadObj()); 
	 JavaValue result(T_VOID); 
	 JavaCalls::call_virtual(&result,obj, 
	 KlassHandle(THREAD,SystemDictionary::Thread_klass()), 
	 vmSymbolHandles::run_method_name(), 
 vmSymbolHandles::void_method_signature(),THREAD); 
 }

可以看到调用了 vmSymbolHandles::run_method_name 方法,这是在 vmSymbols.hpp 用宏定义的:

 class vmSymbolHandles: AllStatic { 
	…
	 template(run_method_name,"run") 
	…
 } 	

至于 run_method_name 是如何声明定义的,因为涉及到很繁琐的代码细节,本文不做赘述。感兴趣的读者可以自行查看 JVM 的源代码。


图 1. Java 线程创建调用关系图
图 1. Java 线程创建调用关系图 

综上所述,Java 线程的创建调用过程如 图 1 所示,首先 , Java 线程的 start 方法会创建一个本地线程(通过调用 JVM_StartThread),该线程的线程函数是定义在 jvm.cpp 中的 thread_entry,由其再进一步调用 run 方法。可以看到 Java 线程的 run 方法和普通方法其实没有本质区别,直接调用 run 方法不会报错,但是却是在当前线程执行,而不会创建一个新的线程。

 

Java 线程与操作系统线程

从上我们知道,Java 线程是建立在系统本地线程之上的,是另一层封装,其面向 Java 开发者提供的接口存在以下的局限性:

线程返回值

Java 没有提供方法来获取线程的退出返回值。实际上,线程可以有退出返回值,它一般被操作系统存储在线程控制结构中 (TCB),调用者可以通过检测该值来确定线程是正常退出还是异常终止。

线程的同步

Java 提供方法 Thread#Join()来等待一个线程结束,一般情况这就足够了,但一种可能的情况是,需要等待在多个线程上(比如任意一个线程结束或者所有线程结束才会返回),循环调用每个线程的 Join 方法是不可行的,这可能导致很奇怪的同步问题。

线程的 ID

Java 提供的方法 Thread#getID()返回的是一个简单的计数 ID,其实和操作系统线程的 ID 没有任何关系。

线程运行时间统计

Java 没有提供方法来获取线程中某段代码的运行时间的统计结果。虽然可以自行使用计时的方法来实现(获取运行开始和结束的时间,然后相减 ),但由于存在多线程调度方法的原因,无法获取线程实际使用的 CPU 运算时间,因而必然是不准确的。

 

总结

本文通过对 Java 进程和线程的分析,可以看出 Java 对这两种操作系统 “资源” 进行了封装,使得开发人员只需关注如何使用这两种 “资源” ,而不必过多的关心细节。这样的封装一方面降低了开发人员的工作复杂度,提高了工作效率;另一方面由于封装屏蔽了操作系统本身的一些特性,因而在使用 Java 进程线程时有了某些限制,这是封装不可避免的问题。语言的演化本就是决定需要什么不需要什么的过程,相信随着 Java 的不断发展,封装的功能子集的必然越来越完善。

 

 

想深入理解,参考如下

http://xmong.iteye.com/blog/1553000

分享到:
评论

相关推荐

    驱动内核打开进程

    在操作系统的世界里,驱动内核打开进程是一个关键的机制,它涉及到系统级程序与硬件交互、资源管理和任务调度等多个核心领域。驱动内核通常是指操作系统内核中的特殊代码,负责管理硬件设备,而进程则是系统中执行的...

    易语言源码易语言使用驱动打开进程源码.rar

    在“易语言源码易语言使用驱动打开进程源码.rar”这个压缩包中,包含的是一段使用易语言编写的源代码,主要功能是利用驱动程序来打开操作系统中的进程。下面我们将详细探讨相关的知识点。 1. **易语言**: - ...

    易语言使用驱动打开进程

    易语言使用驱动打开进程源码,使用驱动打开进程,加载驱动_,卸载驱动_,与驱动程序通信_,键码转换,CreateServiceA,OpenServiceA,StartServiceA,CloseServiceHandle,CreateFileA,ControlService,OpenSCManagerA,...

    易语言使用驱动打开进程源码

    在标题“易语言使用驱动打开进程源码”中,我们可以理解为这是一个关于如何在易语言中利用驱动程序来操作和控制进程的源代码示例。驱动程序是操作系统与硬件或低级软件之间的一个桥梁,它允许更高层次的软件更好地...

    用其他方式打开进程的资源

    标题“用其他方式打开进程的资源”暗示了我们正在讨论如何在不使用标准方法(如任务管理器)的情况下访问和管理进程的资源。下面将详细解释这个主题。 1. **进程的概念**:在计算机科学中,进程是程序的一次执行...

    易语言汇编实现打开进程源码

    在易语言中使用汇编实现打开进程的操作,是一项对技术有一定要求的任务。 首先,我们需要理解“打开进程”在操作系统中的含义。在Windows系统中,打开进程通常指的是获取进程的句柄,以便于后续的读写、控制或监控...

    易语言汇编实现打开进程源码.7z

    "易语言汇编实现打开进程源码.7z"这个压缩包文件很可能是包含了一组教程或代码示例,用于教用户如何在易语言中使用汇编语言来打开并操作进程。 在Windows操作系统中,打开进程通常涉及到以下知识点: 1. 进程管理...

    任务管理器中添加打开进程所在文件夹

    "任务管理器中添加打开进程所在文件夹"的功能,是一个实用的自定义扩展,使得用户可以直接从任务管理器中定位到某个进程相关的文件路径,从而更好地理解和控制系统的运行状态。 首先,我们要理解任务管理器的基本...

    C#读写内存打开进程的类.cs

    本篇文章将深入探讨如何在C#中实现读写内存以及打开进程的功能。 首先,我们要理解在C#中进行读写内存操作的基本概念。这通常涉及到进程管理和Windows API调用。在Windows操作系统中,进程是系统分配资源的基本单位...

    C#客户端对服务端实现的关机,结束进程,运行程序,打开进程,dos命令实现等

    本话题主要探讨了如何使用C#语言来实现客户端对服务端的一些高级功能,包括远程关机、结束进程、运行程序、打开进程以及执行DOS命令。这些功能在远程管理系统或者自动化运维场景中具有广泛应用。 首先,让我们详细...

    怎样打开进程管理器 (2).docx

    【描述】:本文旨在介绍如何打开进程管理器,并深入探讨CMD命令行的常用命令及其在计算机系统管理中的应用。 【标签】:互联网, 计算机科学 【正文】: 打开进程管理器是Windows操作系统中管理和监控系统进程的重要...

    易语言汇编实现打开进程源码-易语言

    本文将深入探讨如何使用易语言汇编来实现打开进程的功能,这对于理解和掌握系统级别的编程操作具有重要意义。 在Windows操作系统中,打开进程通常涉及到操作系统的内核API,比如`OpenProcess`函数。这个函数允许...

    易语言源码易语言汇编实现打开进程源码.rar

    易语言源码易语言汇编实现打开进程源码.rar 易语言源码易语言汇编实现打开进程源码.rar 易语言源码易语言汇编实现打开进程源码.rar 易语言源码易语言汇编实现打开进程源码.rar 易语言源码易语言汇编实现打开进程...

    精选_SSDT Hook 之内核函数ZwOpenProcess实现监控打开进程_源码打包

    在这个主题中,“SSDT Hook 之内核函数ZwOpenProcess实现监控打开进程”主要探讨的是如何通过挂钩(Hook)SSDT中的`ZwOpenProcess`函数来实现实时监控系统中进程的打开行为。下面将详细介绍这一技术及其应用。 首先...

    用于日常监控进程状态,如果不存在则打开进程

    用于日常监控进程状态,获取cpu信息,内存信息,如果进程不存在则打开进程

    Windows 进程管理器

    另外,用户可以利用“打开进程所在文件夹”功能,轻松找到与进程相关联的文件,进行管理和维护。 总之,“Windows 进程管理器”通过模仿原生Windows任务管理器的界面和功能,为广大用户带来了更加直观和高效的进程...

    e语言-易语言强力打开结束进程模块

    资源介绍:易语言强力打开结束进程模块源码例程程序调用API函数实现强行打开或结束进程。点评:源码主要使用了NATIVE API。资源作者:三叶易语言自学网资源下载:

    启动第三方进程测试 支持三个参数: 1、待调用进程名字 2、打开进程后关闭时间,单位秒 3、测试次数

    启动第三方进程测试 支持三个参数: 1、待调用进程名字 2、打开进程后关闭时间,单位秒 3、测试次数

    进程内存监控小软件,自动关闭和打开进程

    这个小软件,可以设置多久检查一次进程的内存占用大小,以及可以设置内存占用的最大值,当某个进程超过这个最大值时就自动关闭这个进程,然后又重启这个进程,现在网上很多这类小软件,都有一个错误,就是当自动关闭...

Global site tag (gtag.js) - Google Analytics