使用 try 时资源泄漏...最后?

Posted

技术标签:

【中文标题】使用 try 时资源泄漏...最后?【英文标题】:Resource leak while using try...finally? 【发布时间】:2013-02-21 13:54:51 【问题描述】:

当我在此方法中的 try 块内的两个 return 值中遇到资源泄漏警告时,我在 Eclipse 中正常工作:

@Override
public boolean isValid(File file) throws IOException

    BufferedReader reader = null;
    try
    
        reader = new BufferedReader(new FileReader(file));
        String line;
        while((line = reader.readLine()) != null)
        
            line = line.trim();
            if(line.isEmpty())
                continue;
            if(line.startsWith("#") == false)
                return false;
            if(line.startsWith("#MLProperties"))
                return true;
        
    
    finally
    
        tryreader.close();catch(Exception e)
    
    return false;

我不明白这将如何导致资源泄漏,因为我在try 范围之外声明了reader 变量,在try 块内添加了一个资源并在finally 块中使用另一个try...catch 忽略异常和一个NullPointerException 如果readernull 出于某种原因...

据我所知,finally 块总是在离开try...catch 结构时执行,因此在try 块内返回一个值仍然会在退出方法之前执行finally 块...

这很容易证明:

public static String test()

    String x = "a";
    try
    
        x = "b";
        System.out.println("try block");
        return x;
    
    finally
    
        System.out.println("finally block");
    


public static void main(String[] args)

    System.out.println("calling test()");
    String ret = test();
    System.out.println("test() returned "+ret);

结果:

calling test()
try block
finally block
test() returned b

知道了这一切,如果我在 finally 块中关闭它,为什么 eclipse 会告诉我 Resource leak: 'reader' is not closed at this location


回答

我只想在this answer 中添加他是正确的,如果new BufferedReader 抛出异常,FileReader 的实例将在被垃圾收集器销毁时打开,因为它不会分配给任何变量并且@ 987654343@ 块不会关闭它,因为 reader 将是 null

这就是我修复这个可能的泄漏的方法:

@Override
public boolean isValid(File file) throws IOException

    FileReader fileReader = null;
    BufferedReader reader = null;
    try
    
        fileReader = new FileReader(file);
        reader = new BufferedReader(fileReader);
        String line;
        while((line = reader.readLine()) != null)
        
            line = line.trim();
            if(line.isEmpty())
                continue;
            if(line.startsWith("#") == false)
                return false;
            if(line.startsWith("#MLProperties"))
                return true;
        
    
    finally
    
        tryreader.close();catch(Exception e)
        tryfileReader.close();catch(Exception ee)
    
    return false;

【问题讨论】:

旁注:if(line.startsWith("#") == false)一般写成:if(!line.startsWith("#")) 警告看起来完全是假的。 在 finally 子句中,你必须检查 reader 是否不为空,因为如果由于某种原因 reader 变量没有被实例化,你将有一个 NPE。 移动新的 BufferedReader(new FileReader(file));到 bufferedreader 的初始化,如果它是 null 它不会尝试关闭 只是一个想法,但也许是因为你的例外。如果读取时出现 IOError,则永远不会捕获此异常,并且您的流永远不会关闭。 【参考方案1】:

由于close() 可以抛出异常(为什么哦,他们为什么要这样设计......)我倾向于使用双重尝试

try 
  BufferedReader reader = new BufferedReader(new FileReader(file));
  try 
    // do stuff with reader
   finally 
    reader.close();
  
 catch(IOException e) 
  // handle exceptions

由于这个习惯用法消除了 finally 块中的 try/catch,它可能足以让 Eclipse 满意。

new BufferedReader(...) 本身不能抛出IOException,但从技术上讲,如果BufferedReader 构造函数抛出RuntimeExceptionError,这仍然可能泄漏FileReader

【讨论】:

这如何回答这个问题?【参考方案2】:

从技术上讲,有一条 BufferedReader 不会关闭的路径:如果reader.close() 会抛出异常,因为您捕获了异常并且什么都不做。这可以通过在您的 catch 块中再次添加 reader.close() 来验证:

     finally
    
        try 
            reader.close();
         catch (Exception e) 
            reader.close();
        
    

或者通过删除 finally 中的 try/catch:

     finally
    
            reader.close();
    

这将使警告消失。

当然,它对你没有帮助。如果 reader.close() 失败,那么再次调用它没有意义。问题是,编译器不够聪明,无法处理这个问题。所以你唯一能做的就是在方法中添加一个@SuppressWarnings("resource")

编辑 如果您使用的是 Java 7,您可以/应该做的是使用 try-with-resources 功能。这将使警告正确,并使您的代码更简单,为您节省 finally 块:

public boolean isValid(File file) throws IOException

  try(BufferedReader reader = new BufferedReader(new FileReader(file)))
  
    String line;
    while ((line = reader.readLine()) != null)
    
      line = line.trim();
      if (line.isEmpty())
        continue;
      if (line.startsWith("#") == false)
        return false;
      if (line.startsWith("#MLProperties"))
        return true;
    
   
  return false;

【讨论】:

谢谢,您还帮助我了解了问题所在。 +1。我不能在这个项目中使用 java 7,因为我需要 java 6 兼容性【参考方案3】:

如果BufferedReader 构造函数抛出异常(例如内存不足),您将有FileReader 泄漏。

【讨论】:

没错,这可能会导致泄漏,但由于这个原因,警告不存在。奇怪的是,这样做 FileReader r2 = new FileReader(file);阅读器 = 新的 BufferedReader(r2);删除警告,但不会删除您指出的可能泄漏。 @JonathanDrapeau 嗯...这只是 Eclipse。 在清理资源时,通常不值得为Errors 和IllegalArgumentExceptions 之类的事情操心。【参考方案4】:
//If this line throws an exception, then neither the try block
    //nor the finally block will execute.
    //That is a good thing, since reader would be null.
    BufferedReader reader = new BufferedReader(new FileReader(aFileName));
    try 
      //Any exception in the try block will cause the finally block to execute
      String line = null;
      while ( (line = reader.readLine()) != null ) 
        //process the line...
      
    
    finally 
      //The reader object will never be null here.
      //This finally is only entered after the try block is 
      //entered. But, it's NOT POSSIBLE to enter the try block 
      //with a null reader object.
      reader.close();
    

【讨论】:

我觉得在 try 块之外实例化 FileReader 不安全...出于安全原因,我喜欢总是尝试关闭。我讨厌软件通过错误阻止文件:P

以上是关于使用 try 时资源泄漏...最后?的主要内容,如果未能解决你的问题,请参考以下文章

尝试使用资源:关闭 TWR 块中的资源时资源泄漏

在 try-with-resources 块之外流式传输 JOOQ 结果时,我是不是会冒 JDBC 连接泄漏的风险?

准备语句时 PreparedStatement 资源泄漏[重复]

尽管 try-catch 异常泄漏到系统

站点资源在 GetMapping 中泄漏到 UUID

try-with-resource 不关闭套接字连接