首页 > 代码库 > Java中内存泄露及垃圾回收机制

Java中内存泄露及垃圾回收机制

3 垃圾回收机制

3.1 什么是垃圾

       垃圾,内存中的垃圾,即内存中已无效但又无法自动释放的空间。在Java语言中,没有引用句柄指向的类对象最容易成为垃圾。,产生垃圾的情况有很多,主要有以下3种:

(1)       超出对象的引用句柄的作用域时,这个引用句柄引用的对象就变成垃圾。

例:

       Person p1 = new Person();

       ……

引用句柄p1的作用域是从定义到“}”处,执行完这对大括号中的所有代码后,产生的Person对象就会变成垃圾,因为引用这个对象的句柄p1已超过其作用域,p1已经无效,Person对象不再被任何句柄引用了。       

(2)       没有超出对象的引用句柄的作用域时,给这个引用句柄赋值为空时,这个引用句柄引用的对象就变成垃圾。

例:

       Person p1 = new Person();

       …..

       p1 = null;

       ….

 

在执行完“p1=null;”后,即使句柄p1还没有超出其作用域,仍然有效,但它已被赋值为空,不再指向任何对象,则这个Person对象不再被任何句柄引用,变成了垃圾。此后p1还可以指向其它Person对象,因为还没有超出它的作用域。

(3)       创建匿名对象时,匿名对象用完以后即成垃圾。

例:

new Person();               //因为是匿名对象,没有引用句柄指向它,即为垃圾

new Person().print();

//当运行完匿名对象的print()方法,这个对象也变成了垃圾

……

       因此,在程序中应尽量少用匿名对象。

      

3.2 垃圾回收

    在Java程序运行过程中,一个垃圾回收器会(Garbage Collector,简称GC)不定时地被唤起检查是否有不再被使用的对象,并释放它们占用的内存空间。垃圾回收器的回收无规律可循,可能在程序的运行的过程中,一次也没有启动,也可能启动很多次。因此,并不会因为程序代码一产生垃圾,垃圾回收器就马上被唤起而自动回收垃圾,很可能到程序结束时垃圾回收器都没有启动。所以垃圾回收器并不能完全避免内存泄漏的问题。

    另一方面,垃圾回收会给系统资源带来额外的负担和时空开销。它被启动的几率越小,带来的负担的几率就越小。因此,垃圾的回收策略也很重要。

3.3 垃圾回收器的回收策略

    不同厂商、不同版本的Java虚拟机中的内存垃圾回收机制并不完全一样,通常越新版本的内存回收机制越快。而不同的Java虚拟机采用不同的回收策略,常用的有两种:复制式回收策略和自省式回收策略。

    复制式回收策略:先将正在运行中的程序暂停,然后把正在被使用的所有对象从它们所在的堆内存A里复制到另一块堆内存B,再释放堆内存A中的所有空间,这些那些不再使用的对象所占用的内存空间就会被释放掉。这种方式需要维护所需内存数量的至少两倍的内存空间,适合垃圾比较多的情况。当程序只产生了少量垃圾或者没有垃圾时,这种回收策略的效率就非常低。

    自省式回收策略:首先检测所有正在使用的对象,并为它们标注,比如用1来标注正在使用的对象,用0来标注不再被使用的对象,然后将所有标注为0的内存空间一次释放。因为标注会增大系统的开销,因此这种方式的速度仍然很慢,尤其是在垃圾比较多的情况下,效率会很低。这种方法适合垃圾比较少的情况。

    这两种方式具有互补性,因此在一些Java虚拟机中两种方式被有机的结合运用。

 System.gc()

       由于Java的垃圾回收器的启用不由程序员控制,而且回收也无规律可循,并不会一产生了垃圾,垃圾回收器就被唤起;有时甚至可能到程序终止,回收器都没有启动的机会。因此这个垃圾回收机制不是一个很可靠的机制。因为垃圾不能及时回收,它们所占用的内存空间不能释放,就会影响程序的性能;如果某段程序产生大量的垃圾而没有回收,回收工作也会变得困难。为了解决这个问题,Java提供一个System.gc()方法,可以强制启动垃圾回收器来回收垃圾,以减少内存泄露发生的概率。

       例:匿名对象会产生垃圾,如果担心这些垃圾不能及时回收,可以在使用完这些匿名对象以后,加上一条语句:System.gc(),强制启动垃圾回收器来回收垃圾。

class TestJc

{

       public void finalize()

       {

              System.out.println("Free the occupied memory...");

       }

      

       public static void main(String args[])

       {

              new TestJc();

              new TestJc();

              new TestJc();

              System.gc();

              System.out.println("End of program.");

       }

}

程序的运行结果是:

End of program.

Free the occupied memory...

Free the occupied memory...

Free the occupied memory...

 

       System.gc()有一个特点,就是在对象被当成垃圾从内存中释放前要调用finalize()方法,而且释放一个对象调用一次finalize()方法。从程序的运行结果可以看到:垃圾回收器启动以后,并不一定马上开始回收垃圾,很可能要等待一段时间才执行。这是因为在程序运行过程中,垃圾收集线程的优先级比较低,如果有比这个线程优先级高的线程,先运行这些优先级高的线程,等这些线程执行完毕,才进行垃圾回收。所以System.gc()方法只是一种“建议”,它建议Java虚拟机执行垃圾回收,释放内存空间,但什么时候能够回收就不能够预知了。

       如果我们把“System.gc();”语句,放在第二个匿名对象语句后面,再进行编译和执行,会发现结果是这样的:

End of program.

Free the occupied memory...

Free the occupied memory...

       这是因为,启动完垃圾回收器以后,它只能检测到在垃圾回收器强制启动之前程序运行所产生的垃圾,Java的虚拟机尽最大的努力从被丢弃的对象上回收垃圾;对于在启动垃圾回收器以后产生的垃圾,这个线程检测到的概率就非常小了,如果检测不到,就不能回收这些垃圾。

       因此,Java中的垃圾回收器机制及System.gc()方法,并不能够完全避免内存泄露的问题,只是尽可能降低内存泄露的可能性和程度。

5. Java编程中需要注意的事项

       为了提高垃圾的回收效率,在实际应用中,使用下列几种方法可以在一定程度上避免Java中的内存泄露

(1)       尽量少用匿名对象,慎用内部类

匿名对象被使用完以后就会变成垃圾;而在内部类中,隐含着一个外部类对象的引用,这个引用也无法自动消除。

(2)       在使用System.gc()方法的程序中,尽量少用finalize()方法

因为System.gc()方法在回收每一个对象所占用的内存空间时,都会调用finalize()方法,在这个方法中的任何操作都会增加垃圾回收的开销。

(3)       慎用System.gc()方法,减少线程的个数

在程序中可以显式地调用System.gc()方法,但这种方法不能保证清除所有的垃圾。另外垃圾回收也是一个线程,也会消耗系统的资源,启动垃圾回收也可能会造成间歇性停顿。线程越多,垃圾回收线程挂起和恢复的可能性就越大,而耗费的时间就越长,系统的开销就越大

 

Java中内存泄露及垃圾回收机制