目录
推荐使用:Apache Commons Exec -调用外部进程的瑞士军刀。
Runtime 运行时类概述
1、每个 Java 应用程序都有一个 java.lang.Runtime 类实例,使应用程序能够与其运行的环境相连接。
2、应用程序不能创建自己的 Runtime 类实例,可以通过 getRuntime
静态方法获取当前运行时机制(Runtime)
3、每一个JAVA程序实际上都是启动了一个JVM进程,每一个JVM进程都对应一个Runtime实例
4、得到了当前的Runtime对象的引用,就可以调用Runtime对象的方法去控制Java虚拟机的状态和行为。
long | maxMemory() | 返回Java虚拟机将尝试使用的最大内存量。如果内存本身没有限制,则返回值 Long.MAX_VALUE,以字节为单位。 |
long | totalMemory() | 返回Java虚拟机中的内存总量。目前为当前和后续对象提供的内存总量,以字节为单位。 |
void | gc() | 运行垃圾回收器。调用此方法意味着 Java 虚拟机做了一些努力来回收未用对象,以便能够快速地重用这些对象当前占用的内存。 |
void | exit(int status) | 通过启动其关闭序列来终止当前正在运行的Java虚拟机。status作为状态码,根据惯例,非零的状态码表示非正常终止。关闭之后,任务管理器中的进程也会结束。 |
Process | exec(String command) | 在单独的进程中执行指定的字符串命令。参数:command - 一条指定的系统命令。返回:一个新的 Process 对象,用于管理子进程 |
Process | exec(String[] cmdarray) | 在单独的进程中执行指定的命令和参数。 |
Process | exec(String[] cmdarray, String[] envp) | 在指定环境的单独进程中执行指定的命令和参数。 |
Process | exec(String[] cmdarray, String[] envp, File dir) | 在指定的环境和工作目录的单独进程中执行指定的命令和参数。 |
public static void main(String[] args) throws UnsupportedEncodingException {
Runtime runtime = Runtime.getRuntime();
System.out.println("-------str变量未使用前-------------");
System.out.println("JVM试图使用的最大内存量:"+runtime.maxMemory()+"字节");
System.out.println("JVM空闲内存量:"+runtime.freeMemory()+"字节");
System.out.println("JVM内存总量:"+runtime.totalMemory()+"字节");
String str = "00";
for (int i=0;i<2000;i++){
str += "xx"+i;
}
System.out.println("-------str变量使用后-------------");
System.out.println("JVM试图使用的最大内存量:"+runtime.maxMemory()+"字节");
System.out.println("JVM空闲内存量:"+runtime.freeMemory()+"字节");
System.out.println("JVM内存总量:"+runtime.totalMemory()+"字节");
runtime.gc();
System.out.println("-------垃圾回收后-------------");
System.out.println("JVM试图使用的最大内存量:"+runtime.maxMemory()+"字节");
System.out.println("JVM空闲内存量:"+runtime.freeMemory()+"字节");
System.out.println("JVM内存总量:"+runtime.totalMemory()+"字节");
}
public static void runtimeTest11() throws InterruptedException {
Runtime runtime = Runtime.getRuntime();
System.out.println("程序开始,延时10s后程序退出...");
Thread.sleep(10000);
runtime.exit(0);
System.out.println("JVM已经关闭,此句不会被输出...");
}
调用外部程序
src/test/java/org/example/se/lang/RuntimeTest.java · 汪少棠/java-se - Gitee.com。
运行 VBS 脚本文件控制电脑系统音量
src/main/java/org/example/uitls/RuntimeUtil.java · 汪少棠/java-se - Gitee.com。
Runtime 做 CMD 操作
复制文件、文件夹,删除文件、文件夹:除了使用 Java SE 中的 IO 流、或者类似第三方如 Apache 的 FileUtils 等进行操作外,其实windows自身的cmd指令也可以实现。
src/test/java/org/example/se/lang/RuntimeTest.java · 汪少棠/java-se - Gitee.com。
Process 获取子进程输入流、杀死子进程
1、ProcessBuilder.start 和 Runtime.exec 方法都可以开启一个本机进程,并返回 Process 子类的一个实例,Process 实例可控制进程并获得相关信息。
2、Process 类提供了执行从进程输入、执行输出到进程、等待进程完成、检查进程的退出状态以及销毁(杀掉)进程的方法。
3、从 JDK 1.5开始, ProcessBuilder.start() 是创建 Process 的首选方式。
获取子进程输入流
1、public abstract InputStream getInputStream() :获取子进程的输入流。
2、输入流获得由该 Process 对象表示的进程的标准输出流。
3、实现注意事项:对输入流进行缓冲是一个好主意。 返回连接到子进程正常输出的输入流。
src/test/java/org/example/se/lang/RuntimeTest.java · 汪少棠/java-se - Gitee.com。
杀死 Process 子进程
1、public abstract void destroy() :杀掉子进程,强制终止此 Process 对象表示的子进程。
2、public Process destroyForcibly():杀死子进程,此Process对象表示的子进程被强制终止。 此方法的默认实现调用destroy() ,因此可能不会强制终止进程。 强烈建议使用此类的具体实现来使用兼容实现覆盖此方法。这是1.8新增的方法,也是推荐方式
3、public boolean isAlive():测试这个Process代表的子 进程是否存活。 为true则表示此 Process对象表示的子进程尚未终止。
不加 cmd 直接打开的子进程
1、如下所示,Runtime 调用本地的 PotPlayer 程序播放了一个视频,返回 Process 对象,Process 就代表着打开的 PotPlayer 子进程。休眠 5 秒之后判断 PotPlayer 子进程是否活着,如果活着则杀死它,即 关闭PotPlayer 程序
2、对于如下 cmdStr 这种没有加前缀"cmd /c"的写法,在杀死子进程时,是没有问题的,因为打开的时候,Process 就直接记录的是 PotPlayer.exe 程序
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* Created by Administrator on 2018/6/27 0027.
* 杀进程测试类
*/
public class ProcessTest {
public static void main(String[] args) {
try {
String cmdStr = "D:\\PotPlayer\\PotPlayerMini.exe E:/wmx/zl2.mp4";
Runtime runtime = Runtime.getRuntime();
Process process = runtime.exec(cmdStr);
Thread.sleep(5000);
if (process.isAlive()){
process.destroy();
}
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
加 cmd 打开的子进程
1、cmdStr1、cmdStr2 :对于 exe 程序是不用加"cmd /c"前缀就可以直接打开的,这种方式打开时,Process的destory()与destroyForcibly()方法能正常杀死子进程。而加了"cmd /c"前缀的cmdStr3、cmdStr4打开的文件或程序都是“杀不死”的
2、然而对于其它的大部分的 DOS 指令是必须借助 cmd.exe 程序的,cmdStr3、cmdStr4中的 cmd 字符表示的就是 windows 系统目录中的程序 “C:\\Windows\\System32\\cmd.exe”
3、如下所示的 cmdStr3、cmdStr4 中要想打开普通的 word 文件,如 txt、ppt、excel、pdf、png、mp4 等等都是要借用 cmd.exe 程序才能打开的,否则会报错说找不到路径
4、cmdStr3、cmdStr4 这种直接指明参数文件,而没有指定打开此文件的程序时,默认会使用文件的关联程序进行打开,如下面的“E:/wmx/演讲稿.pptx”,如果关联的是Office则用Office打开,如果关联的是WPS,则用WPS打开。
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* Created by Administrator on 2018/6/27 0027.
* 杀进程测试类
*/
public class ProcessTest {
public static void main(String[] args) {
try {
String cmdStr1 = "D:\\PotPlayer\\PotPlayerMini.exe E:/wmx/zl2.mp4";
String cmdStr2 = "D:\\PotPlayer\\GPaoPao.exe";
String cmdStr3 = "cmd /c E:/wmx/演讲稿.pptx";
String cmdStr4 = "cmd /c E:/wmx/zl2.mp4";
Runtime runtime = Runtime.getRuntime();
Process process = runtime.exec(cmdStr4);
Thread.sleep(5000);
if (process.isAlive()) {
process.destroy();
}
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
5、加了"cmd /c”前缀就表示会打开cmd.exe程序来执行后面的命令,所以cmdStr3、cmdStr4在执行之后任务管理器就会有cmd进程,于是当Process调用destory()或者destroyForcibly()杀子进程的时候,杀死的只是cmd.exe,而真正需要结束的是打开mp4文件的 PotPlayer,或者是打开 pptx 文件的 WPS 程序
解决方式 1
1、如果能准确知道打开文件的程序路径,则建议直接写上程序路径。如下所示直接指定打开 .docx 文件的 Office 程序,这样结束子进程的时候就不会有问题了。
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* Created by Administrator on 2018/6/27 0027.
* 杀进程测试类
*/
public class ProcessTest {
public static void main(String[] args) {
try {
/** 当路径中有空格时,则应该程序和参数分开写*/
String[] paramArr = new String[2];
paramArr[0] = "C:\\Program Files (x86)\\Microsoft Office\\root\\Office16\\WINWORD.EXE";
paramArr[1] = "E:\\wmx\\Map_in-depth.docx";
Runtime runtime = Runtime.getRuntime();
Process process = runtime.exec(paramArr);
Thread.sleep(15000);
if (process.isAlive()) {
process.destroy();
}
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
解决方式 2
1、因为很多时候可能并不知道某个文件到底将来会用什么程序打开,如.docx文件可能是Office打开,也可能是WPS打开
2、而且形如Office与WPS安装的路径也不一定知道或者路径不固定,此时采用杀进程树的方式来解决。
3、因为加"cmd /c"前缀使用cmd.exe程序来执行命令时,是先打开了cmd程序,然后打开的文件关联程序打开文件,后者属于cmd.exe程序的子进程,它们属于同一个进程树,而cmd提供了杀死整个进程树的命令,taskkill
4、taskkill /PID xxx /F /T:taskkill表示杀死任务,/PID表示按进程的pid进行关闭,xxx表示进程pid值,/F表示强制终止进程,/T表示终止指定的进程和由它启用的子进程。
5、这种方式时借助 JNA 的 API 会更加方便,需要的 Jar 包以及相关知识可以参考《JNA 理论详解 1》、《JNA 实战 1》
6、使用 JNA 则先要导入它的两个开发包
package com.lct.utils;
import com.sun.jna.Pointer;
import com.sun.jna.platform.win32.Kernel32;
import com.sun.jna.platform.win32.WinNT;
import java.lang.reflect.Field;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.UnknownHostException;
/**
* Created by Administrator on 2018/6/26 0026.
* 系统工具类
*/
public class SystemUtils {
/**
* 杀死指定进程数,即包括process进程的所有子进程
*
* @param process
*/
public static void killProcessTree(Process process) {
try {
if (process != null && process.isAlive()) {
Field f = process.getClass().getDeclaredField("handle");
f.setAccessible(true);
long handl = f.getLong(process);
Kernel32 kernel = Kernel32.INSTANCE;
WinNT.HANDLE handle = new WinNT.HANDLE();
handle.setPointer(Pointer.createConstant(handl));
int ret = kernel.GetProcessId(handle);
Long PID = Long.valueOf(ret);
String cmd = "cmd /c taskkill /PID " + PID + " /F /T ";
Runtime rt = Runtime.getRuntime();
Process killPrcess = rt.exec(cmd);
killPrcess.waitFor();
killPrcess.destroyForcibly();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
import com.lct.utils.SystemUtils;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* Created by Administrator on 2018/6/27 0027.
* 杀进程测试类
*/
public class ProcessTest {
public static void main(String[] args) {
try {
String cmdStr4 = "cmd /c E:\\wmx\\Map_in-depth.docx";
Runtime runtime = Runtime.getRuntime();
Process process = runtime.exec(cmdStr4);
/** 休眠5秒后关闭子进程树*/
Thread.sleep(5000);
SystemUtils.killProcessTree(process);
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
这样就能杀死打开.docx文件的程序了,亲测有效!
ProcessBuilder 创建系统进程
1、public final class java.lang.ProcessBuilder extends Object :此类用于创建操作系统进程。
2、每个 ProcessBuilder 实例管理一个进程属性集,start() 方法利用这些属性创建一个新的 Process 实例。start() 方法可以从同一实例重复调用,以利用相同的或相关的属性创建新的子进程。
3、此类不是同步的,如果多个线程同时访问一个 ProcessBuilder,而其中至少一个线程从结构上修改了其中一个属性,它必须保持外部同步。
4、此类从 JDK 5 开始推出,用于替代之前的 Runtime 类。
5、注意事项:使用 Runtime 时,只要程序和参数的路径中没有空格是可以写到一个参数中的,默认按空格分隔;但是ProcessBuilder 设置程序和参数必须分开写,否则报错:“系统找不到指定的文件”。
启动使用默认工作目录和环境的新进程很容易:
Process p = new ProcessBuilder("myCommand", "myArg").start();
构造函数
一共重载了两个构造函数,一个参数是 list,一个是 array。
public ProcessBuilder (List<String> command)
1)利用指定的操作系统程序和参数构造一个进程生成器
2)此构造方法不会 制作一份 command 列表的副本
3)不会检查 command 是否为一个有效的操作系统命令。
4、参数:command - 包含程序及其参数的列表 ,程序和参数必须分开写
5、抛出: NullPointerException - 如果参数为 null
public ProcessBuilder (String... command)
1、利用指定的操作系统程序和参数构造一个进程生成器。
2、一个有用的构造方法,它将进程生成器的命令设置为与 command 数组包含相同字符串的字符串列表,且顺序相同。
3、不会检查 command 是否为一个有效的操作系统命令。
4、参数:command - 包含程序及其参数的字符串数组,程序和参数必须分开写
方法概述
更多 API 可以参考官方文档:Java 8 中文版 - 在线API手册 - 码工具
public Process start() throws IOException
1)使用此进程生成器的属性启动一个新进程
2)此方法检查命令是否为一条有效的操作系统命令,哪条命令是有效的呢?这取决于系统,但至少命令必须是非空字符串的非空列表。
3)返回:一个新的 Process 对象,用于管理子进程
API 测试
启动 windows 自带的计算器
1、在 C:/windows/System32 目录下面,windows 系统自带了很多应用程序,如记事本、计算器、画图、shutdown 等等。
2、下面演示调用系统程序,不加参数,新开的程序就相当于一个子进程。
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;
/**
* Created by Administrator on 2019/2/26 0026.
*/
public class APITest {
private static final Logger logger = Logger.getGlobal();//日志记录器
public static void main(String[] args) {
try {
List<String> paramList = new ArrayList<>();
//启动 windows 的计算器程序,第一个参数必须是可执行程序
paramList.add("C:\\Windows\\System32\\calc.exe");
/** 创建ProcessBuilder对象,设置指令列表*/
ProcessBuilder processBuilder = new ProcessBuilder(paramList);
logger.info("启动子进程...");
Process process = processBuilder.start();
logger.info("子进程启动完成...");
} catch (IOException e) {
e.printStackTrace();
}
}
}
启动 Potplayer 播放一个指定的视频文件
1、各种安装的软件都可以进行打开,这里比如安装好的视频播放器,我这里安装的是 PotPlayer 播放器,路径为:D:\PotPlayer\PotPlayerMini.exe
2、很多程序是支持在 cmd 调用时传递参数的,如 chrome.exe 浏览器等等,potPlayer 也可以在调用时指定参数。
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;
/**
* Created by Administrator on 2019/2/26 0026.
*/
public class APITest {
private static final Logger logger = Logger.getGlobal();//日志记录器
public static void main(String[] args) {
try {
List<String> paramList = new ArrayList<>();
//启动 windows 上安装好的 potplayer 播放器,设置 exe 路径,路径必须存在
//第一个参数必须是可执行程序
paramList.add("D:\\PotPlayer\\PotPlayerMini.exe");
//设置 potplayer 将要播放的文件,也就是给 potplayer 传递的参数,文件必须存在
paramList.add("E:\\Study_Note\\Spring Cloud\\视频\\第1天\\4 服务提供者与服务消费者.avi");
/** 创建ProcessBuilder对象,设置指令列表*/
ProcessBuilder processBuilder = new ProcessBuilder(paramList);
logger.info("启动子进程...");
Process process = processBuilder.start();
logger.info("子进程启动完成...");
for (int i = 5; i > 0; i--) {
logger.info((i) + " 秒后结束子进程...");
Thread.sleep(1000);
}
process.destroyForcibly();//强行终结开启的子进程
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
使用 cmd 命令 ping 目标主机,然后将结果信息存放在指定文件中
ping 目标主机:属于 windows 指令,如果自己的电脑和目标主机之间网络通畅,则会返回成功信息,否则返回失败信息。
import javax.swing.filechooser.FileSystemView;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;
/**
* Created by Administrator on 2019/2/26 0026.
*/
public class APITest {
private static final Logger logger = Logger.getGlobal();//日志记录器
public static void main(String[] args) throws IOException {
List<String> paramList = new ArrayList<>();
//使用 cmd 命令 ping 远程主机
//第一个参数必须是可执行程序,cmd也是一个可执行程序,位于 C:/Windows/System32目录下
paramList.add("cmd");
paramList.add("/c");
paramList.add("ping www.taobao.com");//也可以是ip,如 paramList.add("ping 114.114.114.114");
/** 创建ProcessBuilder对象,设置指令列表*/
ProcessBuilder processBuilder = new ProcessBuilder(paramList);
//获取桌面路径,如:C:\Users\Administrator.SC-201707281232\Desktop
File desktopFile = FileSystemView.getFileSystemView().getHomeDirectory();
//创建子进程输出信息的存放文件,文件不存在时,会自动创建
File outputFile = new File(desktopFile, "output.txt");
//返回此流程构建器的标准输出目标,意思是将来输出信息全部放在这个目标中
processBuilder = processBuilder.redirectOutput(outputFile);
processBuilder.start();//启动进程构建器
logger.info("子进程执行消息存放在:" + processBuilder.redirectOutput().file().getPath());
}
}
4)directory(File directory) 设置此进程构建器的工作目录,注意它是为参数列表中程序参数设置工作目录,而不是程序的工作目录,下面通过实例理解。
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;
/**
* Created by Administrator on 2019/2/26 0026.
*/
public class APITest {
private static final Logger logger = Logger.getGlobal();//日志记录器
public static void main(String[] args) throws IOException {
List<String> paramList = new ArrayList<>();
paramList.add("C:/windows/system32/notepad.exe");//启动系统的记事本,第一个参数必须是可执行程序
//打开 E:/wmx/test/123.txt 文件,文件必须存在
//应该在 start 之前切换工作目录到 E:/wmx/test
paramList.add("123.txt");
ProcessBuilder pb = new ProcessBuilder(paramList);
//切换进程构建器的工作目录到 E:/wmx/test 下,这样上面才能简写 123.txt时找到文件
//必须注意的是 directory 是为paramList中的参数 "123.txt" 设置的工作目录,,而不是程序 "C:/windows/system32/notepad.exe"
//程序要么就是写全路径,要么就是配置系统环境变量才能写简称,如 calc、notepad 等
pb.directory(new File("E:/wmx/test/"));
pb.start();//开启子进程
logger.info(pb.directory() + ":当前工作目录");//默认情况下,未设置时,输出为 null
}
}
路径空格说明
1)如下使用 Office 的 Word.exe 打开一个 docx 文件,Word.exe 与 .docx 文件路径有空格都是可以的,Word.exe 与 .docx 分别占一个元素:
正确:[cmd, /c, C:\Program Files (x86)\Microsoft Office\root\Office16\WINWORD.EXE, E:\wmx\MR软件说明书.docx]
2)如下使用 PotPlayer 播放一个 mp4文件,mp4文件路径含有空格,可以将空格路径作为一个元素,也可以将路径空格位置切分为多个元素:
正确:[cmd, /c, D:\PotPlayer\PotPlayerMini.exe, E:\wmx\w s\sh k\8.mp4]
正确:[cmd, /c, D:\PotPlayer\PotPlayerMini.exe, E:\wmx\w, s\sh, k\8.mp4]
3)如下的本意是想让 Chrome 浏览器全屏模式打开一个地址,因为 --kiosk 与后面的浏览器地址一共是两个参数,所以写法一正确,写法二错误。
正确:[cmd, /c, C:/Program Files (x86)/Google/Chrome/Application/chrome.exe, --kiosk, http://www.baidu.com]
错误:[cmd, /c, C:/Program Files (x86)/Google/Chrome/Application/chrome.exe, --kiosk http://www.baidu.com]