JAVA中的Runtime启动子进程并杀掉

2023-11-19

一、前言

最近在项目中需要将一个java工程打成一个jar包,并在运行jar包后启动通过java中的runtime类来启动一个nodejs的服务,在做的过程中遇到了一些不小的坑,下面就将其记录下来。

二、Runtime类

Runtime.class是java.lang包下的一个类,在开发Android过程中我们有时需要与jni进行交互,我们使用System.load来加载so库,其底层也是调用了Runtime中的loadLibrary0方法,这个和今天的没有太多关系就不讨论了。下面我们先来看一个简单的在使用Runtime执行命令行的代码:

    @Test
    public void test1() throws IOException, InterruptedException, ExecutionException{
        Process process = Runtime.getRuntime().exec("cmd.exe /c dir");
        Future<String> future = executor.submit(new WatchProcess(process, WatchType.NORMAL));
        future.get();
    }

输出结果:
 ������ E �еľ�û�б�ǩ��
 �������� 0007-F0C8

 E:\MyEclipse2016\oa ��Ŀ¼

2016/12/30 ����  ���� 11:35    <DIR>          .
2016/12/30 ����  ���� 11:35    <DIR>          ..
2017/02/28 �ܶ�  ���� 08:01             1,308 .classpath
2017/02/28 �ܶ�  ���� 07:51             1,236 .project
2017/02/28 �ܶ�  ���� 07:42    <DIR>          .settings
2016/12/30 ����  ���� 11:35    <DIR>          logs
2017/03/07 �ܶ�  ���� 08:11             7,036 pom.xml
2016/12/31 ����  ���� 06:29    <DIR>          src
2017/02/28 �ܶ�  ���� 07:42    <DIR>          target
               3 ���ļ�          9,580 �ֽ�
               6 ��Ŀ¼ 301,133,688,832 �����ֽ�

上面的代码很简单,我们通过runtime执行了一个dir的指令,打印当前目录的文件列表,结果由于编码的问题出现了乱码,但是我们得到了想要的结果。具体的指令我们下面介绍,这里先介绍runtime类。

在代码的最开始,我们通过Runtime.getRuntime()方法获取了一个Runtime类的实例,我们进入Runtime的源码可以看见,这是一个静态方法可以获取一个单例的Runtime实例,源码如下:

java.lang.Runtime.class
line:58    private static Runtime currentRuntime = new Runtime();

line:91   public static Runtime getRuntime() {
            return currentRuntime;
        }

获取了实例后,我们调用Runtime的exec方法运行命令,这个方法有好几个重载的方法,源码如下:

line:419    public Process exec(String command) throws IOException {
                return exec(command, null, null);
            }
line:461    public Process exec(String command, String[] envp) throws IOException {
                return exec(command, envp, null);
            }
line:515    public Process exec(String command, String[] envp, File dir)
            throws IOException {
            if (command.length() == 0)
                throw new IllegalArgumentException("Empty command");

            StringTokenizer st = new StringTokenizer(command);
            String[] cmdarray = new String[st.countTokens()];
            for (int i = 0; st.hasMoreTokens(); i++)
                cmdarray[i] = st.nextToken();
            return exec(cmdarray, envp, dir);
        }
        ...

方法不止上面的几个,我们就不一一列举了,第一个参数是我们执行的命令和参数,例如cmd.exe/nodepad.exe这些都是命令。第二个是当前运行进程的环境,第三个是工作目录。

当我们调用该方法后会返回一个Process的对象,该类是一个抽象类,为我们提供了几个方法来获取我们的我们进程的信息,如下:

  1. getOutputStream() 获取一个输出流连接子进程。
  2. getInputStream() 获取一个输入流,我们可以获取控制的信息
  3. getErrorStream() 获取一个输入流,不过该输入流是错误信息的,例如执行命令找不到就会通过它返回
  4. waitFor() 该方法可以阻塞线程,直到命令结束
  5. exitValue() 获取一个int类型的退出码,一般0为正常退出,例如调用System.exit(0)
  6. destroy() 杀掉子进程

介绍了上面的方法,那我们就来看一下如何打印我们的日志,并同时打印多个日志,此处有个坑,当我们使用BufferedReader的形式打印控制台信息,result = reader.readLine()) != null这样的退出方式会有个问题,它会一直阻塞,直到进程结束,所以我们为了能正常的打印多个日志,需要在子集成中去打印,为了方便管理,我们使用一个线程池,具体方法如下所示:

 //创建一个固定大小的线程池用于执行子进程
 ExecutorService executor = Executors.newFixedThreadPool(3, new ExecutorFactory());

//我们为了能获取一些特征值时能返回,我们定义一个callable接口
class WatchProcess implements Callable<String> {

        private WeakReference<Process> reference;
        private WatchType type;

        public WatchProcess(Process p, WatchType type) {
            reference = new WeakReference<Process>(p);
            this.type = type;
        }

        @Override
        public String call() throws Exception {

            BufferedReader reader = null;
            Process process = reference.get();
            if (process == null)
                return "error";
            switch (type) {
            case ERROR:
                reader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
                break;
            case NORMAL:
                reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
                break;
            default:
                reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
                break;
            }
            return logEvent(reader);

        }
//如果不了解callable的可以先去看一下,该接口可以让我们将其传入线程次执行并返回一个结果。
//接下来就是打印数据
private String logEvent(BufferedReader reader) throws IOException{

            String result = null;
            W: while ((result = reader.readLine()) != null) {
                System.out.println(result);
                //这里定义一个结束flag
                if (result.toLowerCase().contains("app is end")) {
                    result = "success";
                    break W;
                }
            }
            reader.close();
            return result;
        }
//为了打印不同的输出等级,我们定义了一个枚举类,这个不重要
enum WatchType {
    ERROR, NORMAL,BEGIN,OUTPUT;
}

//接下来我们就可以调用了,如前面代码所示,New一个callable放入线程池中
Future<String> future = executor.submit(new WatchProcess(process, WatchType.NORMAL));
//该方法会阻塞知道有返回值      
String result=future.get();

三、Runtime启动另一个子进程

上面我们做了一最简单的例子,我们只是运行了一个简单的命令,程序结束后调用destory方法就可以结束了,但是我们需要另外起一个进程怎么办了?下面就先介绍一下在Window和Mac中如何运行命令。

在Window系统中:

  我们经常通过在window的dos窗口中运行一些命令,在程序中我们可以使用Runtime来运行,如下:
  cmd.exe /c   该命令的意识是使用cmd来执行指令 /c 后跟的就是我们的指令,例如前面的dir

在Mac系统中:

 /bin/sh -c    该命令和window中的差不多,只是运行的环境不一样了
 /bin/bash -c   该命令也可以执行

在我们运行的过程中有时候会提示我们找不到某指令的情况,此时我们需要确定我们已经配置了系统的环境变量,在Window中的PATH,Mac系统中的/etc/profile文件中的PATH路径已经配置好了。

注意:在之前的测试过程中出现过在Mac的控制台中可以运行命令,但是在Eclipse中却无法运行,我们使用System.getEnv()打印系统的环境变量,发现Eclipse中的PATH和Mac中使用echo $PATH打印的不一样导致的问题,解决办法就是把$PATH的值拷贝一份配置到Eclipse的环境变量中。

当我们明白上面这些后,我们分别启动一个nodejs的服务,如下:

Window:
    cmd.exe /c node server.js

Mac:
    /bin/sh -c node server.js
在测试的过程中如上代码无法运行,需要定义一个字符数组传入exec方法如下:
String[] cmds={"/bin/sh -c","node server.js"}

运行如上代码后我们的服务就启动起来了,此时,我们就会遇到一个问题,我们如何关闭这个服务了?destory方法是不行的,即使使用System.exit(0)退出进程也不行,我们还是可以在控制台看到服务,下面是几个通过端口查看进程和杀掉进程的方法:

Widnow:
    查看端口被进程占用 netstat -ano|findstr portNum
    杀掉指定的进程    taskkill /pid yourPid -t -f
Mac:
    查看端口被进程占用 lsof -i tcp:portNum
    杀掉指定的进程     kill -9 yourPid

我们杀掉进程的方法就和上面的指令有关,我们通过runtime运行如上命令,然后杀掉指定pid的进程,下面给出一个Window的方法,实现方法比较粗糙,可以优化下:

    public static Integer killProcessByPort(Integer port){
        Runtime runtime = Runtime.getRuntime();
        Integer pid=null;
        try {
            Process process = runtime.exec("cmd.exe /c netstat -ano|findstr "+port);
            BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
            String result = null;

            W: while ((result = reader.readLine()) != null) {
                Pattern pattern=Pattern.compile("[\\s]+(\\S+)");
                Matcher matcher=pattern.matcher(result);
                while(matcher.find()){
                    try {
                         pid = Integer.parseInt(matcher.group(1));
                         break W;
                    } catch (Exception e) {

                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        if (pid!=null) {
            try {
                runtime.exec("cmd.exe /c taskkill /pid "+pid+" -t -f");
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return pid;
    }

我们通过传入一个端口号然后杀掉进程,Mac的方式和这个一样,只是运行的命令不一样而已,注意下字符串的截取pid就行。下面介绍一个在Mac中的特别方法:

    public void killProcess(Process process) {
        if(process != null){
            try {
                Field field = process.getClass().getDeclaredField("pid");

                field.setAccessible(true);
                int pid = field.getInt(process);
                pid+=1;
                Runtime.getRuntime.exec("/bin/sh kill -9 "+pid);
                return;
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
                return;
            }
        }   

这个方法在Mac上测试有效,可能我们发现在我们反射获得pid后进行了加1,这是通过测试发现获取到的进程是runtime.exec的返回的pid,但是启动服务的进程号比它大一,所以这么做(此方法不保证好使)。

还有一种在Window中的方法,但是实测没有通过所以放弃,而且需要导入别的东西:

            try {
                Field field = process.getClass().getDeclaredField("handle");
                field.setAccessible(true);

                Kernel32 kernel32 = Kernel32.INSTANCE;
                WinNT.HANDLE handle = new WinNT.HANDLE();
                handle.setPointer(Pointer.createConstant(field.getLong(process)));
                int pid = kernel32.GetProcessId(handle);

                RuntimeProcess killProcess = new RuntimeProcess("Taskkill /PID " + pid + " /F");
                killProcess.isStdInput = false;
                killProcess.execProcess();
                return;
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
                return;
            }

以上就是全部内容。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

JAVA中的Runtime启动子进程并杀掉 的相关文章

随机推荐

  • postMan使用技巧

    使用postMan调试接口 一些接口要实现登录才能访问 即要还token才能访问 一般登录后 拿到token才复制到其他接口下添加token变量 如些复制感觉是挺麻烦的 这时我们可以设置postman的全局变量 操作如下 添加调试环境和全局
  • 攻防实战

    0x00 前言 本文介绍了笔者在某次攻防演练中 如何从外网渗透到某车企的内网的详细过程 为了保护敏感信息 所有数据都已经脱敏 有些截图也不完整 请见谅 这次网络攻防演练分为两个阶段一共十四天 前七天是私有资源阶段 后七天是公共资源池阶段 共
  • 洛谷 P1056 排座椅 桶排序

    桶排序大法好 每次一看到这种范围小的题 本萌新就想用桶排 因为题目中的m n都小于1000 我们就可以定义两个1000的数组 表示每一行或每一列可以隔开几对讲话的童鞋 然后再定义两个1000的数组用来对前两个数组的值进行桶排序 再用通道总数
  • c51简单delay函数i的值跟延时的时间呈线性关系

    c51简单delay函数i的值跟延时的时间呈线性关系 一 简单delay函数 晶振频率 11 0592 MHZ define uchar unsigned char void Delay uchar i while i 二 证明 1 调试计
  • 深入浅出:前端浏览器缓存、清除缓存的几种方法

    介绍一个浏览器缓存机制的原理 https blog csdn net u014590757 article details 80140654 https www cnblogs com vajoy p 5341664 html https
  • 脊柱神经系统分布在哪里,脊柱神经系统分布图片

    脊柱两侧分出多少对脊神经 它构成了颈丛 臂丛 腰丛和骶丛 接着分布到全身 构成周围神经 31对脊神经 分别对应于31个脊髓节段上 即8颈 12胸 5腰 5骶 1尾 脊髓每个节段发出一对脊神经 颈丛由第1 4颈神经的前支构成 臂丛由第5 8颈
  • 使用Robot Framework实现多平台自动化测试

    基于Robot Framework Jenkins Appium Selenium Requests AutoIt等开源框架和技术 成功打造了通用自动化测试持续集成管理平台 以下简称 平台 显著提高了测试质量和测试用例的执行效率 01 设计
  • [1076]使用IntelliJ IDEA配置Tomcat

    文章目录 一 下载Tomcat 二 Tomcat环境变量配置 三 在IntelliJ IDEA配置Tomcat 一 下载Tomcat 1 进入官网Http tomcat apache org 选择download 下载所需要的Tomcat版
  • 单表查询

    简单sql查询语句应用 1 基本查询 查询Student表中全体学生的全部信息 select from Student go 查询全体学生的学号 姓名 select sno sname from Student go 2 查询时改变列标题的
  • Segmentation简记-Hybrid Task Cascade for Instance Segmentation

    创新点 1 Hybrid Task Cascade HTC 总结 基于maskrcnn和cascade rcnn的改进 论文中提出的几种cascade结构 a b结构就不细说了 分析一下c和d c较ab多出了一个结构 就是mask的casc
  • 基于Matlab的双向长短时记忆网络(BiLSTM)数据预测

    基于Matlab的双向长短时记忆网络 BiLSTM 数据预测 概述 在数据分析与预测领域 使用深度学习模型进行时间序列数据预测已成为一种常用的方法 本文将介绍如何使用Matlab中的双向长短时记忆网络 BiLSTM 模型对时间序列数据进行预
  • 超详细的计算机网络基础知识总结 第四章:网络层

    本文基于 王道计算机考研 计算机网络 其他文章 超详细的计算机网络基础知识 第一章 概述 超详细的计算机网络基础知识 第二章 物理层 超详细的计算机网络基础知识 第三章 数据链路层 超详细的计算机网络基础知识 第五章 传输层 超详细的计算机
  • 如何处理GPU上Error Number:700 an illegal memory access was encounter

    现象描述 GPU上网络运行过程中出现Error Number 700 an illegal memory access was encounter 原因分析 出现该现象 在框架稳定的背景下基本上可以确定是网络中有算子踩显存 因此CUDA上报
  • 毕业三年之际写给可能迷茫的你我

    工作半年 总感觉三分热度 难于沉淀 后劲不足 网上偶遇这篇文章 原来不止我一人是这样 遂转载此文以共勉 转自 http liugang ok iteye com blog 1969907 这些文字其实是在六月中下旬写的 算起来已经有好几个月
  • numpy生成等差等比数列

    文章目录 arange linspace logspace arange numpy arange start stop step dtype None 功能 Return evenly spaced values within a giv
  • java视频压缩

    项目开发中往往有一些时候可能会遇到一些上传视频 这时候我们如果上传一个手机拍摄的1分钟视频 大小110M 可能需要特别长的时间 这个时候就需要我们对视频进行压缩 虽然清晰度有所下降 但是对于一些特定的场景还是很有必要的 ffmpeg主要组成
  • 快速理解各类软件开发过程模型(瀑布模型、螺旋模型、喷泉模型等)

    快速理解各类开发过程模型 瀑布模型 螺旋模型 喷泉模型等等 1 开发模型 基础概念 2 结构化模型 瀑布模型 V模型 3 原型化模型 快速原型模型 增量模型 演化模型 螺旋模型 4 面向对象化模型 喷泉模型 5 其他模型 构建组装模型 6
  • 关于Wince进程外组件~ .

    转载自 http blog csdn net tttyd article details 6223594 Windows CE提供以下几种COM Base的应用 1 Minimal COM提供最基础的COM开发接口 API 每个com对象的
  • Spring中使用RedisTemplate操作Redis(spring-data-redis)

    https www cnblogs com songanwei p 9274348 html RedisTemplate如何检查一个key是否存在 return getRedisTemplate hasKey key 由一个问题 复习了一下
  • JAVA中的Runtime启动子进程并杀掉

    一 前言 最近在项目中需要将一个java工程打成一个jar包 并在运行jar包后启动通过java中的runtime类来启动一个nodejs的服务 在做的过程中遇到了一些不小的坑 下面就将其记录下来 二 Runtime类 Runtime cl