java Process的waitFor()

本文详细介绍了在Java程序中如何高效地调用ffmpeg进行MP3文件的转码,并通过合理利用标准输出流缓冲区避免了等待子进程完成的问题。同时,提供了一个简单的MP3文件操作类,包括获取文件大小、获取歌曲长度和向下转码成低品质文件的功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在编写Java程序时,有时候我们需要调用其他的诸如exe,shell这样的程序或脚本。在Java中提供了两种方法来启动其他程序:

     (1) 使用Runtime的exec()方法

     (2) 使用ProcessBuilder的start()方法

       Runtime和ProcessBulider提供了不同的方式来启动程序,设置启动参数、环境变量和工作目录。但是这两种方法都会返回一个用于管理操作系统进程的Process对象。这个对象中的waitFor()是我们今天要讨论的重点。

      来说说我遇到的实际情况:我想调用ffmpeg程序来对一首歌曲进行转码,把高音质版本的歌曲转为多种低码率的文件。但是在转码完成之后需要做以下操作:读取文件大小,写入ID3信息等。这时我们就想等转码操作完成之后我们可以知道。

如下这样代码

 

Java代码    收藏代码
  1. Process p = null;  
  2. try {  
  3.     p = Runtime.getRuntime().exec("notepad.exe");  
  4. catch (Exception e) {  
  5.     e.printStackTrace();  
  6. }  
  7. System.out.println("我想被打印...");  

 在notepad.exe被执行的同时,打印也发生了,但是我们想要的是任务完成之后它才被打印。


之后发现在Process类中有一个waitFor()方法可以实现。如下:

 

Java代码    收藏代码
  1. Process p = null;  
  2. try {  
  3.     p = Runtime.getRuntime().exec("notepad.exe");  
  4.     p.waitFor();  
  5. catch (Exception e) {  
  6.     e.printStackTrace();  
  7. }  
  8. System.out.println("我想被打印...");  

 这下又出现了这样的现象,必须要等我们把记事本关闭, 打印语句才会被执行。并且你不能手动关闭它那程序就一直不动,程序貌似挂了.....这是什么情况,想调用个别的程序有这么难吗?让我们来看看waitFor()的说明:


JDK帮助文档上这么说:如有必要,一直要等到由该 Process 对象表示的进程已经终止。如果已终止该子进程,此方法立即返回。但是直接调用这个方法会导致当前线程阻塞,直到退出子进程。对此JDK文档上还有如此解释:因为本地的系统对标准输入和输出所提供的缓冲池有效,所以错误的对标准输出快速的写入何从标准输入快速的读入都有可能造成子进程的所,甚至死锁。好了,

        问题的关键在缓冲区这个地方:可执行程序的标准输出比较多,而运行窗口的标准缓冲区不够大,所以发生阻塞。

        接着来分析缓冲区,哪来的这个东西,当Runtime对象调用exec(cmd)后,JVM会启动一个子进程,该进程会与JVM进程建立三个管道连接标准输入标准输出标准错误流

 

假设该程序不断在向标准输出流和标准错误流写数据,而JVM不读取的话,当缓冲区满之后将无法继续写入数据,最终造成阻塞在waitfor()这里。 知道问题所在,我们解决问题就好办了。查看网上说的方法多数是开两个线程在waitfor()命令之前读出窗口的标准输出缓冲区和标准错误流的内容。代码如下:

 

Java代码    收藏代码
  1. Runtime rt = Runtime.getRuntime();  
  2. String command = "cmd /c ffmpeg -loglevel quiet -i "+srcpath+" -ab "+bitrate+"k -acodec libmp3lame "+desfile;  
  3. try {  
  4.  p = rt.exec(command ,null,new File("C:\\ffmpeg-git-670229e-win32-static\\bin"));  
  5.  //获取进程的标准输入流  
  6.  final InputStream is1 = p.getInputStream();   
  7.  //获取进城的错误流  
  8.  final InputStream is2 = p.getErrorStream();  
  9.  //启动两个线程,一个线程负责读标准输出流,另一个负责读标准错误流  
  10.  new Thread() {  
  11.     public void run() {  
  12.        BufferedReader br1 = new BufferedReader(new InputStreamReader(is1));  
  13.         try {  
  14.             String line1 = null;  
  15.             while ((line1 = br1.readLine()) != null) {  
  16.                   if (line1 != null){}  
  17.               }  
  18.         } catch (IOException e) {  
  19.              e.printStackTrace();  
  20.         }  
  21.         finally{  
  22.              try {  
  23.                is1.close();  
  24.              } catch (IOException e) {  
  25.                 e.printStackTrace();  
  26.             }  
  27.           }  
  28.         }  
  29.      }.start();  
  30.                                 
  31.    new Thread() {   
  32.       public void  run() {   
  33.        BufferedReader br2 = new  BufferedReader(new  InputStreamReader(is2));   
  34.           try {   
  35.              String line2 = null ;   
  36.              while ((line2 = br2.readLine()) !=  null ) {   
  37.                   if (line2 != null){}  
  38.              }   
  39.            } catch (IOException e) {   
  40.                  e.printStackTrace();  
  41.            }   
  42.           finally{  
  43.              try {  
  44.                  is2.close();  
  45.              } catch (IOException e) {  
  46.                  e.printStackTrace();  
  47.              }  
  48.            }  
  49.         }   
  50.       }.start();    
  51.                                 
  52.       p.waitFor();  
  53.       p.destroy();   
  54.      System.out.println("我想被打印...");  
  55.     } catch (Exception e) {  
  56.             try{  
  57.                 p.getErrorStream().close();  
  58.                 p.getInputStream().close();  
  59.                 p.getOutputStream().close();  
  60.                 }  
  61.              catch(Exception ee){}  
  62.           }  
  63.    }  

 这个方法确实可以解决调用waitFor()方法阻塞无法返回的问题。但是在其中过程中我却发现真正起关键作用的缓冲区是getErrorStream()所对应的那个缓冲区没有被清空,意思就是说其实只要及时读取标准错误流缓冲区的数据程序就不会被block

 

Java代码    收藏代码
  1. StringBuffer sb = new StringBuffer();  
  2. try {  
  3. Process pro = Runtime.getRuntime().exec(cmdString);  
  4. BufferedReader br = new BufferedReader(new InputStreamReader(pro.getInputStream()), 4096);  
  5. String line = null;  
  6. int i = 0;  
  7. while ((line = br.readLine()) != null) {  
  8. if (0 != i)  
  9. sb.append("\r\n");  
  10. i++;  
  11. sb.append(line);  
  12. }  
  13. catch (Exception e) {  
  14. sb.append(e.getMessage());  
  15. }  
  16. return sb.toString();  

 

 不过这种写法不知道是不是适合所有的情况,网上其他人说的需要开两个线程可能不是没有道理。这个还是具体问题具体对待吧。

 

到这里问题的原因也清楚了,问题也被解决了,是不是就结束了。让我们回过头来再分析一下,问题的关键是处在输入流缓冲区那个地方,子进程的产生的输出流没有被JVM及时的读取最后缓冲区满了就卡住了。如果我们能够不让子进程向输入流写入数据,是不是可以解决这个问题。对于这个想法直接去ffmpeg官网查找,最终发现真的可以关闭子进程向窗口写入数据。命令如下:
ffmpeg.exe -loglevel quiet -i 1.mp3 -ab 16k -ar 22050 -acodec libmp3lame r.mp3
稍微分析一下:-acodec 音频流编码方式 -ab 音频流码率(默认是同源文件码率,也需要视codec而定) -ar 音频流采样率(大多数情况下使用44100和48000,分别对应PAL制式和NTSC制式,根据需要选择),重点就是-loglevel quiet这句 

 

这才是我们想要的结果:

 

Java代码    收藏代码
  1. try {  
  2.   p = Runtime.getRuntime().exec("cmd /c ffmpeg -loglevel quiet -i     D:\\a.mp3 -ab 168k -ar 22050 -acodec libmp3lame D:\\b.mp3",null,  
  3.                     new File( "C:\\ffmpeg-git-670229e-win32-static\\bin"));  
  4.   p.waitFor();  
  5. catch (Exception e) {  
  6.     e.printStackTrace();  
  7. }  
  8. System.out.println("我想被打印...");  

 

最后是自己写的一个简单的操作MP3文件的类

 

Java代码    收藏代码
  1. package com.yearsaaaa.util;  
  2.   
  3. import java.io.File;  
  4. import java.io.FileInputStream;  
  5. import java.math.BigDecimal;  
  6.   
  7. import javazoom.jl.decoder.Bitstream;  
  8. import javazoom.jl.decoder.Header;  
  9.   
  10. /** 
  11.  * @className:MP3Util.java 
  12.  * @classDescription: 
  13.  * @author:MChen 
  14.  * @createTime:2012-2-9 
  15.  */  
  16. public class MP3Util {  
  17.       
  18.     /** 
  19.      * 获取文件大小,以M为单位,保留小数点两位 
  20.      */  
  21.     public static double getMP3Size(String path)  
  22.     {  
  23.         File file = new File(path);  
  24.         double size = (double)file.length()/(1024*1024);  
  25.         size = new BigDecimal(size).setScale(2,BigDecimal.ROUND_UP).doubleValue();  
  26.         System.out.println("MP3文件的大小为:"+size);  
  27.         return size;  
  28.     }  
  29.       
  30.     /** 
  31.      * 该方法只能获取mp3格式的歌曲长度 
  32.      * 库地址:http://www.javazoom.net/javalayer/javalayer.html 
  33.      */  
  34.     public static String getMP3Time(String path)  
  35.     {  
  36.         String songTime = null;  
  37.         FileInputStream fis = null;  
  38.         Bitstream bt = null;  
  39.         File file = new File(path);  
  40.         try {  
  41.             fis = new FileInputStream(file);  
  42.             int b=fis.available();  
  43.             bt=new Bitstream(fis);  
  44.             Header h=bt.readFrame();  
  45.             int time=(int) h.total_ms(b);  
  46.             int i=time/1000;  
  47.             bt.close();  
  48.             fis.close();  
  49.             if(i%60 == 0)  
  50.                 songTime = (i/60+":"+i%60+"0");  
  51.             if(i%60 <10)  
  52.                 songTime = (i/60+":"+"0"+i%60);  
  53.             else  
  54.                 songTime = (i/60+":"+i%60);  
  55.             System.out.println("该歌曲的长度为:"+songTime);  
  56.         }  
  57.         catch (Exception e) {  
  58.             try {  
  59.                 bt.close();  
  60.                 fis.close();  
  61.             } catch (Exception ee) {  
  62.                 ee.printStackTrace();  
  63.             }  
  64.         }  
  65.         return songTime;  
  66.     }  
  67.       
  68.     /** 
  69.      * 将源MP3向下转码成低品质的文件 
  70.      * @参数: @param srcPath 源地址 
  71.      * @参数: @param bitrate 比特率 
  72.      * @参数: @param desfile 目标文件 
  73.      * @return void    
  74.      * @throws 
  75.      */  
  76.     public static void mp3Transcoding(String srcPath,String bitrate,String desFile)  
  77.     {     
  78.         //Java调用CMD命令时,不能有空格  
  79.         String srcpath = srcPath.replace(" ""\" \"");  
  80.         String desfile = desFile.replace(" ""\" \"");  
  81.         Runtime rt = Runtime.getRuntime();  
  82.         String command = "cmd /c ffmpeg -loglevel quiet -i "+srcpath+" -ab "+bitrate+"k -acodec libmp3lame "+desfile;  
  83.         System.out.println(command);  
  84.         Process p = null;  
  85.         try{  
  86.             //在Linux下调用是其他写法  
  87.             p = rt.exec(command ,null,new File("C:\\ffmpeg-git-670229e-win32-static\\bin"));  
  88.             p.waitFor();  
  89.             System.out.println("线程返回,转码后的文件大小为:"+desFile.length()+",现在可以做其他操作了,比如重新写入ID3信息。");  
  90.         }  
  91.         catch(Exception e){  
  92.             e.printStackTrace();  
  93.             try{  
  94.                 p.getErrorStream().close();  
  95.                 p.getInputStream().close();  
  96.                 p.getOutputStream().close();  
  97.                 }  
  98.             catch(Exception ee){}  
  99.         }  
  100.     }  
  101.       
  102.     public static void main(String[] args) {  
  103.         //String[] str = {"E:\\Kugou\\陈慧娴 - 不羁恋人.mp3","E:\\Kugou\\三寸天堂.mp3","E:\\Tmp\\陈淑桦 - 梦醒时分.mp3","E:\\Tmp\\1.mp3","E:\\Test1\\走天涯、老猫 - 杨望.acc","E:\\Test1\\因为爱情 铃.mp3"};  
  104.         String[] str = {"E:\\Kugou\\三寸天堂.mp3"};  
  105.         for(String s : str)  
  106.         {  
  107.             //getMP3Size(s);  
  108.             //getMP3Time(s);  
  109.             File f = new File(s);  
  110.             mp3Transcoding(f.getAbsolutePath(),"64","d:\\chenmiao.mp3");  
  111.         }  
  112.     }  
  113. }  
<think>好的,用户现在问的是`torch.cuda.empty_cache()`是否只能清空一个GPU的缓存。我需要先回忆一下PyTorchCUDA内存管理的机制。 首先,用户之前遇到了关于Scikit-learn的分类指标警告和PyTorch的NCCL支持问题,现在转向了CUDA内存管理。可能用户正在处理多GPU训练,或者在单GPU使用中遇到了显存问题。 `torch.cuda.empty_cache()`的作用是释放当前由缓存分配器持有的未使用内存PyTorch默认情况下会缓存一部分内存以提高分配效率,这个函数可以释放那些未被使用的缓存内存。但用户的问题是,这个函数是否只影响当前的一个GPU。 根据PyTorch的文档,当使用多GPU时,每个GPU都有自己的缓存分配器。所以,如果用户有多个GPU,`torch.cuda.empty_cache()`只会释放当前设备(即通过`torch.cuda.current_device()`指定的那个)的缓存。要清空所有GPU的缓存,需要遍历每个设备,切换当前设备,然后分别调用该函数。 用户可能是在多GPU环境下运行代码,发现显存没有完全释放,或者遇到显存不足的问题。需要确认用户是否在使用多GPU,以及是否正确地切换了设备来清空每个GPU的缓存。 另外,用户可能对PyTorch内存管理机制不太熟悉,所以需要解释清楚这个函数的行为,以及在不同情况下的正确使用方法。可能还需要提醒用户,即使调用了这个函数,显存的释放也依赖于是否有未使用的缓存,实际使用的张量不会被释放。 最后,给出示例代码,说明如何为所有GPU清空缓存,并强调正确使用设备切换的重要性。还要提醒用户,显存管理需要结合其他方法,如手动删除变量、运行垃圾回收等,才能有效管理显存使用。</think>--- 在 PyTorch 中,`torch.cuda.empty_cache()` 的作用是释放当前 CUDA 设备(即当前选中的 GPU)的**未使用缓存内存**。其行为取决于 **GPU 设备的选择**和 **多 GPU 环境**的配置。以下是详细说明: --- ### **1.GPU 场景** - 如果你只有一个 GPU,或者代码中未显式指定 GPU 设备: `torch.cuda.empty_cache()` 会清空当前默认 GPU 的缓存内存。 例如: ```python import torch # 默认使用 GPU 0(仅单卡时) a = torch.randn(1000, 1000, device="cuda") # 占用显存 del a # 删除变量 torch.cuda.empty_cache() # 释放 GPU 0 的未使用缓存 ``` --- ### **2.GPU 场景** - 如果你有多个 GPU,且代码中显式切换了设备: 需要**依次选中每个 GPU 并单独调用** `empty_cache()`,才能清空所有 GPU 的缓存。 例如: ```python import torch # 清空 GPU 0 的缓存 torch.cuda.set_device(0) torch.cuda.empty_cache() # 清空 GPU 1 的缓存 torch.cuda.set_device(1) torch.cuda.empty_cache() ``` --- ### **3. 关键注意事项** 1. **缓存释放的范围**: `empty_cache()` **仅释放由 PyTorch 缓存分配器管理的未占用内存**,不会释放正在被张量占用的显存。 - 已分配的张量必须手动删除(如 `del tensor`)或超出作用域后,其显存才会被缓存分配器回收。 - 调用 `empty_cache()` 后,这些回收的内存才会真正释放回系统。 2. **多进程分布式训练**: 在分布式训练(如使用 `torch.distributed` 或 `DataParallel`)时,每个进程可能绑定到不同的 GPU。 - 每个进程需独立调用 `empty_cache()` 清理自己绑定的 GPU 缓存。 - 例如: ```python # 每个进程仅清理自己绑定的 GPU torch.cuda.empty_cache() ``` 3. **自动缓存管理**: PyTorch 默认会缓存部分显存以提高分配效率。频繁调用 `empty_cache()` 可能导致性能下降,建议仅在显存不足时手动调用。 --- ### **4. 验证显存释放** 可以使用 `torch.cuda.memory_summary()` 或以下代码查看显存状态: ```python import torch # 查看当前 GPU 的显存使用情况(单位:字节) print(torch.cuda.memory_allocated()) # 当前已分配的显存 print(torch.cuda.memory_reserved()) # 当前缓存分配器保留的显存(包括未使用的) ``` --- ### **总结** | 场景 | 行为 | |------------|----------------------------------------------------------------------| | **单 GPU** | 清空当前 GPU 的未使用缓存。 | | **多 GPU** | 需遍历所有 GPU,分别调用 `empty_cache()` 才能清空每个设备的缓存。 | --- ### **最佳实践** - 显存不足时手动调用 `empty_cache()`,但避免在循环中频繁使用。 - 结合显存监控工具(如 `nvidia-smi` 或 PyTorch 内置函数)诊断显存泄漏。 - 多 GPU 场景显式指定设备并分别清理: ```python for i in range(torch.cuda.device_count()): torch.cuda.set_device(i) torch.cuda.empty_cache() ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值