- 浏览: 19850 次
- 性别:
- 来自: 深圳
文章分类
最新评论
(转)垃圾收集几乎是每个开发人员都喜爱的一个 Java™ 平台特性,它简化了开发,消除了所有种类的潜在代码错误。可尽管垃圾收集一般来说可以让您无需进行资源管理,有时候您还是必须自己进行一些内务处理。
显式地释放资源
Java 程序中使用的绝大多数资源都是对象,垃圾收集在清理对象方面做得很好。因此,您可以使用任意多的 String。垃圾收集器最终无需您的干预就会算出它们何时失效,并收回它们使用的内存。
另一方面,像文件句柄和套接字句柄这类非内存资源必须由程序显式地释放,比如使用 close()、destroy()、shutdown() 或 release() 这样的方法来释放。有些类,比如平台类库中的文件句柄流实现,提供终结器(finalizer)作为安全保证,以便当垃圾收集器确定程序不再使用资源而程序却忘了释放资源时,终结器还可以来做这个释放工作。但是尽管文件句柄提供了终结器来在您忘记了时为您释放资源,最好还是在使用完之后显式地释放资源。这样做可以更早地释放资源,降低了资源耗尽的可能。
对于有些资源来说,一直等到终结(finalization)释放它们是不可取的。对于重要的资源,比如锁获取和信号量许可证,Lock 或 Semaphore 直到很晚都可能不会被垃圾收集掉。对于数据库连接这样的资源,如果您等待终结,那么肯定会消耗完资源。许多数据库服务器根据许可的容量,只接受一定数量的连接。如果服务器应用程序为每个请求都打开一个新的数据库连接,然后用完之后就不管了,那么数据库远远未到终结器关闭不再需要的连接,就会到达它的最高容量。
多数资源都不会持续整个应用程序的生命周期,相反,它们只被用于一个活动的生命周期。当应用程序打开一个文件句柄读取文件以处理文档时,它通常读取文件后就不再需要文件句柄了。
在最简单的情况下,资源在同一个方法调用中被获取、使用和释放,代码如下所示
Java代码
01.//不正确地在一个方法中获取、使用和释放资源 —— 不要这样做
02.public static Properties loadPropertiesBadly(String fileName)
03. throws IOException {
04. FileInputStream stream = new FileInputStream(fileName);
05. Properties props = new Properties();
06. props.load(stream);
07. stream.close();
08. return props;
09. }
[java] view plaincopyprint?
01.//不正确地在一个方法中获取、使用和释放资源 —— 不要这样做
02.public static Properties loadPropertiesBadly(String fileName)
03. throws IOException {
04. FileInputStream stream = new FileInputStream(fileName);
05. Properties props = new Properties();
06. props.load(stream);
07. stream.close();
08. return props;
09. }
//不正确地在一个方法中获取、使用和释放资源 —— 不要这样做
public static Properties loadPropertiesBadly(String fileName)
throws IOException {
FileInputStream stream = new FileInputStream(fileName);
Properties props = new Properties();
props.load(stream);
stream.close();
return props;
}
不幸的是,这个例子存在潜在的资源泄漏。如果一切进展顺利,流将会在方法返回之前被关闭。但是如果 props.load() 方法抛出一个 IOException,那么流则不会被关闭(直到垃圾收集器运行其终结器)。解决方案是使用 try...finally 机制来确保流被关闭,而不管是否发生错误,代码如下所示(不过他并不完善)
Java代码
01.//正确地在一个方法中获取、使用和释放资源
02. public static Properties loadProperties(String fileName)
03. throws IOException {
04. FileInputStream stream = new FileInputStream(fileName);
05. try {
06. Properties props = new Properties();
07. props.load(stream);
08. return props;
09. }
10. finally {
11. stream.close();
12. }
13. }
[java] view plaincopyprint?
01.//正确地在一个方法中获取、使用和释放资源
02. public static Properties loadProperties(String fileName)
03. throws IOException {
04. FileInputStream stream = new FileInputStream(fileName);
05. try {
06. Properties props = new Properties();
07. props.load(stream);
08. return props;
09. }
10. finally {
11. stream.close();
12. }
13. }
//正确地在一个方法中获取、使用和释放资源
public static Properties loadProperties(String fileName)
throws IOException {
FileInputStream stream = new FileInputStream(fileName);
try {
Properties props = new Properties();
props.load(stream);
return props;
}
finally {
stream.close();
}
}
注意,资源获取(打开文件)是在 try 块外面进行的;如果把它放在 try 块中,那么即使资源获取抛出异常,finally 块也会运行。不仅该方法会不适当(您无法释放您没有获取的资源),finally 块中的代码也可能抛出其自己的异常,比如 NullPointerException。从 finally 块抛出的异常取代导致块退出的异常,这意味着原来的异常丢失了,不能用于帮助进行调试。
使用 finally 来释放在方法中获取的资源是可靠的,但是当涉及多个资源时,很容易变得难以处理。下面考虑这样一个方法,它使用一个 JDBC Connection 来执行查询和迭代 ResultSet。该方法获得一个 Connection,使用它来创建一个 Statement,并执行 Statement 以得到一个 ResultSet。但是中间 JDBC 对象 Statement 和 ResultSet 具有它们自己的 close() 方法,并且当您使用完之后,应该释放这些中间对象。然而,进行资源释放的 “明显的” 方式并不起作用,如下所示:
Java代码
01.//不成功的释放多个资源的企图 —— 不要这样做
02.public void enumerateFoo() throws SQLException {
03. Statement statement = null;
04. ResultSet resultSet = null;
05. Connection connection = getConnection();
06. try {
07. statement = connection.createStatement();
08. resultSet = statement.executeQuery("SELECT * FROM Foo");
09. // 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 plaincopyprint?
01.//不成功的释放多个资源的企图 —— 不要这样做
02.public void enumerateFoo() throws SQLException {
03. Statement statement = null;
04. ResultSet resultSet = null;
05. Connection connection = getConnection();
06. try {
07. statement = connection.createStatement();
08. resultSet = statement.executeQuery("SELECT * FROM Foo");
09. // 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.
//不成功的释放多个资源的企图 —— 不要这样做
public void enumerateFoo() throws SQLException {
Statement statement = null;
ResultSet resultSet = null;
Connection connection = getConnection();
try {
statement = connection.createStatement();
resultSet = statement.executeQuery("SELECT * FROM Foo");
// Use resultSet
}
finally {
if (resultSet != null)
resultSet.close();
if (statement != null)
statement.close();
connection.close();
}
}
这个 “解决方案” 不成功的原因在于,ResultSet 和 Statement 的 close() 方法自己可以抛出 SQLException,这会导致后面 finally 块中的 close() 语句不执行。您在这里有几种选择,每一种都很烦人:用一个 try..catch 块封装每一个 close(),可以使用嵌套 try...finally 块,或者编写某种小型框架用于管理资源获取和释放。
Java代码
01.//可靠的释放多个资源的方法
02.public void enumerateBar() throws SQLException {
03. Statement statement = null;
04. ResultSet resultSet = null;
05. Connection connection = getConnection();
06. try {
07. statement = connection.createStatement();
08. resultSet = statement.executeQuery("SELECT * FROM Bar");
09. // 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 plaincopyprint?
01.//可靠的释放多个资源的方法
02.public void enumerateBar() throws SQLException {
03. Statement statement = null;
04. ResultSet resultSet = null;
05. Connection connection = getConnection();
06. try {
07. statement = connection.createStatement();
08. resultSet = statement.executeQuery("SELECT * FROM Bar");
09. // 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. }
//可靠的释放多个资源的方法
public void enumerateBar() throws SQLException {
Statement statement = null;
ResultSet resultSet = null;
Connection connection = getConnection();
try {
statement = connection.createStatement();
resultSet = statement.executeQuery("SELECT * FROM Bar");
// Use resultSet
}
finally {
try {
if (resultSet != null)
resultSet.close();
}
finally {
try {
if (statement != null)
statement.close();
}
finally {
connection.close();
}
}
}
}
我们都知道应该使用 finally 来释放像数据库连接这样的重量级对象,但是我们并不总是这样细心,能够记得使用它来关闭流(毕竟,终结器会为我们做这件事,是不是?)。很容易忘记在使用资源的代码不抛出已检查的异常时使用 finally。如下代码展示了针对绑定连接的 add() 方法的实现,它使用 Semaphore 来实施绑定,并有效地允许客户机等待空间可用:
Java代码
01.//绑定连接的脆弱实现 —— 不要这样做
02.public class LeakyBoundedSet<T> {
03. private final Set<T> set = ...
04. private final Semaphore sem;
05. public LeakyBoundedSet(int bound) {
06. sem = new Semaphore(bound);
07. }
08. public boolean add(T o) throws InterruptedException {
09. sem.acquire();
10. boolean wasAdded = set.add(o);
11. if (!wasAdded)
12. sem.release();
13. return wasAdded;
14. }
15.}
[java] view plaincopyprint?
01.//绑定连接的脆弱实现 —— 不要这样做
02.public class LeakyBoundedSet<T> {
03. private final Set<T> set = ...
04. private final Semaphore sem;
05. public LeakyBoundedSet(int bound) {
06. sem = new Semaphore(bound);
07. }
08. public boolean add(T o) throws InterruptedException {
09. sem.acquire();
10. boolean wasAdded = set.add(o);
11. if (!wasAdded)
12. sem.release();
13. return wasAdded;
14. }
15.}
//绑定连接的脆弱实现 —— 不要这样做
public class LeakyBoundedSet<T> {
private final Set<T> set = ...
private final Semaphore sem;
public LeakyBoundedSet(int bound) {
sem = new Semaphore(bound);
}
public boolean add(T o) throws InterruptedException {
sem.acquire();
boolean wasAdded = set.add(o);
if (!wasAdded)
sem.release();
return wasAdded;
}
}
LeakyBoundedSet 首先等待一个许可证成为可用的(表示连接中有空间了),然后试图将元素添加到连接中。添加操作如果由于该元素已经在连接中了而失败,那么它会释放许可证(因为它不实际使用它所保留的空间)。
与 LeakyBoundedSet 有关的问题没有必要马上跳出:如果 Set.add() 抛出一个异常呢?如果 Set 实现中有缺陷,或者 equals() 或 hashCode() 实现(在 SortedSet 的情况下是 compareTo() 实现)中有缺陷,原因在于添加元素时元素已经在 Set 中了。当然,解决方案是使用 finally 来释放信号量许可证,这是一个很简单却容易被遗忘的方法。这些类型的错误很少会在测试期间暴露出来,因而成了定时 bomb,随时可能爆炸。如下代码展示了 BoundedSet 的一个更加可靠的实现:
Java代码
01.//使用一个 Semaphore 来可靠地绑定 Set
02.public class BoundedSet<T> {
03. private final Set<T> set = ...
04. private final Semaphore sem;
05. public BoundedHashSet(int bound) {
06. sem = new Semaphore(bound);
07. }
08. public boolean add(T o) throws InterruptedException {
09. 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 plaincopyprint?
01.//使用一个 Semaphore 来可靠地绑定 Set
02.public class BoundedSet<T> {
03. private final Set<T> set = ...
04. private final Semaphore sem;
05. public BoundedHashSet(int bound) {
06. sem = new Semaphore(bound);
07. }
08. public boolean add(T o) throws InterruptedException {
09. 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.}
//使用一个 Semaphore 来可靠地绑定 Set
public class BoundedSet<T> {
private final Set<T> set = ...
private final Semaphore sem;
public BoundedHashSet(int bound) {
sem = new Semaphore(bound);
}
public boolean add(T o) throws InterruptedException {
sem.acquire();
boolean wasAdded = false;
try {
wasAdded = set.add(o);
return wasAdded;
}
finally {
if (!wasAdded)
sem.release();
}
}
}
对于具有任意生命周期的资源,我们要回到 C 语言的时代,即手动地管理资源生命周期。在一个服务器应用程序中,客户机到服务器的一个持久网络连接存在于一个会话期间(比如一个多人参与的游戏服务器),每个用户的资源(包括套接字连接)在用户退出时必须被释放。好的组织是有帮助的;如果对每个用户资源的角色引用保存在一个 ActiveUser 对象中,那么它们就可以在 ActiveUser 被释放时(无论是显式地释放,还是通过垃圾收集而释放)而被释放。
具有任意生命周期的资源几乎总是存储在一个全局集合中(或者从这里可达)。要避免资源泄漏,因此非常重要的是,要识别出资源何时不再需要了并可以从这个全局集合中删除了。此时,因为您知道资源将要被释放,任何与该资源关联的非内存资源也可以同时被释放。
确保及时的资源释放的一个关键技巧是维护所有权的一个严格层次结构,其中的所有权具有释放资源的职责。如果应用程序创建一个线程池,而线程池创建线程,线程是程序可以退出之前必须被释放的资源。但是应用程序不拥有线程,而是由线程池拥有线程,因此线程池必须负责释放线程。当然,直到它本身被应用程序释放之后,线程池才能释放线程。
维护一个所有权层次结构有助于不至于失去控制,其中每个资源拥有它获得的资源并负责释放它们。这个规则的结果是,每个不能由垃圾收集单独收集的资源(即这样的资源,它直接或间接拥有不能由垃圾收集释放的资源)必须提供某种生命周期支持,比如 close() 方法。
如果说平台库提供终结器来清除打开的文件句柄,这大大降低了忘记显式地关闭这些句柄的风险,为什么不更多地使用终结器呢?原因有很多,最重要的一个原因是,终结器很难正确编写(并且很容易编写错)。终结器不仅难以编写正确,终结的定时也是不确定的,并且不能保证终结器最终会运行。并且终结还为可终结对象的实例化和垃圾收集带来了开销。不要依赖于终结器作为释放资源的主要方式。
垃圾收集为我们做了大量可怕的资源清除工作,但是有些资源仍然需要显式的释放,比如文件句柄、套接字句柄、线程、数据库连接和信号量许可证。当资源的生命周期被绑定到特定调用帧的生命周期时,我们通常可以使用 finally 块来释放该资源,但是长期存活的资源需要一种策略来确保它们最终被释放。对于任何一个这样的对象,即它直接或间接拥有一个需要显式释放的对象,您必须提供生命周期方法 —— 比如 close()、release()、destroy() 等 —— 来确保可靠的清除。
显式地释放资源
Java 程序中使用的绝大多数资源都是对象,垃圾收集在清理对象方面做得很好。因此,您可以使用任意多的 String。垃圾收集器最终无需您的干预就会算出它们何时失效,并收回它们使用的内存。
另一方面,像文件句柄和套接字句柄这类非内存资源必须由程序显式地释放,比如使用 close()、destroy()、shutdown() 或 release() 这样的方法来释放。有些类,比如平台类库中的文件句柄流实现,提供终结器(finalizer)作为安全保证,以便当垃圾收集器确定程序不再使用资源而程序却忘了释放资源时,终结器还可以来做这个释放工作。但是尽管文件句柄提供了终结器来在您忘记了时为您释放资源,最好还是在使用完之后显式地释放资源。这样做可以更早地释放资源,降低了资源耗尽的可能。
对于有些资源来说,一直等到终结(finalization)释放它们是不可取的。对于重要的资源,比如锁获取和信号量许可证,Lock 或 Semaphore 直到很晚都可能不会被垃圾收集掉。对于数据库连接这样的资源,如果您等待终结,那么肯定会消耗完资源。许多数据库服务器根据许可的容量,只接受一定数量的连接。如果服务器应用程序为每个请求都打开一个新的数据库连接,然后用完之后就不管了,那么数据库远远未到终结器关闭不再需要的连接,就会到达它的最高容量。
多数资源都不会持续整个应用程序的生命周期,相反,它们只被用于一个活动的生命周期。当应用程序打开一个文件句柄读取文件以处理文档时,它通常读取文件后就不再需要文件句柄了。
在最简单的情况下,资源在同一个方法调用中被获取、使用和释放,代码如下所示
Java代码
01.//不正确地在一个方法中获取、使用和释放资源 —— 不要这样做
02.public static Properties loadPropertiesBadly(String fileName)
03. throws IOException {
04. FileInputStream stream = new FileInputStream(fileName);
05. Properties props = new Properties();
06. props.load(stream);
07. stream.close();
08. return props;
09. }
[java] view plaincopyprint?
01.//不正确地在一个方法中获取、使用和释放资源 —— 不要这样做
02.public static Properties loadPropertiesBadly(String fileName)
03. throws IOException {
04. FileInputStream stream = new FileInputStream(fileName);
05. Properties props = new Properties();
06. props.load(stream);
07. stream.close();
08. return props;
09. }
//不正确地在一个方法中获取、使用和释放资源 —— 不要这样做
public static Properties loadPropertiesBadly(String fileName)
throws IOException {
FileInputStream stream = new FileInputStream(fileName);
Properties props = new Properties();
props.load(stream);
stream.close();
return props;
}
不幸的是,这个例子存在潜在的资源泄漏。如果一切进展顺利,流将会在方法返回之前被关闭。但是如果 props.load() 方法抛出一个 IOException,那么流则不会被关闭(直到垃圾收集器运行其终结器)。解决方案是使用 try...finally 机制来确保流被关闭,而不管是否发生错误,代码如下所示(不过他并不完善)
Java代码
01.//正确地在一个方法中获取、使用和释放资源
02. public static Properties loadProperties(String fileName)
03. throws IOException {
04. FileInputStream stream = new FileInputStream(fileName);
05. try {
06. Properties props = new Properties();
07. props.load(stream);
08. return props;
09. }
10. finally {
11. stream.close();
12. }
13. }
[java] view plaincopyprint?
01.//正确地在一个方法中获取、使用和释放资源
02. public static Properties loadProperties(String fileName)
03. throws IOException {
04. FileInputStream stream = new FileInputStream(fileName);
05. try {
06. Properties props = new Properties();
07. props.load(stream);
08. return props;
09. }
10. finally {
11. stream.close();
12. }
13. }
//正确地在一个方法中获取、使用和释放资源
public static Properties loadProperties(String fileName)
throws IOException {
FileInputStream stream = new FileInputStream(fileName);
try {
Properties props = new Properties();
props.load(stream);
return props;
}
finally {
stream.close();
}
}
注意,资源获取(打开文件)是在 try 块外面进行的;如果把它放在 try 块中,那么即使资源获取抛出异常,finally 块也会运行。不仅该方法会不适当(您无法释放您没有获取的资源),finally 块中的代码也可能抛出其自己的异常,比如 NullPointerException。从 finally 块抛出的异常取代导致块退出的异常,这意味着原来的异常丢失了,不能用于帮助进行调试。
使用 finally 来释放在方法中获取的资源是可靠的,但是当涉及多个资源时,很容易变得难以处理。下面考虑这样一个方法,它使用一个 JDBC Connection 来执行查询和迭代 ResultSet。该方法获得一个 Connection,使用它来创建一个 Statement,并执行 Statement 以得到一个 ResultSet。但是中间 JDBC 对象 Statement 和 ResultSet 具有它们自己的 close() 方法,并且当您使用完之后,应该释放这些中间对象。然而,进行资源释放的 “明显的” 方式并不起作用,如下所示:
Java代码
01.//不成功的释放多个资源的企图 —— 不要这样做
02.public void enumerateFoo() throws SQLException {
03. Statement statement = null;
04. ResultSet resultSet = null;
05. Connection connection = getConnection();
06. try {
07. statement = connection.createStatement();
08. resultSet = statement.executeQuery("SELECT * FROM Foo");
09. // 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 plaincopyprint?
01.//不成功的释放多个资源的企图 —— 不要这样做
02.public void enumerateFoo() throws SQLException {
03. Statement statement = null;
04. ResultSet resultSet = null;
05. Connection connection = getConnection();
06. try {
07. statement = connection.createStatement();
08. resultSet = statement.executeQuery("SELECT * FROM Foo");
09. // 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.
//不成功的释放多个资源的企图 —— 不要这样做
public void enumerateFoo() throws SQLException {
Statement statement = null;
ResultSet resultSet = null;
Connection connection = getConnection();
try {
statement = connection.createStatement();
resultSet = statement.executeQuery("SELECT * FROM Foo");
// Use resultSet
}
finally {
if (resultSet != null)
resultSet.close();
if (statement != null)
statement.close();
connection.close();
}
}
这个 “解决方案” 不成功的原因在于,ResultSet 和 Statement 的 close() 方法自己可以抛出 SQLException,这会导致后面 finally 块中的 close() 语句不执行。您在这里有几种选择,每一种都很烦人:用一个 try..catch 块封装每一个 close(),可以使用嵌套 try...finally 块,或者编写某种小型框架用于管理资源获取和释放。
Java代码
01.//可靠的释放多个资源的方法
02.public void enumerateBar() throws SQLException {
03. Statement statement = null;
04. ResultSet resultSet = null;
05. Connection connection = getConnection();
06. try {
07. statement = connection.createStatement();
08. resultSet = statement.executeQuery("SELECT * FROM Bar");
09. // 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 plaincopyprint?
01.//可靠的释放多个资源的方法
02.public void enumerateBar() throws SQLException {
03. Statement statement = null;
04. ResultSet resultSet = null;
05. Connection connection = getConnection();
06. try {
07. statement = connection.createStatement();
08. resultSet = statement.executeQuery("SELECT * FROM Bar");
09. // 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. }
//可靠的释放多个资源的方法
public void enumerateBar() throws SQLException {
Statement statement = null;
ResultSet resultSet = null;
Connection connection = getConnection();
try {
statement = connection.createStatement();
resultSet = statement.executeQuery("SELECT * FROM Bar");
// Use resultSet
}
finally {
try {
if (resultSet != null)
resultSet.close();
}
finally {
try {
if (statement != null)
statement.close();
}
finally {
connection.close();
}
}
}
}
我们都知道应该使用 finally 来释放像数据库连接这样的重量级对象,但是我们并不总是这样细心,能够记得使用它来关闭流(毕竟,终结器会为我们做这件事,是不是?)。很容易忘记在使用资源的代码不抛出已检查的异常时使用 finally。如下代码展示了针对绑定连接的 add() 方法的实现,它使用 Semaphore 来实施绑定,并有效地允许客户机等待空间可用:
Java代码
01.//绑定连接的脆弱实现 —— 不要这样做
02.public class LeakyBoundedSet<T> {
03. private final Set<T> set = ...
04. private final Semaphore sem;
05. public LeakyBoundedSet(int bound) {
06. sem = new Semaphore(bound);
07. }
08. public boolean add(T o) throws InterruptedException {
09. sem.acquire();
10. boolean wasAdded = set.add(o);
11. if (!wasAdded)
12. sem.release();
13. return wasAdded;
14. }
15.}
[java] view plaincopyprint?
01.//绑定连接的脆弱实现 —— 不要这样做
02.public class LeakyBoundedSet<T> {
03. private final Set<T> set = ...
04. private final Semaphore sem;
05. public LeakyBoundedSet(int bound) {
06. sem = new Semaphore(bound);
07. }
08. public boolean add(T o) throws InterruptedException {
09. sem.acquire();
10. boolean wasAdded = set.add(o);
11. if (!wasAdded)
12. sem.release();
13. return wasAdded;
14. }
15.}
//绑定连接的脆弱实现 —— 不要这样做
public class LeakyBoundedSet<T> {
private final Set<T> set = ...
private final Semaphore sem;
public LeakyBoundedSet(int bound) {
sem = new Semaphore(bound);
}
public boolean add(T o) throws InterruptedException {
sem.acquire();
boolean wasAdded = set.add(o);
if (!wasAdded)
sem.release();
return wasAdded;
}
}
LeakyBoundedSet 首先等待一个许可证成为可用的(表示连接中有空间了),然后试图将元素添加到连接中。添加操作如果由于该元素已经在连接中了而失败,那么它会释放许可证(因为它不实际使用它所保留的空间)。
与 LeakyBoundedSet 有关的问题没有必要马上跳出:如果 Set.add() 抛出一个异常呢?如果 Set 实现中有缺陷,或者 equals() 或 hashCode() 实现(在 SortedSet 的情况下是 compareTo() 实现)中有缺陷,原因在于添加元素时元素已经在 Set 中了。当然,解决方案是使用 finally 来释放信号量许可证,这是一个很简单却容易被遗忘的方法。这些类型的错误很少会在测试期间暴露出来,因而成了定时 bomb,随时可能爆炸。如下代码展示了 BoundedSet 的一个更加可靠的实现:
Java代码
01.//使用一个 Semaphore 来可靠地绑定 Set
02.public class BoundedSet<T> {
03. private final Set<T> set = ...
04. private final Semaphore sem;
05. public BoundedHashSet(int bound) {
06. sem = new Semaphore(bound);
07. }
08. public boolean add(T o) throws InterruptedException {
09. 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 plaincopyprint?
01.//使用一个 Semaphore 来可靠地绑定 Set
02.public class BoundedSet<T> {
03. private final Set<T> set = ...
04. private final Semaphore sem;
05. public BoundedHashSet(int bound) {
06. sem = new Semaphore(bound);
07. }
08. public boolean add(T o) throws InterruptedException {
09. 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.}
//使用一个 Semaphore 来可靠地绑定 Set
public class BoundedSet<T> {
private final Set<T> set = ...
private final Semaphore sem;
public BoundedHashSet(int bound) {
sem = new Semaphore(bound);
}
public boolean add(T o) throws InterruptedException {
sem.acquire();
boolean wasAdded = false;
try {
wasAdded = set.add(o);
return wasAdded;
}
finally {
if (!wasAdded)
sem.release();
}
}
}
对于具有任意生命周期的资源,我们要回到 C 语言的时代,即手动地管理资源生命周期。在一个服务器应用程序中,客户机到服务器的一个持久网络连接存在于一个会话期间(比如一个多人参与的游戏服务器),每个用户的资源(包括套接字连接)在用户退出时必须被释放。好的组织是有帮助的;如果对每个用户资源的角色引用保存在一个 ActiveUser 对象中,那么它们就可以在 ActiveUser 被释放时(无论是显式地释放,还是通过垃圾收集而释放)而被释放。
具有任意生命周期的资源几乎总是存储在一个全局集合中(或者从这里可达)。要避免资源泄漏,因此非常重要的是,要识别出资源何时不再需要了并可以从这个全局集合中删除了。此时,因为您知道资源将要被释放,任何与该资源关联的非内存资源也可以同时被释放。
确保及时的资源释放的一个关键技巧是维护所有权的一个严格层次结构,其中的所有权具有释放资源的职责。如果应用程序创建一个线程池,而线程池创建线程,线程是程序可以退出之前必须被释放的资源。但是应用程序不拥有线程,而是由线程池拥有线程,因此线程池必须负责释放线程。当然,直到它本身被应用程序释放之后,线程池才能释放线程。
维护一个所有权层次结构有助于不至于失去控制,其中每个资源拥有它获得的资源并负责释放它们。这个规则的结果是,每个不能由垃圾收集单独收集的资源(即这样的资源,它直接或间接拥有不能由垃圾收集释放的资源)必须提供某种生命周期支持,比如 close() 方法。
如果说平台库提供终结器来清除打开的文件句柄,这大大降低了忘记显式地关闭这些句柄的风险,为什么不更多地使用终结器呢?原因有很多,最重要的一个原因是,终结器很难正确编写(并且很容易编写错)。终结器不仅难以编写正确,终结的定时也是不确定的,并且不能保证终结器最终会运行。并且终结还为可终结对象的实例化和垃圾收集带来了开销。不要依赖于终结器作为释放资源的主要方式。
垃圾收集为我们做了大量可怕的资源清除工作,但是有些资源仍然需要显式的释放,比如文件句柄、套接字句柄、线程、数据库连接和信号量许可证。当资源的生命周期被绑定到特定调用帧的生命周期时,我们通常可以使用 finally 块来释放该资源,但是长期存活的资源需要一种策略来确保它们最终被释放。对于任何一个这样的对象,即它直接或间接拥有一个需要显式释放的对象,您必须提供生命周期方法 —— 比如 close()、release()、destroy() 等 —— 来确保可靠的清除。
发表评论
-
利用Lucene打造站内搜索引擎的思路
2014-01-24 13:53 0利用Lucene打造站内搜索 ... -
错误日志
2013-10-28 16:54 0一、乱码解决方案: 1.配置tomcat编码: < ... -
eclipse:项目没有加载jre的类,而是使用WEB-INF/lib里的类
2013-07-09 15:58 0eclipse:项目没有加载jre的类,而是使用WEB-I ... -
Access restriction: The constructor SunJCE() is not accessible due to restrictio
2013-07-09 15:09 0今天在做JAVA网络编程实验,在搭建项目环境时出现了以下编 ... -
Server Tomcat v6.0 Server at localhost was unable to start within 45 seconds
2013-07-04 10:33 0错误 : Server Tomcat v6.0 Serve ... -
系统类加载器
2013-06-19 15:32 0public static Logger getLog(){ ... -
MyEclipse中防止代码格式化时出现换行的情况的设置
2013-05-14 09:43 0编辑完成代码,用MyEclipse的代码格式化后,本来不长的代 ... -
ant命令总结
2013-03-27 10:35 629ant命令总结 1 Ant是什么? Apache A ... -
java优化占用内存的方法一
2012-11-06 17:38 439(转)java做的系统给人的印象是什么?占内存!说道这句话就会 ... -
Session的工作原理
2012-09-28 14:44 0Session的工作原理 session ... -
DWR安全
2012-07-05 17:39 0在一些网站中,我们虽然使用了AJAX,但是我们并不希望用户能够 ... -
Java序列化与反序列化
2012-06-29 14:14 0首先解释两个概念,何为 ... -
cron表达式 quartz
2012-06-28 18:31 638CronTrigger配置格式:格式: [秒] [分] [小时 ...
相关推荐
从 Java 代码到 Java 堆 理解和优化您的应用程序的内存使用 很有用!~
当用户量过大,或服务器性能不足以支持大用户量,但同时又得不到扩容的情况下,进行性能分析,并对系统、应用、程序进行优化显得尤为重要,也是节省资源的一种必不可少的手段。目前大多数运维产品都基于JAVA语言开发...
JAVA进程占用高内存缘由分析与优化方法_.docx
JAVA 进程占用巨大内存的分析 在 64 位 LINUX 系统上,JAVA 进程的内存...JAVA 进程在 64 位 LINUX 下占用巨大内存是一个非常复杂的问题,但通过合理的配置和优化,我们可以解决这个问题,提高系统的性能和可靠性。
8、程序里不可避免大量使用字符串处理,避免使用 String,应大量使用StringBuffer,每一个 String 对象都得独立占用内存一块区域。 在使用字符串时,应该避免使用 String,而应该使用 StringBuffer,以免内存溢出。...
我们在开发应用都知道IO是系统性能的瓶颈,在...2.对数据实时计算,而且数据量很大时,比如电信系统的电信的二次批价和实时累账 3. 需实时统计数据,监控海量数据问题,比如ATM监控系统的可疑交易监控(广东中行)
JVM性能调优 Java内存模型和线程安全.
比如说,程序中存在被引用但无用的对象:程序引用了该对象,但后续不会或者不能再使用它,那么它占用的内存空间浪费了。 我们先来看看GC是如何工作的:监控每一个对象的运行状态,包括对象的申请、引用、被引用、...
一年前写过一个百万级别数据库数据生成配置xml文件的程序,程序目的是用来把数据库里面的数据生成xml文件....最近几天现场催的比较紧,最近抽空把这个问题处理了一下,在解决问题的过程中我把解决的步骤和方法记
此文章从5个方面讲解优化java堆: 1.JVM:对难以理解的东西产生恐惧感 2.数据和应用程序为王:回顾静态占用需求 3.业务流量设置规则:审查动态内存占用需求 4.量体裁衣 5.分而治之
Android 中加载大图片时内存溢出的...可以使用 BitmapFactory.Options、recycle() 方法、Java 中的内存优化技术、Android 中的内存优化技术、JNI 技术、避免使用大图片和缓存技术来减少内存的消耗,从而避免内存溢出。
java应用程序运行过程中会使用web应用服务器的内存,如执行报表获取的数据,运算的中间数据等都需要暂存在服务器内存中。 当没有空内存可用时,就会出现内存溢出错误,为了避免内存溢出的问题,我们一方面应适当启用...
java内存优化的文档,内容丰富,包括内存的优化,内存泄露分析等等。
需要注意的是,在使用这个方法时,需要弄清楚Excel的二进制格式,并且需要根据实际情况进行调整和优化。同时,我们也可以使用其他方法来解决大批量数据导出Excel时内存溢出的问题,例如使用流的方式读写Excel文件等...
二、解决火狐浏览器的内存占用率高的问题 Firefox 中没有设置快速缓存和自动释放内存,这可能会导致内存占用率高的问题。解决方法如下: 1. 打开 Firefox 浏览器,在地址栏中输入 about:config,然后在过滤器中...
然后,你去调查为何如此之慢,才意识到大部分的toString方法使用的是introspection,它其实是可以被优化的。 不过,首先让我们一起看看Javadoc回忆下Object.toString应当做什么:“返回该对象的字符串表示,该...
JVM内存结构:JVM的内存结构主要包括堆内存、方法区、栈(包括Java虚拟机栈和本地方法栈)、程序计数器等。堆内存是所有线程共享的内存区域,用于存放对象实例;方法区保存已加载的类信息、常量、静态变量和编译后的...
凡是搞研发的都知道,像eclipse,oracle这样的工具用的时间长了内存占用很大,而这款内存优化工具就是用来优化这种内存占用问题的,我强烈推荐给大家。
JProbe在简单易用的集成化套件中,为servlet、JSP和EJB应用代码提供了强大的Java性能分析、内存纠错、代码覆盖及线程分析功能。 JProbe Profiler JProbe Profiler * JProbe Profiler JProbe Profiler内置了Call ...
为提高系统的性能,需要重点优化占用 CPU 时间较多的方法和单次调用时间较长的方法,并且需要减少内存中的对象数量和占用内存的大小。 性能测试报告的结论是,对性能测试结果的分析和优化是非常重要的,以便于提高...