c++与java中对象销毁及内存泄露...
时间:2010-08-11 来源:goodxiaolu
C++与Java中对象销毁及内存泄露
Destroying Objects And Memory Leaks in C++ And Java
摘要:本文详细阐述了C++与Java中对象销毁的机制;分析了C++用析构函数进行对象销毁时会产生内存泄露的原因;而Java中的垃圾回收机制可以自动回收垃圾,则有效的防止了内存泄露问题,大大提高了系统性能。
关键词:对象销毁,内存泄露,析构函数,垃圾回收机制
Abstract: Destroying objects in C++ and Java is introduced, and the reason of memory leaks in C++ when use destructors to destroy objects is also analyzed. In Java, Garbage Collector can collect garbage automatically,then it can avoid occurrence of memory leaks and improve system’s capability.
Key Words: destroying objects, memory leaks, destructor, Garbage Collector
引言
“Forrester[1] 最 新的调查显示,C++、微软Visual Basic和Java是众多公司产品体系的首选语言。对100家公司的调查显示,C/C++、Visual Basic和Java在产品体系中的使用比例分别是59%、61%和66%。”如今,C++和Java语言仍是大多数软件开发公司的首选语言,由这些公司 开发的软件系统质量的优劣是用户选择时考虑的一个重要因素。现在好多公司放弃用C++开发的软件系统而选用Java开发的软件系统,主要原因是前者容易产 生内存泄露而使系统性能降低。不彻底地销毁对象是引起内存泄露的主要原因,我们一起来分析一下这两种语言中对象的销毁问题。
1. 一个弃用C++而选择Java的实例
Java的设计者James Gosling有一次问一位在投资银行工作的软件工程师:他们为什么成为Java的早期使用者。这位工程师回答说原来他们采用一个较庞大的C++开发的软 件系统,经常发生内存泄露的问题,而他们不能够找出解决的方法,只能每天重启系统。重启会使所有的数据回到一个比较好的初始状态。当软件系统开始运行后, 内存就一点点泄露直到第二天的重启。内存的泄露使得交易的处理变得非常的缓慢。
随着银行生意的增长和客户的增多,跨时区的交易越来越多。在交易进行时是不能够重启系统的,如果重启,可能会造成较大的损失;而内存泄露问题又必须要求重 启计算机。继续采用这个系统会对银行的发展不利,所以他们决定使用Java开发的软件系统。当工程师谈到新系统时,说:“It just stayed up, and it just stayed up, and it just stayed up.”它可以持续工作而不需要重新启动计算机。
为什么C++会产生内存泄露呢?Java是不是就不产生内存泄露呢?我们从对象以及对象的销毁开始谈起。
2. 对象及内存泄露
“对象”一词在现实生活中经常会遇到,它表示现实世界中的某个具体的事物。而每个对象又隶属于某个特定的“类”。“类”是对于有同样属性 、共同的行为、共同的联系和共同的语义的对象 的描述,而“对象”是“类”的实例。
不管在C++语言中还是在Java语句中,都是先定义类,再定义对象,然后再使用这些对象,用完以后要销毁对象。对象在它的生存期内(从定义到销毁)是占用内存的。
在C++中,如果定义了对象,在内存中给它分配了空间;如果使用完以后,忘了销毁对象,即无法释放它所占用的内存空间。则这些内存空间一直被占用,直到重 启计算机才可释放,这样就造成了内存泄露。例:本来512Mb的内存,应该有300Mb的内存可用,可真正可以使用的内存只占100Mb,好像内存少了 200Mb一样。如果内存泄露比较严重,会造成系统性能降低,甚至崩溃。因此,为了减少内存泄露的发生,一定要注意对象的销毁问题。首先讨论一下C++中 对象的销毁。
3. C++中对象的销毁
在C++中,普通的对象离开它的作用域之后,它的析构函数会被自动调用,从而销毁这个对象,释放它所占的内存,不会发生内存泄露的问题。
例:文件“point.cpp”
class Point
{
public:
Point(int x=0,int y=0)
{
this->x=x; this->y=y;
cout<<"Construct... x="<<",Y="<<Y<<ENDL;< P>
在这个程序中p1和p2是两个对象,它们的作用域就是从定义到它们所在的main()函数的结束。定义时自动调用构造函数,销毁之前会自动调用析构函数, 因此在构造函数和析构函数中加上一些输出语句,就可知道这个对象所占的空间是否被释放。在这个程序,构造了两个对象,并销毁了两个对象,所以不会产生内存 泄露。
从一个程序执行的开始到结束,如果一个对象只是调用了构造函数,而没有调用析构函数,即没有销毁这个对象,则会产生内存泄露。那么什么情况下可能产生内存泄露呢?
3.1 匿名对象
如果我们把“point.cpp”中的main()函数中的语句变为
void main()
{ new Point; new Point(); new Point(3,3); }
程序的运行结果是:
调用了三次构造函数,而没有调用析构函数,表明这三个对象只是在内存中动态分配了空间,但并没有销毁它们,没有释放它们的空间,造成了内存泄露。
3.2 用 new 动态分配空间时
用new命令可以为对象在内存中动态分配一定的空间,并返回分配空间的首地址;如果在程序运行结束之前没有用delete来销毁这些对象,释放它们所占用的空间也会发生内存泄露。
如果把“point”中的main()改为:
void main()
{
Point *p1=new Point(1,1);
Point *p2=new Point(2,2);
delete p1;
}
程序的运行结果是:
可以看到指针p1所指向的对象因为调用了delete,则调用了析构函数,而销毁了对象;而指针p2指向的对象只是调用了new,而没有调用delete,没有被销毁,也发生了内存泄露。
3.3 指针悬挂
例:“string.cpp”
#include
class String
{
public:
String(int s){contents=new char[s]; }
~String()
{ cout<<"Deconstruct... "<< P>
private:
char *contents;
int size;
};
void main()
{
String s1(10),s2(20);
s1=s2;
}
在这个程序的main()函数中,创建了两个对象s1和s2,在创建时调用了它们的构造函数,为它们的数据成员contents分配了内存空间,并在执行 赋值语句s1=s2时,将对象s2的数据成员逐域复制到s1对象中去。即s1对象的指向字符串的指针contents不再指向原来的区域,而指向了s2的 字符串。这样,原来s1中的contents原来指向的区域就被封锁起来,没有办法释放,而这个区域也没有办法再用,产生了指针悬挂问题。而最后又使得一 块内存区域被释放了两次,这是又一个比较严重的问题。
指针悬挂也没有销毁对象,也产生了内存泄露。
C++的编程中,如果一个经常被调用的函数中出现了内存泄露现象。内存会一点点地被占用而不释放,到最后,系统的处理速度会越来越慢,严重影响系统的性能。要解决这个问题,只能通过重启计算机来完成,这样做的代价会很大。
那么,Java中是如何进行对象的销毁的呢?
4. Java中的内存泄露
Java程序中同样会发生内存泄露的问题,但是Java中引入了垃圾回收机制。这里所说的垃圾就是那些泄露的内存。
在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()方法,这个对象也变成了垃圾
……
}
因此,在程序中应尽量少用匿名对象。
5. Java中的对象销毁
Java语言没有提供析构函数,要解决内存泄露的问题,要销毁不再被引用的对象,就要借助其它方法,因此Java提供了一种非常好的机制:垃圾回收机制,即Garbage Collector,简称GC。
在Java中,不再被引用的对象所占据的内存由一个低优先级的垃圾回收线程自动回收。这个线程是在我们程序的执行过程中在后台持续运行的。在Java程序 运行过程中,一个垃圾回收器会不定时地被唤起检查是否有不再被使用的对象,并释放它们占用的内存空间。垃圾回收器的回收无规律可循,可能在程序的运行的过 程中,一次也没有启动,也可能启动很多次。因此,并不会因为程序代码一产生垃圾,垃圾回收器就马上被唤起而自动回收垃圾,很可能到程序结束时垃圾回收器都 没有启动。所以垃圾回收器并不能完全避免内存泄漏的问题。
正因为垃圾回收器启动的无规律性,Java又提供了一种强制启动垃圾回收器的方法:System.gc()方法。在程序中显式地加入这个语句,就会强制启 动垃圾回收器。垃圾回收器启动后,就会等待时机释放不再被引用的对象所占据的内存空间。但并不是一启动垃圾回收器,它就马上回收垃圾,如果这时有高优先级 的线程仍在运行,回收垃圾的线程需要等待这个高优先级的线程执行完毕以后才可执行。
另一方面,垃圾回收会给系统资源带来额外的负担和时空开销。它被启动的几率越小,带来的负担的几率就越小。因此,不提倡在程序代码中加入大量的System.gc()语句。
正因为垃圾回收器的自动回收功能,保证了Java开发的程序在长期运行期间产生比较少的内存泄露,提高了系统的性能,方便了用户的使用。
结束语
在C++中,可能因为不恰当地使用new和delete语句,而导致不能销毁某些对象,造成内存泄露,从而影响系统的性能;而Java提供了垃圾回收机制自动回收垃圾,销毁不再使用的对象,使得内存泄露的可能性变得很小。