Chinaunix首页 | 论坛 | 博客
  • 博客访问: 161869
  • 博文数量: 53
  • 博客积分: 2042
  • 博客等级: 大尉
  • 技术积分: 425
  • 用 户 组: 普通用户
  • 注册时间: 2010-01-15 21:39
文章存档

2011年(6)

2010年(47)

分类: Java

2010-08-11 19:19:05

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虚拟机中两种方式被有机的结合运用。

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

       }

}

程序的运行结果是:

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

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

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

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

       这是因为,启动完垃圾回收器以后,它只能检测到在垃圾回收器强制启动之前程序运行所产生的垃圾,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爱好者提供一些参考。

阅读(1216) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~