java优化占用内存的方法(二)

转 http://blog.csdn.net/xueyepiaoling/article/details/5185795

垃圾收集几乎是每一个开发人员都喜好的一个 Java™ 平台特性,它简化了开发,消除了全部种类的潜在代码错误。可尽管垃圾收集通常来讲可让您无需进行资源管理,有时候您仍是必须本身进行一些内务处理。
java

显式地释放资源数据库

Java 程序中使用的绝大多数资源都是对象,垃圾收集在清理对象方面作得很好。所以,您可使用任意多的 String。垃圾收集器最终无需您的干预就会算出它们什么时候失效,并收回它们使用的内存。
安全

另外一方面,像文件句柄和套接字句柄这类非内存资源必须由程序显式地释放,好比使用 close()destroy()shutdown()release() 这样的方法来释放。有些类,好比平台类库中的文件句柄流实现,提供终结器(finalizer)做为安全保证,以便当垃圾收集器肯定程序再也不使用资源而程 序却忘了释放资源时,终结器还能够来作这个释放工做。可是尽管文件句柄提供了终结器来在您忘记了时为您释放资源,最好仍是在使用完以后显式地释放资源。这 样作能够更早地释放资源,下降了资源耗尽的可能。
服务器

对于有些资源来讲,一直等到终结(finalization)释放它们是不可取的。对于重要的资源,好比锁获取和信号量许可证,LockSemaphore 直到很晚均可能不会被垃圾收集掉。对于数据库链接这样的资源,若是您等待终结,那么确定会消耗完资源。许多数据库服务器根据许可的容量,只接受必定数量的 链接。若是服务器应用程序为每一个请求都打开一个新的数据库链接,而后用完以后就无论了,那么数据库远远未到终结器关闭再也不须要的链接,就会到达它的最高容 量。
网络

多数资源都不会持续整个应用程序的生命周期,相反,它们只被用于一个活动的生命周期。当应用程序打开一个文件句柄读取文件以处理文档时,它一般读取文件后就再也不须要文件句柄了。框架

在最简单的状况下,资源在同一个方法调用中被获取、使用和释放,代码以下所示测试

Java代码
  1. //不正确地在一个方法中获取、使用和释放资源 —— 不要这样作   
  2. public static Properties loadPropertiesBadly(String fileName)   
  3.             throws IOException {   
  4.         FileInputStream stream new FileInputStream(fileName);   
  5.         Properties props new Properties();   
  6.         props.load(stream);   
  7.         stream.close();   
  8.         return props;   
  9.      
[java] view plain copy
  1. //不正确地在一个方法中获取、使用和释放资源 —— 不要这样作  
  2. public static Properties loadPropertiesBadly(String fileName)  
  3.             throws IOException  
  4.         FileInputStream stream new FileInputStream(fileName);  
  5.         Properties props new Properties();  
  6.         props.load(stream);  
  7.         stream.close();  
  8.         return props;  
  9.      

 

不幸的是,这个例子存在潜在的资源泄漏。若是一切进展顺利,流将会在方法返回以前被关闭。可是若是 props.load() 方法抛出一个 IOException,那么流则不会被关闭(直到垃圾收集器运行其终结器)。解决方案是使用 try...finally 机制来确保流被关闭,而不论是否发生错误,代码以下所示(不过他并不完善)ui

Java代码
  1. //正确地在一个方法中获取、使用和释放资源   
  2.  public static Properties loadProperties(String fileName)    
  3.             throws IOException {   
  4.         FileInputStream stream new FileInputStream(fileName);   
  5.         try {   
  6.             Properties props new Properties();   
  7.             props.load(stream);   
  8.             return props;   
  9.         }   
  10.         finally {   
  11.             stream.close();   
  12.         }   
  13.      
[java] view plain copy
  1. //正确地在一个方法中获取、使用和释放资源  
  2.  public static Properties loadProperties(String fileName)   
  3.             throws IOException  
  4.         FileInputStream stream new FileInputStream(fileName);  
  5.         try  
  6.             Properties props new Properties();  
  7.             props.load(stream);  
  8.             return props;  
  9.          
  10.         finally  
  11.             stream.close();  
  12.          
  13.      

 

注意,资源获取(打开文件)是在 try 块外面进行的;若是把它放在 try 块中,那么即便资源获取抛出异常,finally 块也会运行。不只该方法会不适当(您没法释放您没有获取的资源),finally 块中的代码也可能抛出其本身的异常,好比 NullPointerException。从 finally 块抛出的异常取代致使块退出的异常,这意味着原来的异常丢失了,不能用于帮助进行调试。spa

 

使用 finally 来释放在方法中获取的资源是可靠的,可是当涉及多个资源时,很容易变得难以处理。下面考虑这样一个方法,它使用一个 JDBC Connection 来执行查询和迭代 ResultSet。该方法得到一个 Connection,使用它来建立一个 Statement,并执行 Statement 以获得一个 ResultSet。可是中间 JDBC 对象 StatementResultSet 具备它们本身的 close() 方法,而且当您使用完以后,应该释放这些中间对象。然而,进行资源释放的 “明显的” 方式并不起做用,以下所示:.net

Java代码
  1. //不成功的释放多个资源的企图 —— 不要这样作   
  2. public void enumerateFoo() throws SQLException {   
  3.         Statement statement null;   
  4.         ResultSet resultSet null;   
  5.         Connection connection getConnection();   
  6.         try {   
  7.             statement connection.createStatement();   
  8.             resultSet statement.executeQuery("SELECT FROM Foo");   
  9.             // Use resultSet   
  10.         }   
  11.         finally {   
  12.             if (resultSet != null)   
  13.                 resultSet.close();   
  14.             if (statement != null)   
  15.                 statement.close();   
  16.             connection.close();   
  17.         }   
  18.     }   
  19.   
  20.    
[java] view plain copy
  1. //不成功的释放多个资源的企图 —— 不要这样作  
  2. public void enumerateFoo() throws SQLException  
  3.         Statement statement null 
  4.         ResultSet resultSet null 
  5.         Connection connection getConnection();  
  6.         try  
  7.             statement connection.createStatement();  
  8.             resultSet statement.executeQuery("SELECT FROM Foo");  
  9.             // Use resultSet  
  10.          
  11.         finally  
  12.             if (resultSet != null 
  13.                 resultSet.close();  
  14.             if (statement != null 
  15.                 statement.close();  
  16.             connection.close();  
  17.          
  18.      
  19.   
  20.    

 这个 “解决方案” 不成功的缘由在于ResultSetStatementclose() 方法本身能够抛出 SQLException这会致使后面 finally 块中的 close() 语句不执行。您在这里有几种选择,每一种都很烦人:用一个 try..catch 块封装每个 close(),可使用嵌套 try...finally 块,或者编写某种小型框架用于管理资源获取和释放。  

Java代码
  1. //可靠的释放多个资源的方法   
  2. public void enumerateBar() throws SQLException {   
  3.         Statement statement null;   
  4.         ResultSet resultSet null;   
  5.         Connection connection getConnection();   
  6.         try {   
  7.             statement connection.createStatement();   
  8.             resultSet statement.executeQuery("SELECT FROM Bar");   
  9.             // Use resultSet   
  10.         }   
  11.         finally {   
  12.             try {   
  13.                 if (resultSet != null)   
  14.                     resultSet.close();   
  15.             }   
  16.             finally {   
  17.                 try {   
  18.                     if (statement != null)   
  19.                         statement.close();   
  20.                 }   
  21.                 finally {   
  22.                     connection.close();   
  23.                 }   
  24.             }   
  25.         }   
  26.      
[java] view plain copy
  1. //可靠的释放多个资源的方法  
  2. public void enumerateBar() throws SQLException  
  3.         Statement statement null 
  4.         ResultSet resultSet null 
  5.         Connection connection getConnection();  
  6.         try  
  7.             statement connection.createStatement();  
  8.             resultSet statement.executeQuery("SELECT FROM Bar");  
  9.             // Use resultSet  
  10.          
  11.         finally  
  12.             try  
  13.                 if (resultSet != null 
  14.                     resultSet.close();  
  15.              
  16.             finally  
  17.                 try  
  18.                     if (statement != null 
  19.                         statement.close();  
  20.                  
  21.                 finally  
  22.                     connection.close();  
  23.                  
  24.              
  25.          
  26.      

 咱们都知道应该使用 finally 来释放像数据库链接这样的重量级对象,可是咱们并不老是这样细心,可以记得使用它来关闭流(毕竟,终结器会为咱们作这件事,是否是?)。很容易忘记在使用资源的代码不抛出已检查的异常时使用 finally。以下代码展现了针对绑定链接的 add() 方法的实现,它使用 Semaphore 来实施绑定,并有效地容许客户机等待空间可用:

Java代码
  1. //绑定链接的脆弱实现 —— 不要这样作   
  2. public class LeakyBoundedSet {   
  3.     private final Set set ...   
  4.     private final Semaphore sem;   
  5.     public LeakyBoundedSet(int bound) {   
  6.         sem new Semaphore(bound);   
  7.     }   
  8.     public boolean add(T o) throws InterruptedException {   
  9.         sem.acquire();   
  10.         boolean wasAdded set.add(o);   
  11.         if (!wasAdded)   
  12.             sem.release();   
  13.         return wasAdded;   
  14.     }   
  15.  
[java] view plain copy
  1. //绑定链接的脆弱实现 —— 不要这样作  
  2. public class LeakyBoundedSet  
  3.     private final Set set ...  
  4.     private final Semaphore sem;  
  5.     public LeakyBoundedSet(int bound)  
  6.         sem new Semaphore(bound);  
  7.      
  8.     public boolean add(T o) throws InterruptedException  
  9.         sem.acquire();  
  10.         boolean wasAdded set.add(o);  
  11.         if (!wasAdded)  
  12.             sem.release();  
  13.         return wasAdded;  
  14.      
  15.  

 

LeakyBoundedSet 首先等待一个许可证成为可用的(表示链接中有空间了),而后试图将元素添加到链接中。添加操做若是因为该元素已经在链接中了而失败,那么它会释放许可证(由于它不实际使用它所保留的空间)。

LeakyBoundedSet 有关的问题没有必要立刻跳出:若是 Set.add() 抛出一个异常呢?若是 Set 实现中有缺陷,或者 equals()hashCode() 实现(在 SortedSet 的状况下是 compareTo() 实现)中有缺陷,缘由在于添加元素时元素已经在 Set 中了。固然,解决方案是使用 finally 来释放信号量许可证,这是一个很简单却容易被遗忘的方法。这些类型的错误不多会在测试期间暴露出来,于是成了定时炸弹,随时可能爆炸。以下代码展现了BoundedSet 的一个更加可靠的实现

Java代码
  1. //使用一个 Semaphore 来可靠地绑定 Set   
  2. public class BoundedSet {   
  3.     private final Set set ...   
  4.     private final Semaphore sem;   
  5.     public BoundedHashSet(int bound) {   
  6.         sem new Semaphore(bound);   
  7.     }   
  8.     public boolean add(T o) throws InterruptedException {   
  9.         sem.acquire();   
  10.         boolean wasAdded false;   
  11.         try {   
  12.             wasAdded set.add(o);   
  13.             return wasAdded;   
  14.         }   
  15.         finally {   
  16.             if (!wasAdded)   
  17.                 sem.release();   
  18.         }   
  19.     }   
  20.  
[java] view plain copy
  1. //使用一个 Semaphore 来可靠地绑定 Set  
  2. public class BoundedSet  
  3.     private final Set set ...  
  4.     private final Semaphore sem;  
  5.     public BoundedHashSet(int bound)  
  6.         sem new Semaphore(bound);  
  7.      
  8.     public boolean add(T o) throws InterruptedException  
  9.         sem.acquire();  
  10.         boolean wasAdded false 
  11.         try  
  12.             wasAdded set.add(o);  
  13.             return wasAdded;  
  14.          
  15.         finally  
  16.             if (!wasAdded)  
  17.                 sem.release();  
  18.          
  19.      
  20.  

 

 

对于具备任意生命周期的资源,咱们要回到 C 语言的时代,即手动地管理资源生命周期。在一个服务器应用程序中,客户机到服务器的一个持久网络链接存在于一个会话期间(好比一个多人参与的游戏服务 器),每一个用户的资源(包括套接字链接)在用户退出时必须被释放。好的组织是有帮助的;若是对每一个用户资源的角色引用保存在一个 ActiveUser 对象中,那么它们就能够在 ActiveUser 被释放时(不管是显式地释放,仍是经过垃圾收集而释放)而被释放。

 

具备任意生命周期的资源几乎老是存储在一个全局集合中(或者从这里可达)。要避免资源泄漏,所以很是重要的是,要识别出资源什么时候再也不须要了并能够从这个全局集合中删除了。此时,由于您知道资源将要被释放,任何与该资源关联的非内存资源也能够同时被释放。

 

确保及时的资源释放的一个关键技巧是维护全部权的一个严格层次结构,其中的全部权具备释放资源的职责。若是应用程序建立一个线程池,而线程池建立线 程,线程是程序能够退出以前必须被释放的资源。可是应用程序不拥有线程,而是由线程池拥有线程,所以线程池必须负责释放线程。固然,直到它自己被应用程序 释放以后,线程池才能释放线程。

维护一个全部权层次结构有助于不至于失去控制,其中每一个资源拥有它得到的资源并负责释放它们。这个规则的结果是,每一个不能由垃圾收集单独收集的资源(即这样的资源,它直接或间接拥有不能由垃圾收集释放的资源)必须提供某种生命周期支持,好比close() 方法。

若是说平台库提供终结器来清除打开的文件句柄,这大大下降了忘记显式地关闭这些句柄的风险,为何不更多地使用终结器呢?缘由有不少,最重要的一个 缘由是,终结器很难正确编写(而且很容易编写错)。终结器不只难以编写正确,终结的定时也是不肯定的,而且不能保证终结器最终会运行。而且终结还为可终结 对象的实例化和垃圾收集带来了开销。不要依赖于终结器做为释放资源的主要方式。

垃圾收集为咱们作了大量可怕的资源清除工做,可是有些资源仍然须要显式的释放,好比文件句柄、套接字句柄、线程、数据库链接和信号量许可证。当资源的生命周期被绑定到特定调用帧的生命周期时,咱们一般可使用 finally 块来释放该资源,可是长期存活的资源须要一种策略来确保它们最终被释放。对于任何一个这样的对象,即它直接或间接拥有一个须要显式释放的对象,您必须提供生命周期方法 —— 好比 close()release()destroy() 等 —— 来确保可靠的清除。

相关文章
相关标签/搜索