1. 发现问题
工作中使用java程序导数据,
该导数据的java程序是通过另外一个java程序通过Runtime.getRuntime().exec() 启动的。
总是会出现数据传输到一半就卡住了,不会继续传输,也不会失败
纹丝不动,不生不死
2. 定位问题
查看java进程pid :jps -lm
查看java进程占用资源情况: top -p pid
再查看该java进程中线程资源使用情况: top -H -p pid
发现该java程序,一点资源也不占用,0cpu 0mem
也就是该java程序并没有在运行,被挂起了,继续分析
查看Thread Dump
jstack 2714 查看进程2714的stack
这里省略了其它不相关的信息
可以看到两个线程处于TIME_WAITING,一个处于RUNNABLE
这里需要注意Java的线程状态和操作系统层面的线程状态有点不一样
操作系统层面一个线程有Ready和Running状态,分别表示就绪和运行状态,但是Java只有RUNNABLE状态
操作系统虽然从cpu角度认为该线程已经阻塞,不再占用cpu,
但是Java认为程序虽然不再占用cpu,但是可能还占用着IO,或网络,所以还是认定程序是RUNNABLE,和BLOCKED要区分开
看前面几行信息
java.lang.Thread.State: RUNNABLE
at java.io.FileOutputStream.writeBytes(Native Method)
隔了一段时间,重新打印Thread Dump,发现信息完全一样
到这里几乎可以判断是 IO 阻塞了,而且根据后面的的信息,可以推断出是LOG日志造成的IO阻塞
注释掉程序的Console日志模块配置,还是阻塞
上网查了半天,原来是Process p = Runtime.getRuntime().exec()方法挖的坑
该类提供三个流来处理输入输出错误,注:输入输出都是从父进程角度观察
getInputStream() 标准输入
getOutputStream() 标准输出
getErrorStream() 标准错误
不能同步处理这三个流,否则可能会出现某个流没有被及时处理,一直占用缓冲区,
等缓冲区满了,无法写入数据,导致线程阻塞,进程卡死,对外现象就是进程无法停止,也不占资源,什么反应也没有
3. 解决问题
不要同步处理,可以另外创建线程来处理缓冲区的流,代码
Thread t1 = new Thread(new ProcessStreamRunable(pro.getInputStream(), "INPUT"));
Thread t2 = new Thread(new ProcessStreamRunable(pro.getErrorStream(), "ERROR"));
t1.start();
t2.start();
完工
额外补充一段:
==========================补充分割线====================================
java的线程转储可能很多,不便于观察,可以重定向到文件,慢慢找,
也可以如下查找
先使用top -H -p pid 找到操作系统上的线程ID, 第一列就是
jstack 2714 | grep -A 10 a9f
-A 10 表示往后多打印10行
a9f 是操作系统线程ID,对应的16进制,就是nid,线程ID通过上面的top命令查看
这样就可以打印指定线程的转储信息了,这可以定位某个线程的状态,如可以查看占用资源很多的线程到底在干什么
顺便给出linux打印16进制的命令
printf "%x\n" 2719 十进制 -> 十六进制
printf "%d\n" 0xa9f 十六进制 -> 十进制
Thread Dump字段意思
prio : 表示线程优先级,就是Thread中定义的这个。Thread类
os_prio : 表示操作系统级别的优先级
tid : 表示Java内的线程ID,同样在Thread类中
nid:表示操作系统级别的线程ID的16进制形式