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

转自:http://blog.sina.com.cn/s/blog_538b279a0100098d.html

写的相当不错滴......................


摘  要

   Java语言中,内存空间中垃圾回收的工作由垃圾回收器(Garbage Collector,GC)完成,GC可以有效地减少内存泄露发生的概率,但它的启动无规律可循,因此不能完全避免内存泄露。通过分析产生内存泄露的原因,提出了Java编程中的一些注意事项,有效地避免了内存泄露。
关键词  Java  内存泄露  垃圾回收器  虚拟机
 
1 引言
       Java语言,是一种可以编写跨平台应用软件的面向对象的程序设计语言,由升阳公司[1]的詹姆斯·高斯林等人于二十世纪90年代初开发。伴随着Java技术的普及,网络上越来越多的服务器程序采用Java技术,特别是Internet[2]使Java成为网上最流行的编程语言。在传统的高级编程语言(例C++)中,对象的创建和回收均由程序员自行负责,如果创建了对象而忘记回收,则会造成内存泄漏,长此以往,程序运行的时候可能会生成很多不清除的垃圾,浪费了不必要的内存空间,有时还可能引起系统的崩溃。在Java中,引入了垃圾回收机制:垃圾回收器(Garbage Collector, GC),可以自动回收内存中的垃圾,这是Java语言相对于其它语言的优势地方,但是内存泄漏并不会因此而完全避免


2 内存泄露
       大多数C++编译器不支持垃圾回收机制。通常使用C++编程的时候,程序员所创建的对象在创建时在堆栈上分配一块内存地址,当不需要这个对象,进行析构或者删除的时候再释放分配的内存空间。如果对象是在堆上分配了,而程序员又忘记进行释放,这些空间又无法自动回收,就会造成内存泄漏。而无法回收的内存空间,即丢失的内存(我们称之为“垃圾”),除非是重新启动系统否则永远也不会还给操作系统。长此以往,程序运行的时候可能会生成很多垃圾,浪费了不必要的内存空间。更糟糕的是,如果同一内存地址被删除两次的话,程序会变得不稳定,甚至崩溃
       Java语言则不同,上述的情况被自动垃圾回收机制自动处理。对象的建立和放置都是在内存堆栈上面进行的。程序或者其他的对象可以锁定一块堆栈地址来进行其他对象的引用。当一个对象没有任何引用的时候,Java的自动垃圾回收机制就发挥作用,自动删除这个对象所占用的空间,释放内存以避免内存泄漏。但是内存泄漏并不是就此而完全避免了,当程序员疏忽大意地忘记解除一个对象不应该有的引用的时候,内存泄漏仍然不可避免,不过发生的几率要比不启用垃圾回收机制的C++程序少很多。但是总体来讲,自动垃圾回收机制要安全和简单许多。
 
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虚拟机中两种方式被有机的结合运用。


4  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.");
       }
}

程序的运行结果是:

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


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

  这是因为,启动完垃圾回收器以后,它只能检测到在垃圾回收器强制启动之前程序运行所产生的垃圾Java的虚拟机尽最大的努力从被丢弃的对象上回收垃圾对于在启动垃圾回收器以后产生的垃圾,这个线程检测到的概率就非常小了,如果检测不到,就不能回收这些垃圾
      因此,Java中的垃圾回收器机制及System.gc()方法,并不能够完全避免内存泄露的问题,只是尽可能降低内存泄露的可能性和程度。


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

(1)尽量少用匿名对象,慎用内部类:
  匿名对象被使用完以后就会变成垃圾;而在内部类中,隐含着一个外部类对象的引用,这个引用也无法自动消除。
(2)在使用System.gc()方法的程序中,尽量少用finalize()方法:
  因为System.gc()方法在回收每一个对象所占用的内存空间时,都会调用finalize()方法,在这个方法中的任何操作都会增加垃圾回收的开销。
(3)慎用System.gc()方法,减少线程的个数:
  在程序中可以显式地调用System.gc()方法,但这种方法不能保证清除所有的垃圾。另外垃圾回收也是一个线程,也会消耗系统的资源,启动垃圾回收也可能会造成间歇性停顿。线程越多,垃圾回收线程挂起和恢复的可能性就越大,而耗费的时间就越长,系统的开销就越大。

6 结语
       本文对Java中的内存泄漏和垃圾回收机制进行了论述,分析了内存泄露的原因,以及System.gc()的工作方式,提出了一些避免内存泄露的方法,希望能为Java爱好者提供一些参考。

[1] 升阳公司:Sun Microsystems,美国的一家电脑公司,在中国大陆的正式中文名为“太阳计算机系统公司”,在台湾的正式中文名为“升阳电脑公司”。公司创建于1982年,1986年在美国成功上市。主要产品是工作站及服务器。
[2] Internet:interconnection network因特网或互联网,“联接网络的网络”,可以是任何分离的实体网络之集合,这些网络以一组通用的协定相连,形成逻辑上的单一网络。

posted @ 2014-04-05 21:41  horizon~~~  阅读(1978)  评论(0编辑  收藏  举报