[Java] [内存泄露]

Overview

  • Java一个重要特性就是通过GC自动管理内存的回收,而不需要程序员自己来释放内存。因而,理论上Java中所有不再利用的对象所占用的内存都可以被GC会回收。但事实上,Java也存在内存泄露。

Java中的内存管理

  • 要了解Java中的内存泄露,首先就得知道Java中的内存是如何管理的。
  • Java使用可达性分析来判断对象是否可以被清理,详见link

Java中的内存泄露

  • 内存泄露就是指:不会再被使用的对象的内存没有(也不再能够)被释放。
  • 不同于C++中程序员必须手动释放所分配的对象内存,Java不用也不能自己释放内存。
  • 如果长生命周期的对象持有短生命周期的引用,就很可能会出现内存泄露
    例子如下:
    public class Simple {
      Object object;
      public void method1() {
        object = new Object();
        // ...else
      }
    }
    

    (长生命周期的对象(Simple对应的实例对象))持有短生命周期的引用(object引用))
    上述代码就是一个典型的例子(突然想起之前code review的时候,董老师always让我在用对象的时候才定义那个引用,一来可以避免上述现象,二来也使对象作用更清晰~)

    • 这里的object实例,我们只期望它作用于method1()中,其他地方不会再用。但是,在method1执行完后,object所分配的内存不会马上被认为是可以被释放的对象。只有在Simple类创建的对象释放后才会被释放。严格的说,这里就是一种内存泄露。
    • 解决的方法就是将object作为method1()中的局部变量。或者也可以:
      public class Simple {
        Object object;
        public void method1() {
          object = new Object();
          // ...else
          object = null;
        }
      }
  • Summary: 
    • 在堆中分配的内存,在没有将其释放掉时,就将所有能访问这块内存的方式都删掉,这是C++等需要自己垃圾回收的语言,Java中不需要担心。
    • 在内存对象已经不再需要的时候,还仍然保留着这块内存和它的引用,这是所有语言都可能出现的内存泄露方式。编程时如果不小心,我们很容易发生这种情况,如果不太严重,可能就只是短暂的内存泄露。

Examples & Solutions

  • 上述例子的解决原则就是 --> 尽量减小对象的作用域
  • 及时将无用对象标记为可被清理的对象。

静态集合类

  • 像HashMap、Vector等的使用最容易出现内存泄露,这些静态变量的生命周期和应用程序一致,他们所引用的所有对象Object也不能被释放。
  • 例如:
    Static Vector v = new Vector(10);
    for(int i = 0; i < 100; i++) {
      Object o = new Object();
      v.add(o);
      o = null;
    }
    

    上述例子中,虽然原引用o被释放,但对象本身仍然被Vector引用,所以该对象对GC来说是不可回收的。只能通过v = null的方式来回收。

 Closeable

  • 数据库连接、网络连接和io连接等,除非显示调用其close方法将其连接关闭,否则是不会自动被GC回收的。其原因仍然是长生命周期对象持有短生命周期对象的引用
  • 比如,操作数据库时我们会通过SessionFactory获取一个session:
    Session session = SessionFactory.openSession();  
    完成后:session.close()
    SessionFactory就是一个长生命周期的对象,而session相对是个短生命周期的对象。框架这样设计是合理的:它并不清除我们要使用session到多久,于是只能提供一个方法让我们决定何时不再使用。

Singleton

  • 单例对象通常生命周期与整个程序的生命周期差不多,所以是一个长生命周期地对象。如果该对象持有其他对象的引用,也很容易发生内存泄露。

与清理相关的方法

gc()

  • 对程序员而言,gc基本是透明的,不可见的。运行gc的函数是System.gc(),调用后启动垃圾回收器开始清理。
  • 但是根据java的语义,该函数不保证GC一定会执行。通常GC的线程优先级较低

finalize()

  • finalize()是Object类中的方法。
  • finalize绝不等于C++中的析构方法
  • 一旦GC准备好释放对象所占用的存储空间,将先调用其finalize()方法,并在下一次GC回收动作发生时,才会真正回收对象占用的内存。因此,一些清理工作可被放到finalize()中。
  • 该方法的一个重要作用就是:当在java中调用非java代码时,在这些非java代码中可能会用到相应的申请内存的操作,而在这些非java代码中并没有有效的释放这些内存,就可以使用finalize()方法,并在里面调用本地方法的free()等函数。
  • finalize()并不适合用作普通的清理工作。

FYI

原文地址:https://www.cnblogs.com/wttttt/p/7709057.html