Runtime.getRuntime().exec 路徑包含空格的解決
Runtime.getRuntime().exec 路徑包含空格
1. 現(xiàn)象
java代碼通過(guò)Runtime.getRuntime().exec刪除linux上的目錄,如果路徑信息不包含空格沒(méi)有問(wèn)題,但是有了空格,雖沒(méi)有報(bào)錯(cuò),但執(zhí)行沒(méi)有效果,文件夾刪不掉。
2. 原因
Runtime.getRuntime().exec語(yǔ)法不支持空白符和管道符"|"
不支持空白符和管道符"|"的例子:
//包含空格 String cmd = "rm -fr test 1"; // 包含管道符 String cmd2 = "netstat -antup | grep ftp"; Process ps = Runtime.getRuntime().exec(cmd); ps.waitFor();
解決辦法
如果直接在命令行執(zhí)行的話(huà),可以通過(guò)給完整的字符帶上單引號(hào)或雙引號(hào),java代碼用法不適用。
[root@EMS3 ~]# rm -rf "/root/test 1"
使用重載函數(shù)即可:
public Process exec(String cmdarray[])
String cmd = "rm -fr test 1"; // 或 String cmd2 = "netstat -antup | grep ftp"; Process ps = Runtime.getRuntime().exec(new String[]{"/bin/sh", "-c", cmd}); ps.waitFor(); ...
其中-c表示cmd是一條命令
/bin/sh -c注意事項(xiàng)
由于增加了-c 參數(shù),并指定shell類(lèi)型,因此需要確認(rèn)shell的類(lèi)型。因此根據(jù)實(shí)際環(huán)境的shell類(lèi)型。
/bin/sh是什么?
shell編程是以"#“為注釋?zhuān)珜?duì)”#!/bin/sh"卻不是。"#!/bin/sh"是對(duì)shell的聲明,說(shuō)明你所用的是那種類(lèi)型的shell及其路徑所在(#! /bin/sh 是指此腳本使用/bin/sh來(lái)解釋執(zhí)行,#!是特殊的表示符,其后面跟的是解釋此腳本的shell的路徑)。如果沒(méi)有聲明,則腳本將在默認(rèn)的shell中執(zhí)行,默認(rèn)shell是由用戶(hù)所在的系統(tǒng)定義為執(zhí)行shell腳本的shell。
如果腳本被編寫(xiě)為在Kornshell ksh中運(yùn)行,而默認(rèn)運(yùn)行shell腳本的為C shell csh,則腳本在執(zhí)行過(guò)程中很可能失敗。所以建議大家就把"#!/bin/sh"當(dāng)成C 語(yǔ)言的main函數(shù)一樣,寫(xiě)shell必須有,以使shell程序更嚴(yán)密。
Runtime.getRuntime().exec()產(chǎn)生阻塞的2個(gè)陷阱
背景
相信做java服務(wù)端開(kāi)發(fā)的童鞋,經(jīng)常會(huì)遇到Java應(yīng)用調(diào)用外部命令啟動(dòng)一些新進(jìn)程來(lái)執(zhí)行一些操作的場(chǎng)景,這時(shí)候就會(huì)使用到Runtime.getRuntime().exec(),然而這個(gè)方法如果不謹(jǐn)慎很容易掉進(jìn)陷阱。
我們的一個(gè)PDF轉(zhuǎn)碼服務(wù)就踩到了這個(gè)坑掉進(jìn)陷阱,這個(gè)轉(zhuǎn)碼服務(wù)主要是對(duì)pdf進(jìn)行加密和轉(zhuǎn)碼成swf。這個(gè)服務(wù)上線(xiàn)后大部分時(shí)間都是穩(wěn)定運(yùn)行的,但是隔一段時(shí)間就會(huì)死掉,然后人肉手動(dòng)重啟一下服務(wù)就復(fù)活了??戳巳罩荆袝r(shí)候有一堆關(guān)于pdf轉(zhuǎn)碼過(guò)程的錯(cuò)誤日志,有時(shí)候死掉的時(shí)候什么日志也沒(méi)輸出。這時(shí)候猜測(cè)可能是pdf轉(zhuǎn)碼異常導(dǎo)致應(yīng)用掛掉的{因?yàn)檫@個(gè)轉(zhuǎn)碼服務(wù)一直是單線(xiàn)程在工作}。更深的原因大家也空沒(méi)去找。反正運(yùn)營(yíng)反饋上傳的pdf一直處在轉(zhuǎn)碼中很久了,一兩天了還在轉(zhuǎn)碼中,于是開(kāi)發(fā)就手動(dòng)重啟下服務(wù)。是的你沒(méi)看過(guò),就是一兩天才發(fā)現(xiàn),我們的業(yè)務(wù)監(jiān)控沒(méi)作上去,因?yàn)橄鄬?duì)迭代任務(wù),這都算不緊急的事情了。
后來(lái)運(yùn)營(yíng)反饋pdf問(wèn)題次數(shù)增多了,于是寫(xiě)了個(gè)腳本,定時(shí)去檢查日志最后的更新時(shí)間,發(fā)現(xiàn)日志超過(guò)一個(gè)小時(shí)沒(méi)更新就重啟應(yīng)用,重啟腳本沒(méi)問(wèn)題,問(wèn)題是應(yīng)用重啟后,日志中出現(xiàn)了一堆的找不到要執(zhí)行的命令。目前也不知道為什么通過(guò)腳本去重啟動(dòng)應(yīng)用后,應(yīng)用找不到要執(zhí)行的命令。有知道的可以告知下。
終于某一天,應(yīng)用又死掉了,看了下數(shù)據(jù)庫(kù)堆積了將近2000個(gè)待轉(zhuǎn)的文件??戳讼聭?yīng)用日志打了exe()后就再也沒(méi)內(nèi)容了,于是下狠心花了半天時(shí)間來(lái)研究下Runtime.getRuntime().exe()找了下原因,最終解決了這個(gè)問(wèn)題。
關(guān)于Runtime.getRuntime().exe()
根據(jù)jdk官方文檔描述,每個(gè)Java應(yīng)用都存在一個(gè)而Runtime的單例實(shí)例。這個(gè)類(lèi)Runtime類(lèi)封裝了應(yīng)用運(yùn)行時(shí)的環(huán)境,通過(guò)這個(gè)類(lèi)我們的java應(yīng)用可以與其運(yùn)行環(huán)境相連接。
1、java應(yīng)用無(wú)法創(chuàng)建自己的Runtime實(shí)例,只能通過(guò)Runtime.getRuntime()來(lái)取得當(dāng)前JVM的運(yùn)行時(shí)環(huán)境,這也是在Java中唯一一個(gè)得到運(yùn)行時(shí)環(huán)境的方法。一旦得到了一個(gè)當(dāng)前的Runtime對(duì)象的引用,就可以調(diào)用Runtime對(duì)象的方法來(lái)控制Java虛擬機(jī)的狀態(tài)和行為。
2、Runtime中的exit方法是退出當(dāng)前JVM的方法,System類(lèi)中的exit實(shí)際上也是通過(guò)調(diào)用Runtime.exit()來(lái)退出JVM的,這里說(shuō)明一下Java對(duì)Runtime返回值的一般規(guī)則(后邊也提到了),0代表正常退出,非0代表異常中止。
3、Runtime具有的詳細(xì)方法請(qǐng)參考官方api,http://docs.oracle.com/javase/8/docs/api/
阻塞陷阱之Runtime.getRuntime().exe()的返回值Process
應(yīng)用在調(diào)用Runtime.getRuntime().exec()這個(gè)方法會(huì)創(chuàng)建一個(gè)本機(jī)進(jìn)程并返回Process子類(lèi)的一個(gè)實(shí)例。該實(shí)例可用來(lái)控制該進(jìn)程并獲得其相關(guān)信息。Process類(lèi)提供了執(zhí)行從進(jìn)程輸入、執(zhí)行輸出到進(jìn)程、等待進(jìn)程完成、檢查進(jìn)程的退出狀態(tài)以及銷(xiāo)毀(殺掉)進(jìn)程的方法。
官方文檔解釋了創(chuàng)建進(jìn)程的方法可能無(wú)法針對(duì)某些本機(jī)平臺(tái)上的特定進(jìn)程很好地工作,比如,本機(jī)窗口進(jìn)程,守護(hù)進(jìn)程,Microsoft Windows 上的 Win16/DOS 進(jìn)程,或者 shell腳本。創(chuàng)建的子進(jìn)程沒(méi)有自己的終端或控制臺(tái)。它的所有標(biāo)準(zhǔn) io(即 stdin、stdout 和 stderr)操作都將通過(guò)三個(gè)管道重定向到父進(jìn)程(也就是調(diào)用者java應(yīng)用)。三個(gè)管道用于處理標(biāo)準(zhǔn)輸入流,標(biāo)準(zhǔn)輸出流,標(biāo)準(zhǔn)錯(cuò)誤流。子進(jìn)程在執(zhí)行過(guò)程中,會(huì)不斷的向JVM寫(xiě)入標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯(cuò)誤輸出。java應(yīng)用可以通過(guò)Process 提供的getOutputStream()、getInputStream() 和 getErrorStream()來(lái)獲得子進(jìn)程輸入輸出信息。因?yàn)橛行┍緳C(jī)平臺(tái)僅針對(duì)標(biāo)準(zhǔn)輸入和輸出流提供有限的緩沖區(qū)大小,當(dāng)標(biāo)準(zhǔn)輸出或者標(biāo)準(zhǔn)錯(cuò)誤輸出寫(xiě)滿(mǎn)緩存池時(shí),程序無(wú)法繼續(xù)寫(xiě)入,子進(jìn)程無(wú)法正常退出。讀寫(xiě)子進(jìn)程的輸出流或輸入流迅速出現(xiàn)失敗,則可能導(dǎo)致子進(jìn)程阻塞,甚至產(chǎn)生死鎖。
當(dāng)調(diào)用Runtime.getRuntime().exe()后返回的Process對(duì)象除了可以多的三種輸入輸出流外,還有兩個(gè)常用的方法:
1、非阻塞方法exitValue()獲得子進(jìn)程退出的狀態(tài)值(0,正常退出,非0異常退出),需要注意的是調(diào)用這個(gè)方法程序會(huì)立即得到結(jié)果,如果子進(jìn)程沒(méi)有執(zhí)行完,調(diào)用這個(gè)方法會(huì)拋出IllegalThreadStateException,表示此 Process 對(duì)象表示的子進(jìn)程尚未終止。
2、阻塞方法 waitFor()導(dǎo)致當(dāng)前線(xiàn)程等待,直到子進(jìn)程結(jié)束并返回退出狀態(tài)。如果已終止該子進(jìn)程,此方法立即返回,如果沒(méi)有終止該子進(jìn)程,調(diào)用的線(xiàn)程將被阻塞,直到退出子進(jìn)程。
先看看我們轉(zhuǎn)碼服務(wù)這里的歷史代碼:
這段代碼,用同步的方法去讀取標(biāo)準(zhǔn)錯(cuò)誤輸出流即相當(dāng)于清空了錯(cuò)誤輸出流緩沖區(qū),然而正常的標(biāo)準(zhǔn)輸出流并沒(méi)有清空,按照上面的原理解釋?zhuān)枞脑蚩赡芫彤a(chǎn)生在這里。當(dāng)阻塞產(chǎn)生的時(shí)候jstack了一下線(xiàn)程棧信息如下圖所示。確實(shí)線(xiàn)程鎖在了讀取緩沖流上面了。
這種情況網(wǎng)上通用的解決方法就是異步開(kāi)兩個(gè)線(xiàn)程去讀取正常的輸出和錯(cuò)誤輸出流信息,清空緩沖區(qū),參考了大家的解決方法,下圖是修改后的方案,ProcessClearStream是一個(gè)異步線(xiàn)程,主要做的是將標(biāo)準(zhǔn)inputSream讀取完畢。
阻塞陷阱之子進(jìn)程阻塞
通過(guò)上面的代碼優(yōu)化后還是發(fā)現(xiàn)有轉(zhuǎn)碼阻塞的現(xiàn)象出現(xiàn),而且發(fā)現(xiàn)每次阻塞都出現(xiàn)在固定的幾個(gè)pdf上,測(cè)試發(fā)現(xiàn)重啟應(yīng)用后主要轉(zhuǎn)到那幾個(gè)特定的pdf時(shí)候,轉(zhuǎn)碼服務(wù)必掛無(wú)疑(通常一個(gè)pdf轉(zhuǎn)碼只需要幾十秒,而這個(gè)阻塞持續(xù)幾個(gè)小時(shí),不人為干預(yù)它就可能無(wú)限阻塞下去)。所以重啟應(yīng)用也不管用了,只能跳過(guò)這幾個(gè)pdf應(yīng)用才行,于是在測(cè)試環(huán)境測(cè)試這幾個(gè)pdf,每次阻塞的時(shí)候再jstack發(fā)現(xiàn)應(yīng)用阻塞在proc.waitFor(),再也沒(méi)其他錯(cuò)誤信息了。查看了官方api,Process的waitFor方法本身會(huì)阻塞直到子進(jìn)程正?;虍惓M顺?,到這里,應(yīng)該可以推斷是子進(jìn)程無(wú)限阻塞下去了,導(dǎo)致waitFor一直阻塞中。為了驗(yàn)證這個(gè)推斷,直接在終端kill掉這個(gè)子進(jìn)程,然后再查看日志,發(fā)現(xiàn)轉(zhuǎn)碼服務(wù)又繼續(xù)工作了。
有了上面的結(jié)論,一個(gè)簡(jiǎn)單的思路也就有了,我需要檢測(cè)子進(jìn)程狀態(tài),如果發(fā)現(xiàn)子進(jìn)程有阻塞狀態(tài)就kill掉(因?yàn)檫@個(gè)轉(zhuǎn)碼腳本比較老,要拿他的堆棧信息比較麻煩,所以kill掉是最簡(jiǎn)單直接暴力效率高的方法)。將這個(gè)想法和同事聊了下,萬(wàn)能的Java肯定可以干這事,大概思路就啟動(dòng)個(gè)線(xiàn)程去監(jiān)控process的waitFor的阻塞時(shí)間,超過(guò)設(shè)置時(shí)間,就干掉了子進(jìn)程,這不是Java線(xiàn)程池ExecutorService類(lèi)配合Future接口來(lái)干的事情么。同事按照這個(gè)思路網(wǎng)上找了下現(xiàn)成的代碼,于是照著這個(gè)這個(gè)方法抄襲了一下,下面貼下關(guān)鍵的代碼:
當(dāng)waitFor超時(shí)線(xiàn)程中斷的的時(shí)候再調(diào)用process的destroy()銷(xiāo)毀子進(jìn)程。這個(gè)方案上線(xiàn)后,截至目前一周多時(shí)間轉(zhuǎn)碼服務(wù)穩(wěn)定運(yùn)行,沒(méi)在出現(xiàn)以前的服務(wù)死掉的情況。
我們業(yè)務(wù)中當(dāng)檢測(cè)到超時(shí)退出后就重置任務(wù)狀態(tài)為失敗(算是降級(jí)吧),導(dǎo)致這種pdf轉(zhuǎn)碼子進(jìn)程阻塞的一般是pdf本身不太標(biāo)準(zhǔn),而這個(gè)轉(zhuǎn)碼工具不能很好的兼容處理這些pdf,后面把這些有問(wèn)題的pdf重新轉(zhuǎn)成標(biāo)準(zhǔn)pdf上傳測(cè)試即可以正常轉(zhuǎn)碼。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
SpringBoot項(xiàng)目Jar包如何瘦身部署的實(shí)現(xiàn)
這篇文章主要介紹了SpringBoot項(xiàng)目Jar包如何瘦身部署的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-09-09劍指Offer之Java算法習(xí)題精講二叉樹(shù)與N叉樹(shù)
跟著思路走,之后從簡(jiǎn)單題入手,反復(fù)去看,做過(guò)之后可能會(huì)忘記,之后再做一次,記不住就反復(fù)做,反復(fù)尋求思路和規(guī)律,慢慢積累就會(huì)發(fā)現(xiàn)質(zhì)的變化2022-03-03JavaSE圖像驗(yàn)證碼簡(jiǎn)單識(shí)別程序詳解
這篇文章主要為大家詳細(xì)介紹了JavaSE圖像驗(yàn)證碼簡(jiǎn)單識(shí)別程序,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-01-01Java 指定微信好友自動(dòng)發(fā)送消息的實(shí)現(xiàn)示例
這篇文章主要介紹了Java 指定微信好友自動(dòng)發(fā)送消息的實(shí)現(xiàn)示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-10-10SpringBoot配置使Mybatis打印SQL執(zhí)行時(shí)的實(shí)際參數(shù)值操作
這篇文章主要介紹了SpringBoot配置使Mybatis打印SQL執(zhí)行時(shí)的實(shí)際參數(shù)值操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-12-12基于@Autowierd(自動(dòng)裝配)的使用說(shuō)明
這篇文章主要介紹了@Autowierd(自動(dòng)裝配)的使用說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08解決fastjson泛型轉(zhuǎn)換報(bào)錯(cuò)的解決方法
這篇文章主要介紹了解決fastjson泛型轉(zhuǎn)換報(bào)錯(cuò)的解決方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-11-11Java多種經(jīng)典排序算法(含動(dòng)態(tài)圖)
排序算法是老生常談的了,但是在面試中也有會(huì)被問(wèn)到,例如有時(shí)候,在考察算法能力的時(shí)候,不讓你寫(xiě)算法,就讓你描述一下,某個(gè)排序算法的思想以及時(shí)間復(fù)雜度或空間復(fù)雜度。我就遇到過(guò),直接問(wèn)快排的,所以這次我就總結(jié)梳理一下經(jīng)典的十大排序算法以及它們的模板代碼2021-04-04