第一篇:性能测试总结之内存泄露和内存溢出
性能测试总结之内存泄露和内存溢出
主要从以下几部分来说明,关于内存和内存泄露、溢出的概念,区分内存泄露和内存溢出;内存的区域划分,了解GC回收机制;重点关注如何去监控和发现内存问题;此外分析出问题还要如何解决内存问题。
下面就开始本篇的内容:
第一部分 概念
众所周知,java中的内存java虚拟机自己去管理的,他不想C++需要自己去释放。笼统地去 讲,java的内存分配分为两个部分,一个是数据堆,一个是栈。程序在运行的时候一般分配数据堆,把局部的临时的变量都放进去,生命周期和进程有关系。但 是如果程序员声明了static的变量,就直接在栈中运行的,进程销毁了,不一定会销毁static变量。
另外为了保证java内存不会溢出,java中有垃圾回收机制。System.gc()即垃圾收集机制是指jvm用于释放那些不再使用的对象所占用的内存。java语言并不要求jvm有gc,也没有规定gc如何工作。垃圾收集的目的在于清除不再使用的对象。gc通过确定对象是否被活动对象引用来确定是否收集该对象。
而其中,内存溢出就是你要求分配的java虚拟机内存超出了系统能给你的,系统不能满足需求,于是产生溢出。
内存泄漏是指你向系统申请分配内存进行使用(new),可是使用完了以后却不归还(delete),结果你申请到的那块内存你自己也不能再访问,该块已分配出来的内存也无法再使用,随着服务器内存的不断消耗,而无法使用的内存越来越 多,系统也不能再次将它分配给需要的程序,产生泄露。一直下去,程序也逐渐无内存使用,就会溢出。
第二部分 原理
JAVA垃圾回收及对内存区划分
在Java虚拟机规范中,提及了如下几种类型的内存空间:
◇ 栈内存(Stack):每个线程私有的。
◇ 堆内存(Heap):所有线程公用的。
◇ 方法区(Method Area):有点像以前常说的“进程代码段”,这里面存放了每个加载类的反射信息、类函数的代码、编译时常量等信息。
◇ 原生方法栈(Native Method Stack):主要用于JNI中的原生代码,平时很少涉及。
而Java的使用的是堆内存,java堆是一个运行时数据区,类的实例(对象)从中分配空间。Java虚拟机(JVM)的堆中储存着正在运行的应用程序所建立的所有对象,“垃圾回收”也是主要是和堆内存(Heap)有关。
垃圾回收的概念就是JAVA虚拟机(JVM)回收那些不再被引用的对象内存的过程。一般我们认为正在被引用的对象状态为“alive”,而没有 被应用或者取不到引用属性的对象状态为“dead”。垃圾回收是一个释放处于”dead”状态的对象的内存的过程。而垃圾回收的规则和算法被动态的作用于 应用运行当中,自动回收。
JVM的垃圾回收器采用的是一种分代(generational)回收策略,用较高的频率对年轻的对象(young generation)进行扫描和回收,这种叫做minor collection,而对老对象(old generation)的检查回收频率要低很多,称为major collection。这样就不需要每次GC都将内存中所有对象都检查一遍,这种策略有利于实时观察和回收。
(Sun JVM 1.3 有两种最基本的内存收集方式:一种称为copying或scavenge,将所有仍然生存的对象搬到另外一块内存后,整块内存就可回收。这种方法有效率,但需要有一定的空闲内存,拷贝也有开销。这种方法用于minor collection。另外一种称为mark-compact,将活着的对象标记出来,然后搬迁到一起连成大块的内存,其他内存就可以回收了。这种方法不 需要占用额外的空间,但速度相对慢一些。这种方法用于major collection.)
一些对象被创建出来只是拥有短暂的生命周期,比如 iterators 和本地变量。
另外一些对象被创建是拥有很长的生命周期,比如 高持久化对象等。
垃圾回收器的分代策略是把内存区划分为几个代,然后为每个代分配一到多个内存区块。当其中一个代用完了分配给他的内存后,JVM会在分配的内存 区内执行一个局部的GC(也可以叫minor collection)操作,为了回收处于“dead”状态的对象所占用的内存。局部GC通常要不Full GC要快很多。
JVM定义了两个代,年轻代(yong generation)(有时称为“nursery”托儿所)和老年代(old generation)。年轻代包括 “Eden space(伊甸园)”和两个“survivor spaces”。虚拟内存初始化的时候会把所有对象都分配到 Eden space,并且大部分对象也会在该区域被释放。当进行 minor GC的时候,VM会把剩下的没有释放的对象从Eden space移动到其中一个survivor spaces当中。此外,VM也会把那些长期存活在survivor spaces 里的对象移动到 老生代的“tenured” space中。当 tenured generation 被填满后,就会产生Full GC,Full GC会相对比较慢因为回收的内容包括了所有的 live状态的对象。pemanet generation这个代包括了所有java虚拟机自身使用的相对比较稳定的数据对象,比如类和对象方法等。
关于代的划分,可以从下图中获得一个概况:
如果垃圾回收器影响了系统的性能,或者成为系统的瓶颈,你可以通过自定义各个代的大小来优化它的性能。使用JConsole,可以方便的查看到当前应用所配置的垃圾回收器的各个参数。想要获得更详细的参数,可以参考以下调优介绍:
Tuning Garbage collection with the 5.0 HotSpot VM
http://java.sun.com/docs/hotspot/gc/index.html
最后,总结一下各区内存:
Eden Space(heap): 内存最初从这个线程池分配给大部分对象。
Survivor Space(heap):用于保存在eden space内存池中经过垃圾回收后没有被回收的对象。
Tenured Generation(heap):用于保持已经在 survivor space内存池中存在了一段时间的对象。
Permanent Generation(non-heap): 保存虚拟机自己的静态(refective)数据,例如类(class)和方法(method)对象。Java虚拟机共享这些类数据。这个区域被分割为只读的和只写的,Code Cache(non-heap):HotSpot Java虚拟机包括一个用于编译和保存本地代码(native code)的内存,叫做“代码缓存区”(code cache)
第三部分 监控(工具发现问题)
谈到内存监控工具,JConsole是必须要介绍的,它是一个用JAVA写的GUI程序,用来监控 VM,并可监控远程的VM,易用且功能强大。具体可监控JAVA内存、JAVA CPU使用率、线程执行情况、加载类概况等,Jconsole需要在JVM参数中配置端口才能使用。
由于是GUI程序,界面可视化,这里就不做详细介绍,具体帮助支持文档请参阅性能测试JConsole使用方法总结:
http://
http://Java.sun.com/javase/6/docs/technotes/tools/share/jconsole.html
在实际测试某一个项目时,内存出现泄露现象。起初在性能测试的1个小时中,并不明显,而在稳定性测试的时候才发现,应用的HSF调用在经过几个 小时运行后,就出现性能明显下降的情况。在服务日志中报大量HSF超时,但所调用系统没有任何超时日志,并且压力应用的load都很低。经过查看日志后,认为应用可能存在内存泄漏。通过jconsole 以及 jmap 工具进行分析发现,确实存在内存泄漏问题,其中PS Old Gen最终达到占用 100%的占用。如图所示:
从上图可以看到,虽然每次Full GC,JVM内存会有部分回收,但回收并不彻底,不可回收的内存对象会越来越多,这样便会出现以上的一个趋势。在Full GC无法回收的对象越来越多时,最终已使用内存达到系统分配的内存最大值,系统最后无内存可分配,最终down机。
第四部分 分析
经过开发和架构师对应用的分析,查看此时内存队列,看哪个对象占用数据最多,再利用jmap命令,对线程数据分析,如下所示:
num #instances #bytes class name
1: 9248056 665860032 com.taobao.matrix.mc.domain.**
2: 9248031 295936992 com.taobao.matrix.**
3: 9248068 147969088 java.util.**
4: 1542111 37010664 java.util.Date
前三个instances不断增加,指代的是同一个代码逻辑,异步分发的问题,堵塞消息,回收多次都无法回收成功。导致内存溢出。
此外,对应用的性能单独做了压测,他的性能只能支撑到一半左右,故发送消息的TPS,应用肯定无法处理过来,导致消息堆积,而JAVA垃圾回收期认为这些都是有用的对象,导致内存堆积,直至系统崩溃。
调优方法
由于具体调优方法涉及到应用的配置信息,故在此暂不列出,可以参考性能测试小组发布的《性能测试调优宝典》
第四部分 总结
内存溢出主要是由于代码编写时对某些方法、类应用不合理,或者没有预估到临时对象会占用很大内存量,或者把过多的数据放入JVM缓存,或者性能 压力大导致消息堆积而占用内存,以至于在性能测试时,生成庞大数量的临时对象,GC时没有做出有效回收甚至根本就不能回收,造成内存空间不足,内存溢出。
如果编码之前,对内存使用量进行预估,对放在内存中的数据进行评估,保证有用的信息尽快释放,无用的信息能够被GC回收,这样在一定程度上是可以避免内存溢出问题的。
第二篇:性能测试总结之内存泄露和内存溢出
性能测试总结之内存泄露和内存溢出
2009-12-10 作者:yunshuai 来源:Taobao QA Team
刚刚做完了一个项目的性能测试,“有幸”也遇到了内存泄露的案例,所以在此和大家分享一下。
主要从以下几部分来说明,关于内存和内存泄露、溢出的概念,区分内存泄露和内存溢出;内存的区域划分,了解GC回收机制;重点关注如何去监控内存问题;此外分析出问题还要如何解决内存问题。下面就开始本篇的内容: 第一部分 概念
众所周知,java中的内存java虚拟机自己去管理的,他不想C++需要自己去释放。笼统地去讲,java的内存分配分为两个部分,一个是数据堆,一个是序在运行的时候一般分配数据堆,把局部的临时的变量都放进去,生命周期和进程有关系。但是如果程序员声明了static的变量,就直接在栈中运行的,进了,不一定会销毁static变量。
另外为了保证java内存不会溢出,java中有垃圾回收机制。System.gc()即垃圾收集机制是指jvm用于释放那些不再使用的对象所占用的内存。java语言求jvm有gc,也没有规定gc如何工作。垃圾收集的目的在于清除不再使用的对象。gc通过确定对象是否被活动对象引用来确定是否收集该对象。而其中,内存溢出就是你要求分配的java虚拟机内存超出了系统能给你的,系统不能满足需求,于是产生溢出。
内存泄漏是指你向系统申请分配内存进行使用(new),可是使用完了以后却不归还(delete),结果你申请到的那块内存你自己也不能再访问,该块已分配出存也无法再使用,随着服务器内存的不断消耗,而无法使用的内存越来越多,系统也不能再次将它分配给需要的程序,产生泄露。一直下去,程序也逐渐使用,就会溢出。第二部分 原理
JAVA垃圾回收及对内存区划分
在Java虚拟机规范中,提及了如下几种类型的内存空间: ◇ 栈内存(Stack):每个线程私有的。◇ 堆内存(Heap):所有线程公用的。
◇ 方法区(Method Area):有点像以前常说的“进程代码段”,这里面存放了每个加载类的反射信息、类函数的代码、编译时常量等信息。◇ 原生方法栈(Native Method Stack):主要用于JNI中的原生代码,平时很少涉及。
而Java的使用的是堆内存,java堆是一个运行时数据区,类的实例(对象)从中分配空间。Java虚拟机(JVM)的堆中储存着正在运行的应用程序所建立的象,“垃圾回收”也是主要是和堆内存(Heap)有关。
垃圾回收的概念就是JAVA虚拟机(JVM)回收那些不再被引用的对象内存的过程。一般我们认为正在被引用的对象状态为“alive”,而没有被应用或者取用属性的对象状态为“dead”。垃圾回收是一个释放处于”dead”状态的对象的内存的过程。而垃圾回收的规则和算法被动态的作用于应用运行当中,自动回JVM的垃圾回收器采用的是一种分代(generational)回收策略,用较高的频率对年轻的对象(young generation)进行扫描和回收,这种叫做minor collec对老对象(old generation)的检查回收频率要低很多,称为major collection。这样就不需要每次GC都将内存中所有对象都检查一遍,这种策略有利于实时观收。
(Sun JVM 1.3 有两种最基本的内存收集方式:一种称为copying或scavenge,将所有仍然生存的对象搬到另外一块内存后,整块内存就可回收。这种效率,但需要有一定的空闲内存,拷贝也有开销。这种方法用于minor collection。另外一种称为mark-compact,将活着的对象标记出来,然后搬迁到一起块的内存,其他内存就可以回收了。这种方法不需要占用额外的空间,但速度相对慢一些。这种方法用于major collection.)一些对象被创建出来只是拥有短暂的生命周期,比如 iterators 和本地变量。另外一些对象被创建是拥有很长的生命周期,比如 高持久化对象等。
垃圾回收器的分代策略是把内存区划分为几个代,然后为每个代分配一到多个内存区块。当其中一个代用完了分配给他的内存后,JVM会在分配的内存行一个局部的GC(也可以叫minor collection)操作,为了回收处于“dead”状态的对象所占用的内存。局部GC通常要不Full GC要快很多。
JVM定义了两个代,年轻代(yong generation)(有时称为“nursery”托儿所)和老年代(old generation)。年轻代包括 “Eden space(伊甸园)”和两个“survivor虚拟内存初始化的时候会把所有对象都分配到 Eden space,并且大部分对象也会在该区域被释放。当进行 minor GC的时候,VM会把剩下的没有释放的Eden space移动到其中一个survivor spaces当中。此外,VM也会把那些长期存活在survivor spaces 里的对象移动到 老生代的“tenured” space中。当 tenured g被填满后,就会产生Full GC,Full GC会相对比较慢因为回收的内容包括了所有的 live状态的对象。pemanet generation这个代包括了所有java虚拟机自身相对比较稳定的数据对象,比如类和对象方法等。关于代的划分,可以从下图中获得一个概况:
如果垃圾回收器影响了系统的性能,或者成为系统的瓶颈,你可以通过自定义各个代的大小来优化它的性能。使用JConsole,可以方便的查看到当前应置的垃圾回收器的各个参数。想要获得更详细的参数,可以参考以下调优介绍: Tuning Garbage collection with the 5.0 HotSpot VM http://java.sun.com/docs/hotspot/gc/index.html 最后,总结一下各区内存:
Eden Space(heap): 内存最初从这个线程池分配给大部分对象。
Survivor Space(heap):用于保存在eden space内存池中经过垃圾回收后没有被回收的对象。Tenured Generation(heap):用于保持已经在 survivor space内存池中存在了一段时间的对象。
Permanent Generation(non-heap): 保存虚拟机自己的静态(refective)数据,例如类(class)和方法(method)对象。Java虚拟机共享这些类数据。这个区割为只读的和只写的,Code Cache(non-heap):HotSpot Java虚拟机包括一个用于编译和保存本地代码(native code)的内存,叫做“代码缓存区”(code cache)第三部分 监控(工具发现问题)
谈到内存监控工具,JConsole是必须要介绍的,它是一个用JAVA写的GUI程序,用来监控VM,并可监控远程的VM,易用且功能强大。具体可监控内存、JAVA CPU使用率、线程执行情况、加载类概况等,Jconsole需要在JVM参数中配置端口才能使用。由于是GUI程序,界面可视化,这里就不做详细介绍,具体帮助支持文档请参阅性能测试JConsole使用方法总结:
http:// http://Java.sun.com/javase/6/docs/technotes/tools/share/jconsole.html
在实际测试某一个项目时,内存出现泄露现象。起初在性能测试的1个小时中,并不明显,而在稳定性测试的时候才发现,应用的HSF调用在经过几个行后,就出现性能明显下降的情况。在服务日志中报大量HSF超时,但所调用系统没有任何超时日志,并且压力应用的load都很低。经过查看日志后,认可能存在内存泄漏。通过jconsole 以及 jmap 工具进行分析发现,确实存在内存泄漏问题,其中PS Old Gen最终达到占用 100%的占用。如图所示:
从上图可以看到,虽然每次Full GC,JVM内存会有部分回收,但回收并不彻底,不可回收的内存对象会越来越多,这样便会出现以上的一个趋势。在无法回收的对象越来越多时,最终已使用内存达到系统分配的内存最大值,系统最后无内存可分配,最终down机。第四部分 分析
经过开发和架构师对应用的分析,查看此时内存队列,看哪个对象占用数据最多,再利用jmap命令,对线程数据分析,如下所示: num #instances #bytes class name ———————————————-1: 9248056 665860032 com.taobao.matrix.mc.domain.** 2: 9248031 295936992 com.taobao.matrix.** 3: 9248068 147969088 java.util.** 4: 1542111 37010664 java.util.Date 前三个instances不断增加,指代的是同一个代码逻辑,异步分发的问题,堵塞消息,回收多次都无法回收成功。导致内存溢出。此外,对应用的性能单独做了压测,他的性能只能支撑到一半左右,故发送消息的TPS,应用肯定无法处理过来,导致消息堆积,而JAVA垃圾回收期些都是有用的对象,导致内存堆积,直至系统崩溃。
调优方法
由于具体调优方法涉及到应用的配置信息,故在此暂不列出,可以参考性能测试小组发布的《性能测试调优宝典》 第四部分 总结
内存溢出主要是由于代码编写时对某些方法、类应用不合理,或者没有预估到临时对象会占用很大内存量,或者把过多的数据放入JVM缓存,或者性大导致消息堆积而占用内存,以至于在性能测试时,生成庞大数量的临时对象,GC时没有做出有效回收甚至根本就不能回收,造成内存空间不足,内存如果编码之前,对内存使用量进行预估,对放在内存中的数据进行评估,保证有用的信息尽快释放,无用的信息能够被GC回收,这样在一定程度上是免内存溢出问题的。
第三篇:检查内存泄露
Debug Assertion Failed!
这个错误的原因可能是数组越界或出现了野指针.内存释放(或资源释放)时出现了错误
这是个很一般性的错误, 就像Windows报告说执行了非法操作一样.凭此信息无法判 断具体错误位置, 只能靠自己跟踪了
当出现这个错误的时候,我重新检查了自己new的指针,由于对于这块很发怵,所以把所有new的指针都避掉。但还是出现同样的问题。
后来又上网搜了一下,在《VC++6.0中内存泄漏检测》这篇文章中提到,“可用于被多态继承的基类其析构函数应当有virtual修饰“的法则(一不小心就忘了写virtual ^_^),”,哈哈,我也违反了,后来加上virtual后就没有问题了。
下面把那篇文章贴上来以供自己日后查看。
VC++6.0中内存泄漏检测(转)
VC++6.0中内存泄漏检测
这篇文章是对2004-09-02日发表的《VC++6.0中简单的内存泄漏检测事例代码》(已经删除)的更新.对C++代码而言,内存泄漏问题虽然有诸多方法避免,但实际代码编写的时候,或出于自信或出于复杂性的考虑,常常还会用到原始的operator new,这不可避免的会带来内存泄漏的可能,不久前本人因为违反了”可用于被多态继承的基类其析构函数应当有virtual修饰“的法则(一不小心就忘了写virtual ^_^),导致了内存泄漏,因此我觉得出于安全考虑,在代码中加入内存泄漏检查机制还是很必要的,也因为这次的内存泄漏事件促使我写出这一篇文章.VC++中本身就有内存泄漏检查的机制,你可以在向导生成的支持MFC的工程中看到如下代码:
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
通过它们,你能非常容易的发现代码中的内存泄漏,但是如果手工将这个功能移植到非MFC工程中去是很繁琐的一件事,另外它还有一个bug,在多线程并发调用这个DEBUG_NEW时会导致系统级错误,因此本人在此重写了这个功能,将以下的debug_new.h和debug_new.cpp添加到工程中,并在需要检测的cpp中#include ”debug_new.h“和main中一开始处加入REG_DEBUG_NEW宏即可.1.debug_new.h 源代码
/************************************************************************/ /* comment: 此文件与debug_new.cpp配合使用,用于在调试期发现内存泄漏 */
/* 仅在VC++编译器中适用(包括Intel C++,因为它使用了相同的库)*/
/* 作者: 周星星*/
/* 版权申明: 无,可任意 使用,修改 和 发布 */
/************************************************************************/ /* sample
#include
#include ”debug_new.h“ // +
using namespace std;
int main(void)
{
REG_DEBUG_NEW;// +
char* p = new char[2];
cout << ”--End--“ << endl;
return 0;
}
在VC++ IDE中按F5调试运行将会在Output窗口的Debug页看到类似如下的提示: Dumping objects->
d:test.cpp(10): {45} normal block at 0x003410C8, 2 bytes long.Data: < > CD CD
Object dump complete.如果不出现如上提示请Rebuild All一次.*/
#ifndef _DEBUG_NEW_H_
#define _DEBUG_NEW_H_
#ifdef _DEBUG
#undef new
extern void _RegDebugNew(void);
extern void* __cdecl operator new(size_t, const char*, int);
extern void __cdecl operator delete(void*, const char*, int);
#define new new(__FILE__, __LINE__)
#define REG_DEBUG_NEW _RegDebugNew();
#else
#define REG_DEBUG_NEW
#endif // _DEBUG
#endif // _DEBUG_NEW_H_
2.debug_new.cpp 源代码
/************************************************************************/ /* comment: 此文件与debug_new.h配合使用,用于在调试期发现内存泄漏 */
/* 仅在VC++编译器中适用(包括Intel C++,因为它使用了相同的库)*/
/* 作者: 周星星*/
/* 版权申明: 无,可任意 使用,修改 和 发布 */
/************************************************************************/ //#include ”debug_new.h“
#ifdef _DEBUG
#include
#include
class _CriSec
{
CRITICAL_SECTION criSection;
public:
_CriSec(){ InitializeCriticalSection(&criSection);}
~_CriSec(){ DeleteCriticalSection(&criSection);}
void Enter(){ EnterCriticalSection(&criSection);}
void Leave(){ LeaveCriticalSection(&criSection);}
} _cs;
void _RegDebugNew(void)
{
_CrtSetDbgFlag(_CRTDBG_REPORT_FLAG | _CRTDBG_LEAK_CHECK_DF);
}
void* __cdecl operator new(size_t nSize, const char* lpszFileName, int nLine)
{
// comment 1: MFC中提供的debug new虽然加了锁,但我在实际测试的时候发现多线程并发 // 调用的时候还是抛出了系统错误,所以我在这里加了一个线程互斥量.// comment 2: debug new和debug delete之间需不需要互斥我并不知道,保险起见,我同样 // 加了线程互斥量.// comment 3: 按照C++标准规定,在operator new失败后应当调用set_new_handler设置的 // 函数,但是MSDN中却说”头文件new中的set_new_handler是stub的,而应该使 // 用头文件new.h中的_set_new_handler“,这简直是滑天下之大稽.// 以下是VC++6.0中的set_new_handler定义:
// new_handler __cdecl set_new_handler(new_handler new_p)
// {
// assert(new_p == 0);// cannot use stub to register a new handler
// _set_new_handler(0);
// return 0;
// }
// 所以我也无计可施,只能舍弃set_new_handler的作用._cs.Enter();
void* p = _malloc_dbg(nSize, _NORMAL_BLOCK, lpszFileName, nLine);
_cs.Leave();
return p;
}
void __cdecl operator delete(void* p, const char* /*lpszFileName*/, int /*nLine*/)
{
_cs.Enter();
_free_dbg(p, _CLIENT_BLOCK);
_cs.Leave();
}
#endif
3.事例代码
#include
#include ”debug_new.h“
using namespace std;
int main(void)
{
REG_DEBUG_NEW;
char* p = new char[2];
p[0] = 'A';
p[1] = 'B';
cout << ”--End--" << endl;
return 0;
}
4.结果输出
在VC++ IDE中按F5调试运行将会在Output窗口的Debug页看到类似如下的提示: ……
Dumping objects->
d:test.cpp(10): {45} normal block at 0x003410C8, 2 bytes long.Data:
Object dump complete.……
第四篇:系统应用服务器内存溢出解决报告
XXX系统应用服务器内存溢出解决报告
xxxx股份有限公司
2010.9
目录
第一章 问题现象与分析................................................................................2
1.1、问题现象.....................................................................................2 1.2、通常导致这种现象的原因..................................................................2 1.3、xxx社保宕机现象对比分析...............................................................3 第二章
解决方法路线图..............................................................................4
2.1 jvm的调整...................................................................................4 2.2 减少jvm内存使用..........................................................................5 2.2.1 加快db访问速度,减少中间件并发业务量.......................................5 2.2.2 限制sql返回结果集..................................................................6 2.2.3 减少业务会话中存放的对象.........................................................6 2.3 补救措施......................................................................................6 第三章、解决结果与进一步建议......................................................................6
3.1 解决结果......................................................................................6 3.2 进一步建议...................................................................................7
第一章 问题现象与分析
1.1、问题现象
XXX应用服务器经常有内存溢出、系统没有响应的现象,尤其在每月的月末最为明显。目前的应用服务器有三种类型,其中ibm和linux应用服务器报告频繁出现内存溢出或没有响应的现象,hp unix应用服务器相稳定。在出现问题期间Weblogic无法响应任何客户端请求,大量请求加载到了这台没有响应的Server上,最后只有杀掉并重启这台应用服务器。
1.2、通常导致这种现象的原因
WLS Server 没响应可能的几种原因:
xxxx股份有限公司
1、繁重的I/O,呼叫DB时间过长导致中间件内存耗尽,server没有响应。
2、程序死循环,loop-backs,这种情况cpu很忙,系统没有响应。
3、连接到外部server,没响应,由于网络等原因 4、2个以上的执行者同步死锁
5、业务量过大,全部线程都被占用,出现队列等待现象
6、读写本地I/O,发生阻塞
WLS Server 宕机的原因:
OutOfMemory JNI程序 jvm的bug os的bug 1.3、xxx社保宕机现象对比分析
应用服务器没有响应分析
通过初步判断,对于xxx应用服务器没有响应的情况可以做如下排出法解决: ――程序死循环
这种情况会导致cpu非常繁忙,而通过目前观察,每次系统没响应的时候,cpu没有一直100%忙,另外,对出现问题时的java core分析没有发现这类线程,因此可以基本排除这种可能。
――连接到外部server,没响应,由于网络等原因
目前我们的业务基本都是直接通过中间件访问数据,没有通过应用服务器间调用或多数据库调用的,基本排除这种可能。――2个以上的执行者同步死锁
这种情况有可能,但比较难找,一般都是业务高峰的时候才有可能出现,跟应用人员了解后得知我们很少使用同步方式实现对资源的共享。另外通过对javacore进行分析,并未发现同步造成的死锁现象。
――业务量过大,全部线程都被占用,出现队列等待现象
通过观察我们的业务量在高峰时确实很大,但由于我们配置的线程数都很高,尽管出现宕机时也没有达到配置的上线,所以这个方面可以被排除。――繁重的I/O,呼叫DB时间过长导致中间件内存耗尽
由于我们经常有新业务变更,尤其近期还有居民医保业务上线,因此I/O问题导致
xxxx股份有限公司 的因素也需要重点考察!
――读写本地I/O,发生阻塞,多线程耗尽jvm内存
这种现象很可能发生,应重点给予关注
对WLS SERVER 宕机的几种情况的分析:
――OufOfMemory 目前xxx社保应用服务器出现宕机的时候基本都表现为这种现象,这也是中间件服务器最常见的现象。原因可能有多种,可能是平台的,多数情况下是物理内存配置过低,或jvm参数配置过低造成的。但通过对xxx社保配置参数进行分析发现参数基本合理,除了线程数和连接池配置稍大点,其它都很正常。由此分析是估计是其它原因造成的。
其它可能的原因可能是平台原因,比如jvm版本、垃圾回收方式和算法的缺陷等;也可能是应用造成的,比如业务并发量过大,内存不足造成,也可能是返回大结果集以及会话存放对象过多等原因。因此重点是找出可行的解决方案,避免出现内存溢出,减少对jvm内存的使用量。――平台bug 比如jni、jvm、os的bug等。每个weblogic版本都有对应的平台Jni,用来增加系统性能,但有时表现出不稳定的现象。Jvm和os版本对WLS server的稳定更是影响很大,通过以前的记录发现ibm和linux的应用服务器比hp出现的宕机频率更多些,因此有必要对ibm和linuxjvm做些分析和调整。
第二章
解决方法路线图
通过前面分析把解决问题的路线图定位在三方面,一个是调整现有平台jvm版本和参数,尽量达到平台的稳定性;另外一个是考虑如何减少jvm内存的使用上,尤其要解决访问DB慢以及返回大结果集这两方面,以期通过增强访问速度减少并发量,减少返回结果对内存的占用,从而使系统不发生或少发生OutOfMemory现象。另外,在意外出现宕机的情况下,通过负载均衡器的配置实现新请求直接发送给其它运行正常的服务器。
2.1 jvm的调整
采用方法:
调整ibm应用服务器的 jvm 系统参数 kcluster等,消除内存碎片。 调整 linux应用服务器的jvm,由bea的jrockit到sun jdk。
xxxx股份有限公司 实际效果:
Ibm服务器jvm为1.4.2,由于本版本的垃圾回收算法问题,会出现内存碎片,7月份相应调整了jvm参数,不过还是宕机很多次,没有明显效果。通过对8月份ibm服务器一次宕机javacore分析,发现在高峰阶段jvm还是会出现heap lock资源等待现象,经查ibm资料,基本上还是证实是内存碎片过多,并发申请内存太多导致系统无内存可用,最后宕机。不过8月份已经好很多了,才发现一次。这种情况目前最好方法是通过减少并发量来解决,由于应用的原因目前还无法升级jvm。 Linux服务器的jvm通过从jroick调整到sun后,在7月份就效果就很好。在8月份系统出现一次没有响应了,当时内存还是剩余很多的,现象也是OutOfMemory,但同时报sun javaException in thread “CompilerThread0” java.lang.OutOfMemoryError: requested 32760 bytes forChunkPool::allocate.Out of swap space? 经查这种现象跟在linux平台上jvm虚拟机不稳定有关,但这种现象不会经常出现。
2.2 减少jvm内存使用
想办法减少jvm内存使用量是解决问题的关键,减少应用服务器瞬时的并发量是一个好的途径,这就要保证快速的DB访问,小的结果集返回,session中少量的保存对象,同时会话保持不宜过长。
2.2.1 加快db访问速度,减少中间件并发业务量
采用方法1:通过oracle oem等工具跟踪监控大量耗I/O的语句,同时监控其它影响db服务器运行慢的进程。
实际效果:项目组调整低性能的sql后,该部分业务明显加快,没有再发现相关业务的大量全表扫描等情况。
采用方法2:对影响应收预览速度的ac40瘦身,重建并进行了分区。实际效果:根据现场反映速度有些提升。但由于对另外一个影响速度的关键表ab30无法瘦身(医保业务用),目前应收预览速度要有质的飞跃还很难。
xxxx股份有限公司
2.2.2 限制sql返回结果集
采用方法:从底层编写监控sql返回的大结果集程序,可定制记录数等参数
实际效果:目前已经抓到很多大sql,返回的结果集从几千达到10几万以上,基本消除了大结果集造成的原因,长期部署可对新程序新业务的大结果集检验有非常大的好处。
2.2.3 减少业务会话中存放的对象
采用方法:减少会话中的存放对象数,把没有必要或不需要使用的对象从会话中清除。
实际效果:这是一个备用手段,由于是改动了程序,为了生产安全考虑,暂时没有部署,在其它手段没有效果的情况下经过测试后再把它加载上去。
2.3 对本地读写的定位
通过对大量ibm java core分析,发现有读写I/O导致的堵塞。
2.4 补救措施
方法:在应用服务器上部署一个test.html静态页面,同时在负载均衡器上配置对这个静态页面的定时访问。
结果:通过8月份业务的实际运行考验确实起到了作用,7月份当一台服务器没有响应的时候马上就有业务人员反映,8月份却没有,同时我们也发现了的确新的请求就不再发给问题服务器,重新启动后新请求一点一点的加载上来,改善是很有效果的。
第三章、解决结果与进一步建议
3.1 解决结果
通过两个月周期的现场分析、调整,目前应用服务器系统稳定性已经明显提高了。尽管
xxxx股份有限公司 月底个别高峰的时候还会出现系统没有响应情况,但通过其它手段弥补已经不会影响业务的运行。
分析导致系统宕机因素是多方面的,包括java平台的原因,程序大结果集的原因,表数据量大/sql程序不够优化的原因,阵列I/O性能的原因、并发大业务的等原因。这些原因往往交织在一起,呈现出各种系统宕机状况。但最终只要我们提高sql的运行速度,降低jvm的内存使用量,把握好大的结果集和大的业务对象使用,尽管jvm本身有不稳定的情况,也不会或很少出现jvm宕机现象的。
3.2 进一步建议
优化或升级现有阵列
目前整体系统的瓶颈在I/O上,希望考虑阵列升级计划。 对目前业务数据和程序做一个周期瘦身和优化方案
从系统整体性能分析看,不良的I/O状况,越来越多的上亿记录的表导致大量对数据库操作业务缓慢,使中间件服务器并发量瞬时增加,中间件服务器的负载量加重,也成为中间件的宕机的一个主要原因。
优化本地I/O读写,将日志调试信息去掉。
对新业务继续监控大结果集(目前部署在11、12上)。
对新业务继续要做及时监控,抓大sql(耗I/O量大,运行次数多,阻塞其它业务)。
xxxx股份有限公司
第五篇:C与C 经典面试题(内存泄露)汇总
C、C++语言面试题2007-07-15 18:57 1.已知strcpy 函数的原型是:
char *strcpy(char *strDest, const char *strSrc);其中strDest 是目的字符串,strSrc 是源字符串。不调用C++/C 的字符串库函数,请编写函数 strcpy 答案:
char *strcpy(char *strDest, const char *strSrc){ if(strDest == NULL || strSrc == NULL)return NULL;if(strDest == strSrc)return strDest;char *tempptr = strDest;while((*strDest++ = *strSrc++)!= ‘ ’);return tempptr;}
2.已知类String 的原型为: class String { public: String(const char *str = NULL);// 普通构造函数 String(const String &other);// 拷贝构造函数 ~ String(void);// 析构函数
String & operate =(const String &other);// 赋值函数 private: char *m_data;// 用于保存字符串 };请编写String 的上述4 个函数。答案:
String::String(const char *str){ if(str == NULL)//strlen在参数为NULL时会抛异常才会有这步判断 { m_data = new char[1];m_data[0] = ' ';} else { m_data = new char[strlen(str)+ 1];strcpy(m_data,str);} }
String::String(const String &other){ m_data = new char[strlen(other.m_data)+ 1];strcpy(m_data,other.m_data);} String & String::operator =(const String &other){ if(this == &other)return *this;delete []m_data;m_data = new char[strlen(other.m_data)+ 1];strcpy(m_data,other.m_data);return *this;} String::~ String(void){ delete []m_data;}
3.简答
3.1 头文件中的ifndef/define/endif 干什么用? 答:防止该头文件被重复引用。
3.2#include
答:对于#include
答:C++语言支持函数重载,C 语言不支持函数重载。函数被C++编译后在库中的名字与C 语言的不同。假设某个函数的原型为: void foo(int x, int y);该函数被C 编译器编译后在库中的名字为_foo,而C++ 编译器则会产生像_foo_int_int 之类的名字。
C++提供了C 连接交换指定符号extern“C”来解决名字匹配问题。
3.4 一个类有基类、内部有一个其他类的成员对象,构造函数的执行顺序是怎样的。(Autodesk)
答:先执行基类的(如果基类当中有虚基类,要先执行虚基类的,其他基类则按照声明派生类时的顺序依次执行),再执行成员对象的,最后执行自己的。3.5 请描述一个你熟悉的设计模式(Autodesk)3.6 在UML 中,聚合(aggregation)和组合(composition)有什么区别 Autodesk)答案:聚合关系更强,类似于pages 和book 的关系;组合关系要弱,类似于books和bookshelf 的关系。
3.7C#和C++除了语法上的差别以外,有什么不同的地方?(Autodesk,Microsoft)答案:(C#我只是了解,不是很精通)
(1)c#有垃圾自动回收机制,程序员不用担心对象的回收。(2)c#严禁使用指针,只能处理对象。如果希望使用指针,则仅可在unsafe 程序块中能使用指针。(3)c#只能单继承。(4)必须通过类名访问静态成员。不能像C++中那样,通过对象访问静态成员。(5)在子类中覆盖父
类的虚函数时必须用关键字override,覆盖父类的方法要用关键字new 3.8ADO.net 和ADO 的区别?
答案:实际上除了“能够让应用程序处理存储于DBMS 中的数据“这一基本相似点外,两者没有太多共同之处。但是ADO 使用OLE DB 接口并基于微软的COM 技术,而ADO.NET 拥有自己的ADO.NET 接口并且基于微软的.NET 体系架构。众所周知.NET 体系不同于COM 体系,ADO.NET 接口也就完全不同于ADO和OLE DB 接口,这也就是说ADO.NET 和ADO是两种数据访问方式。ADO.net 提供对XML 的支持。3.9 New delete 与malloc free 的区别(Autodesk)区别: 1.new 自动计算需要分配的空间,而malloc要手动计算分配的空间。2.new 是类型安全的,而malloc不是。
如: int * p = new double[3];//编译时能够检查出错误
int* p = malloc(n*sizeof(double));//编译时不能够检查出错误 3.malloc/free需要库文件支持,而new/delete不用。4.operator new 对应于malloc, 但operator new 可以重载,可以自定义内存分配策略,甚至不做内存分配。但malloc做不到。5.new 能为非内部数据分配动态内存,而malloc不能。
3.9.2那为什么有了new/delete,还要malloc/free呢?
3.10 #define DOUBLE(x)x+x(Autodesk)i = 5*DOUBLE(10); i 是多少?正确的声明是什么? 答案:i 为60。正确的声明是#define DOUBLE(x)(x+x)3.11 有哪几种情况只能用intialization list 而不能用assignment?(Autodesk)答案:当类中含有const、reference 成员变量;基类的构造函数都需要参数;类中含有其他类的成员对象,而该类的构造函数都需要参数。3.11 C++是不是类型安全的?(Autodesk)答案:不是。两个不同类型的指针之间可以强制转换。C#是类型安全的。3.12 main 函数执行以前,还会执行什么代码?(Autodesk)答案:全局对象的构造函数会在main 函数之前执行。
3.13 描述内存分配方式以及它们的区别。(Autodesk , Microsoft)答案:1)从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static 变量。
(2)在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集。
(3)从堆上分配,亦称动态内存分配。程序在运行的时候用malloc 或new 申请任意多少的内存,程序员自己负责在何时用free 或delete 释放内存。动态内存的生存期由我们决定,使用非常灵活,但问题也最多。
3.14 什么是虚拟存储器?virtual memory 怎样映射到physical memory?页面替换算法有哪些?(Microsoft)见操作系统 p238 页。掌握的页面替换算法NRU(最近不用),FIFO,第二次机会页面替换算法,LRU(最近最少使用算法)
3.15 有四个同样的容器,里面装满了粒数相同的药丸,正常药丸的质量为m,变质药丸的质量为m+1,现在已知这四个容器中,有一个装的全是变质药丸,用电子秤只称一次,找出哪个容器装的是变质药丸(Microsoft)
答案:把四个容器依次编号为1、2、3、4,然后从中分别取出1、2、3、4 粒药丸,称这10 粒药丸的质量,如果质量为10m+1,则说明第一个容器装的是变质药丸,如果为10m+2 则说明第二个装的变质药丸,依次类推。
3.16 比较一下C++中static_cast 和 dynamic_cast 的区别。(Autodesk)
dynamic_casts在帮助你浏览继承层次上是有限制的。它不能被用于缺乏虚函数的类型上,它被用于安全地沿着类的继承关系向下进行类型转换。如你想在没有继承关系的类型中进行转换,你可能想到static_cast 3.17 Struct 和class 的区别(Autodesk)答案:struct 中成员变量和成员函数默认访问权限是public,class 是private 3.18 当一个类A 中没有生命任何成员变量与成员函数,这时sizeof(A)的值是多少,如果不是零,请解释一下编译器为什么没有让它为零。(Autodesk)
答案:肯定不是零。我举个反例,如果是零的话,声明一个class A[10]对象数组,而每一个对象占用的空间是零,这时就没办法区分A[0],A[1]…了 3.18 这道题我又找到答案了,为了确保每个对象都拥有唯一的地址!可查阅http://blog.csdn.net/smonster/articles/432767.aspx 3.19 在8086 汇编下,逻辑地址和物理地址是怎样转换的?(Intel)
答案:通用寄存器给出的地址,是段内偏移地址,相应段寄存器地址*10H+通用寄存器内地址,就得到了真正要访问的地址。
3.20 描述一下C++的多态(microsoft)
答案:C++的多态表现在两个部分,一个是静态连编下的函数重载,运算符重载;动态连编下的虚函数、纯虚函数(抽象类)
4.写出BOOL,int,float,指针类型的变量a 与零的比较语句。答案:
BOOL : if(!a)int : if(a == 0)float : const EXPRESSION EXP = 0.000001 if(a < EXP && a >-EXP)pointer : if(a!= NULL)
5.请说出const 与#define 相比优点 答案:
(1)const 常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查。而对后者只进行字符替换,没有类型安全检查,并且在字符替换可能会产生意料不到的错误。
(2)有些集成化的调试工具可以对const 常量进行调试,但是不能对宏常量进行调试。
6.简述数组与指针的区别
数组要么在静态存储区被创建(如全局数组),要么在栈上被创建。指针可以随时指向任意类型的内存块。
(1)修改内容上的差别 char a[] = “hello”;a[0] = ‘X’;
char *p = “world”;// 注意p 指向常量字符串
p[0] = ‘X’;// 编译器不能发现该错误,运行时错误(2)用运算符sizeof 可以计算出数组的容量(字节数)。sizeof(p),p 为指针得到的是一个指针变量的字节数,而不是p 所指的内存容量。C++/C 语言没有办法知道指针所指的内存容量,除非在申请内存时记住它。注意当数组作为函数的参数进行传递时,该数组自动退化为
同类型的指针。
char a[] = “hello world”;char *p = a;cout<< sizeof(a)<< endl;// 12 字节 cout<< sizeof(p)<< endl;// 4 字节 计算数组和指针的内存容量 void Func(char a[100]){ cout<< sizeof(a)<< endl;// 4 字节而不是100 字节 }
7.类成员函数的重载、覆盖和隐藏区别 答案:
成员函数被重载的特征:
(1)相同的范围(在同一个类中);(2)函数名字相同;(3)参数不同;
(4)virtual 关键字可有可无。
覆盖是指派生类函数覆盖基类函数,特征是:(1)不同的范围(分别位于派生类与基类);(2)函数名字相同;(3)参数相同;
(4)基类函数必须有virtual 关键字。
“隐藏”是指派生类的函数屏蔽了与其同名的基类函数,规则如下:
(1)如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual关键字,基类的函数将被隐藏(注意别与重载混淆)。
(2)如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual 关键字。此时,基类的函数被隐藏(注意别与覆盖混淆)
8.There are two int variables: a and b, don’t use “if”, “? :”, “switch” or other judgement statements, find out the biggest one of the two numbers.答案:((a + b)+ abs(a – b))/ 2
9.如何打印出当前源文件的文件名以及源文件的当前行号? 答案:
cout << __FILE__;cout<<__LINE__;__FILE__和__LINE__是系统预定义宏,这种宏并不是在某个文件中定义的,而是由编译器定义的。
10.main 主函数执行完毕后,是否可能会再执行一段代码,给出说明?
答案:可以,可以用_onexit 注册一个函数,它会在main 之后执行int fn1(void), fn2(void), fn3(void), fn4(void);
void main(void){ String str(“zhanglin”);_onexit(fn1);_onexit(fn2);_onexit(fn3);_onexit(fn4);printf(“This is executed first.n”);} int fn1(){ printf(“next.n”);return 0;} int fn2(){ printf(“executed ”);return 0;} int fn3(){ printf(“is ”);return 0;} int fn4(){ printf(“This ”);return 0;} The _onexit function is passed the address of a function(func)to be called when the program terminates normally.Successive calls to _onexit create a register of functions that are executed in LIFO(last-in-first-out)order.The functions passed to _onexit cannot take parameters.11.如何判断一段程序是由C 编译程序还是由C++编译程序编译的? 答案:
#ifdef __cplusplus cout<<“c++”;#else cout<<“c”;#endif
12.文件中有一组整数,要求排序后输出到另一个文件中 答案:
void Order(vector
{ int count = data.size();int tag = false;for(int i = 0;i < count;i++){ for(int j = 0;j < count1;j++){ if(data[j] > data[j+1]){ tag = true;int temp = data[j];data[j] = data[j+1];data[j+1] = temp;} } if(!tag)break;} } void main(void){ vector out.close();} 13.排序方法比较(intel) 排序方法平均时间 最坏时间 辅助存储 1:直接插入排序:插入排序的基本操作就是将一个数据插入到已经排好序的有序数据中,从而得到一个新的、个数加一的有序数据,算法适用于少量数据的排序,时间复杂度为O(n^2)。 2:起泡排序:依次比较相邻的两个数,将小数放在前面,大数放在后面(时间复杂度为O(n^2) n2/2-n/2,) 3:选择排序:每一趟从待排序的数据元素中选出最小(或最大)的一个元素,顺序放在已排好序的数列的最后,直到全部待排序的数据元素排完。选择排序是不稳定的排序方法(4)快速排序;通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序 n log n(5)堆排序; 二叉树 堆排序的最坏时间复杂度为O(nlog2n)。(6)归并排序; 14.一个链表的结点结构 struct Node { int data;Node *next;};typedef struct Node Node;(1)已知链表的头结点head,写一个函数把这个链表逆序(Intel)Node * ReverseList(Node *head)//链表逆序 { if(head == NULL || head->next == NULL)return head;Node *p1 = head;Node *p2 = p1->next;Node *p3 = p2->next;p1->next = NULL;while(p3!= NULL){ p2->next = p1;p1 = p2;p2 = p3;p3 = p3->next;} p2->next = p1;head = p2; return head;}(2)已知两个链表head1 和head2 各自有序,请把它们合并成一个链表依然有序。Node * Merge(Node *head1 , Node *head2){ if(head1 == NULL)return head2;if(head2 == NULL)return head1;Node *head = NULL;Node *p1 = NULL;Node *p2 = NULL;if(head1->data < head2->data){ head = head1;p1 = head1->next;p2 = head2;} else { head = head2;p2 = head2->next;p1 = head1;} Node *pcurrent = head;while(p1!= NULL && p2!= NULL){ if(p1->data <= p2->data){ pcurrent->next = p1;pcurrent = p1;p1 = p1->next;} else { pcurrent->next = p2;pcurrent = p2;p2 = p2->next;} } if(p1!= NULL)pcurrent->next = p1;if(p2!= NULL)pcurrent->next = p2; return head;}(2)已知两个链表head1 和head2 各自有序,请把它们合并成一个链表依然有序,这次要求用递归方法进行。(Autodesk)答案: Node * MergeRecursive(Node *head1 , Node *head2){ if(head1 == NULL)return head2;if(head2 == NULL)return head1;Node *head = NULL;if(head1->data < head2->data){ head = head1;head->next = MergeRecursive(head1->next,head2);} else { head = head2;head->next = MergeRecursive(head1,head2->next);} return head;} 15.分析一下这段程序的输出(Autodesk)class B { public: B(){ cout<<“default constructor”< B Play(B b){ return b;} int main(int argc, char* argv[]){ B temp = Play(5);return 0;} 请自己执行一下看看。 16.写一个函数找出一个整数数组中,第二大的数(microsoft)答案: const int MINNUMBER =-32767;int find_sec_max(int data[] , int count)//类似于1 4 4 4这样的序列将认为1是第二大数 { int maxnumber = data[0];int sec_max = MINNUMBER;for(int i = 1;i < count;i++){ if(data[i] > maxnumber){ sec_max = maxnumber;maxnumber = data[i];} else { if(data[i] > sec_max)sec_max = data[i];} } return sec_max;} 写一个在一个字符串中寻找一个子串第一个位置的函数 这个题目的一般算法比较简单我就不给出了,如果要求高效率的话请参见数据结构中的KMP 算法,不过在笔试时间有限情况下,写出那个算法还是挺难的。 一、#include “filename.h”和#include 的区别 #include “filename.h”是指编译器将从当前工作目录上开始查找此文件 #include 是指编译器将从标准库目录中开始查找此文件 二、头文件的作用 加强安全检测 通过头文件可能方便地调用库功能,而不必关心其实现方式 三、* , &修饰符的位置 对于*和&修饰符,为了避免误解,最好将修饰符紧靠变量名 四、if语句 不要将布尔变量与任何值进行比较,那会很容易出错的。 整形变量必须要有类型相同的值进行比较 浮点变量最好少比点,就算要比也要有值进行限制 指针变量要和NULL进行比较,不要和布尔型和整形比较 五、const和#define的比较 const有数据类型,#define没有数据类型 个别编译器中const可以进行调试,#define不可以进行调试 在类中定义常量有两种方式 1、在类在声明常量,但不赋值,在构造函数初始化表中进行赋值; 2、用枚举代替const常量。 六、C++函数中值的传递方式 有三种方式:值传递(Pass by value)、指针传递(Pass by pointer)、引用传递(Pass by reference) void fun(char c)//pass by value void fun(char *str)//pass by pointer void fun(char &str)//pass by reference 如果输入参数是以值传递的话,最好使用引用传递代替,因为引用传递省去了临时对象的构造和析构 函数的类型不能省略,就算没有也要加个void 七、函数体中的指针或引用常量不能被返回 Char *func(void) { char str[]=”Hello Word”; //这个是不能被返回的,因为str是个指定变量,不是一般的值,函数结束后会被注销掉 return str; } 函数体内的指针变量并不会随着函数的消亡而自动释放八、一个内存拷贝函数的实现体 void *memcpy(void *pvTo,const void *pvFrom,size_t size) { assert((pvTo!=NULL)&&(pvFrom!=NULL)); byte *pbTo=(byte*)pvTo;//防止地址被改变 byte *pbFrom=(byte*)pvFrom; while(size-->0) *pbTo++ = *pbForm++; return pvTo; } 九、内存的分配方式 分配方式有三种,请记住,说不定那天去面试的时候就会有人问你这问题 1、静态存储区,是在程序编译时就已经分配好的,在整个运行期间都存在,如全局变量、常量。 2、栈上分配,函数内的局部变量就是从这分配的,但分配的内存容易有限。 3、堆上分配,也称动态分配,如我们用new,malloc分配内存,用delete,free来释放的内存。 十、内存分配的注意事项 用new或malloc分配内存时,必须要对此指针赋初值。 用delete 或free释放内存后,必须要将指针指向NULL 不能修改指向常量的指针数据 十一、内容复制与比较 //数组…… char a[]=”Hello Word!”; char b[10]; strcpy(b,a); if(strcmp(a,b)==0) {} //指针…… char a[]=”Hello Word!”; char *p; p=new char[strlen(a)+1]; strcpy(p,a); if(strcmp(p,a)==0) {} 十二、sizeof的问题 记住一点,C++无法知道指针所指对象的大小,指针的大小永远为4字节 char a[]=”Hello World!” char *p=a; count< count< 而且,在函数中,数组参数退化为指针,所以下面的内容永远输出为4 void fun(char a[1000]) { count< } 十三、关于指针 1、指针创建时必须被初始化 2、指针在free 或delete后必须置为NULL 3、指针的长度都为4字节 4、释放内存时,如果是数组指针,必须要释放掉所有的内存,如 char *p=new char[100]; strcpy(p,”Hello World”); delete []p;//注意前面的[]号 p=NULL; 5、数组指针的内容不能超过数组指针的最大容易。 如: char *p=new char[5]; strcpy(p,”Hello World”);//报错 目标容易不够大 delete []p;//注意前面的[]号 p=NULL; 十四、关于malloc/free 和new /delete l malloc/free 是C/C+的内存分配符,new /delete是C++的内存分配符。 l 注意:malloc/free是库函数,new/delete是运算符 l malloc/free不能执行构造函数与析构函数,而new/delete可以 l new/delete不能在C上运行,所以malloc/free不能被淘汰 l 两者都必须要成对使用 l C++中可以使用_set_new_hander函数来定义内存分配异常的处理 如何查出内存泄漏和非法操作的BUG(在Release版本下)? 检查window(release)下的内存泄漏 1、放置关键字 assert() 2、生成map 文件。它并不往可执行文件exe 中添加任何东西,只是在编译的时候将各个函数入口地址记录在后缀为.map的文件中,程序崩溃的时候可以得到一个EIP地址,通过地址知道崩溃所在函数 3、可以设置断点,在希望设置断点的地方加入 _ASM int 3 4、可以通过编译时的汇编程序看出 5、采用第三方工具 十五、C++的特性 C++新增加有重载(overload),内联(inline),Const,Virtual四种机制 重载和内联:即可用于全局函数,也可用于类的成员函数; Const和Virtual:只可用于类的成员函数; 重载:在同一类中,函数名相同的函数。由不同的参数决定调用那个函数。函数可要不可要Virtual关键字。和全局函数同名的函数不叫重载。如果在类中调用同名的全局函数,必须用全局引用符号::引用。 覆盖是指派生类函数覆盖基类函数 函数名相同; 参数相同; 基类函数必须有Virtual关键字; 不同的范围(派生类和基类)。 隐藏是指派生类屏蔽了基类的同名函数相同 1、函数名相同,但参数不同,此时不论基类有无Virtual关键字,基类函数将被隐藏。 2、函数名相同,参数也相同,但基类无Virtual关键字(有就是覆盖),基类函数将被隐藏。 内联:inline关键字必须与定义体放在一起,而不是单单放在声明中。 Const:const是constant的缩写,“恒定不变”的意思。被const修饰的东西都受到强制保护,可以预防意外的变动,能提高程序的健壮性。 1、参数做输入用的指针型参数,加上const可防止被意外改动。 2、按值引用的用户类型做输入参数时,最好将按值传递的改为引用传递,并加上const关键字,目的是为了提高效率。数据类型为内部类型的就没必要做这件事情;如: 将void Func(A a)改为void Func(const A &a)。 而void func(int a)就没必要改成void func(const int &a); 3、给返回值为指针类型的函数加上const,会使函数返回值不能被修改,赋给的变量也只能是const型变量。如:函数const char*GetString(void);char *str=GetString()将会出错。而const char *str=GetString()将是正确的。 4、Const成员函数是指此函数体内只能调用Const成员变量,提高程序的键壮性。如声明函数 int GetCount(void)const;此函数体内就只能调用Const成员变量。 Virtual:虚函数:派生类可以覆盖掉的函数,纯虚函数:只是个空函数,没有函数实现体; 十六、extern“C”有什么作用? Extern “C”是由C++提供的一个连接交换指定符号,用于告诉C++这段代码是C函数。这是因为C++编译后库中函数名会变得很长,与C生成的不一致,造成C++不能直接调用C函数,加上extren “c”后,C++就能直接调用C函数了。 Extern “C”主要使用正规DLL函数的引用和导出 和 在C++包含C函数或C头文件时使用。使用时在前面加上extern “c” 关键字即可。 十七、构造函数与析构函数 派生类的构造函数应在初始化表里调用基类的构造函数; 派生类和基类的析构函数应加Virtual关键字。 不要小看构造函数和析构函数,其实编起来还是不容易。 #include class Base { public: virtual ~Base(){ cout<< “~Base” << endl;} }; class Derived : public Base { public: virtual ~Derived(){ cout<< “~Derived” << endl;} }; void main(void) { Base * pB = new Derived;// upcast delete pB; } 输出结果为: ~Derived ~Base 如果析构函数不为虚,那么输出结果为 ~Base 十八、#IFNDEF/#DEFINE/#ENDIF有什么作用 仿止该头文件被重复引用 转http://bbs.csai.cn/bbs/view.asp?Id={8DB2582C-97E1-428A-AD9C-358BCD02C506