内存溢出已经是软件开发历史上存在了近40年的“老大难”问题,像在“红色代码”病毒事件中表现的那样,它已经成为黑客攻击企业网络的“罪魁祸首”。如果在一个域中输入的数据超过了它的要求就会引发数据溢出问题,多余的数据就可以作为指令在计算机上运行。据有关安全小组称,操作系统中超过50%的安全漏洞都是由内存溢出引起的,其中大多数与微软的技术有关。内存溢出错误是大数据处理平台的常见错误,例如,国际知名的程序开发者问答网站stackoverflow上关于“Hadoop out of memory”的问题超过10000个,在Spark邮件列表上有10%的问题是关于“out of memory”。内存溢出错误会导致处理数据的任务失败,甚至会引发平台崩溃等严重后果。对于内存溢出大部分的处理方法是重新执行任务,然而,对于由系统配置、数据流、用户代码等原因而导致的内存溢出错误,即使用户重新执行任务依然无法避免。
内存溢出通俗理解就是内存不够,是指运行程序时要求的内存,超出了系统所能分配的范围,从而导致发生内存溢出。一般在运行大型软件时,所需的内存远远超出了主机内安装的内存所承受大小时就会发生这种情况。
当出现内存溢出这种情况,系统一般会提示相关信息,有时候会自动关闭软件甚至会造成设备卡死等现象,重启电脑或者软件后释放掉一部分内存又可以正常运行该软件或游戏一段时间。
内存溢出为了便于理解,不妨打个比方。缓冲区溢出好比是将十磅的糖放进一个只能装五磅的容器里。一旦该容器放满了,余下的部分就溢出在柜台和地板上,弄得一团糟。由于计算机程序的编写者写了一些编码,但是这些编码没有对目的区域或缓冲区——五磅的容器——做适当的检查,看它们是否够大,能否完全装入新的内容——十磅的糖,结果可能造成缓冲区溢出的产生。如果打算被放进新地方的数据不适合,溢得到处都是,该数据也会制造很多麻烦。但是,如果缓冲区仅仅溢出,这只是一个问题。到此时为止,它还没有破坏性。当糖溢出时,柜台被盖住。可以把糖擦掉或用吸尘器吸走,还原柜台本来的面貌。与之相对的是,当缓冲区溢出时,过剩的信息覆盖的是计算机内存中以前的内容。除非这些被覆盖的内容被保存或能够恢复,否则就会永远丢失。
在丢失的信息里有能够被程序调用的子程序的列表信息,直到缓冲区溢出发生。另外,给那些子程序的信息——参数——也丢失了。这意味着程序不能得到足够的信息从子程序返回,以完成它的任务。就像一个人步行穿过沙漠。如果他依赖于他的足迹走回头路,当沙暴来袭抹去了这些痕迹时,他将迷失在沙漠中。这个问题比程序仅仅迷失方向严重多了。入侵者用精心编写的入侵代码(一种恶意程序)使缓冲区溢出,然后告诉程序依据预设的方法处理缓冲区,并且执行。此时的程序已经完全被入侵者操纵了。
入侵者经常改编现有的应用程序运行不同的程序。例如,一个入侵者能启动一个新的程序,发送秘密文件(支票本记录,口令文件,或财产清单)给入侵者的电子邮件。这就好像不仅仅是沙暴吹了脚印,而且后来者也会踩出新的脚印,将我们的迷路者领向不同的地方,他自己一无所知的地方。
你屋子里的门和窗户越少,入侵者进入的方式就越少……
由于缓冲区溢出是一个编程问题,所以只能通过修复被破坏的程序的代码而解决问题。如果你没有源代码,从上面“堆栈溢出攻击”的原理可以看出,要防止此类攻击,我们可以:
1、开放程序时仔细检查溢出情况,不允许数据溢出缓冲区。由于编程和编程语言的原因,这非常困难,而且不适合大量已经在使用的程序;
2、使用检查堆栈溢出的编译器或者在程序中加入某些记号,以便程序运行时确认禁止黑客有意造成的溢出。问题是无法针对已有程序,对新程序来讲,需要修改编译器;
3、经常检查你的操作系统和应用程序提供商的站点,一旦发现他们提供的补丁程序,就马上下载并且应用在系统上,这是最好的方法。但是系统管理员总要比攻击者慢一步,如果这个有问题的软件是可选的,甚至是临时的,把它从你的系统中删除。举另外一个例子,你屋子里的门和窗户越少,入侵者进入的方式就越少。
内存溢出与数据库锁表的问题,可以说是开发人员的噩梦,一般的程序异常,总是可以知道在什么时候或是在什么操作步骤上出现了异常,而且根据堆栈信息也很容易定位到程序中是某处出现了问题。内存溢出与锁表则不然,一般现象是操作一般时间后系统越来越慢,直到死机,但并不能明确是在什么操作上出现的,发生的时间点也没有规律,查看日志或查看数据库也不能定位出问题的代码。
更严重的是内存溢出与数据库锁表在系统开发和单元测试阶段并不容易被发现,当系统正式上线一段时间后,操作的并发量上来了,数据也积累了一些,系统就容易出现内存溢出或是锁表的现象,而此时系统又不能随意停机或重启,为修正BUG带来很大的困难。
内存溢出虽然很棘手,但也有相应的解决办法,可以按照从易到难,一步步的解决。
第一步,就是修改JVM启动参数,直接增加内存。这一点看上去似乎很简单,但很容易被忽略。JVM默认可以使用的内存为64M,Tomcat默认可以使用的内存为128MB,对于稍复杂一点的系统就会不够用。在某项目中,就因为启动参数使用的默认值,经常报“OutOfMemory”错误。因此,-Xms,-Xmx参数一定不要忘记加。
第二步,检查错误日志,查看“OutOfMemory”错误前是否有其它异常或错误。在一个项目中,使用两个数据库连接,其中专用于发送短信的数据库连接使用DBCP连接池管理,用户为不将短信发出,有意将数据库连接用户名改错,使得日志中有许多数据库连接异常的日志,一段时间后,就出现“OutOfMemory”错误。经分析,这是由于DBCP连接池BUG引起的,数据库连接不上后,没有将连接释放,最终使得DBCP报“OutOfMemory”错误。经过修改正确数据库连接参数后,就没有再出现内存溢出的错误。
查看日志对于分析内存溢出是非常重要的,通过仔细查看日志,分析内存溢出前做过哪些操作,可以大致定位有问题的模块。
第三步,安排有经验的编程人员对代码进行走查和分析,找出可能发生内存溢出的位置。重点排查以下几点:
检查代码中是否有死循环或递归调用。
检查是否有大循环重复产生新对象实体。
检查对数据库查询中,是否有一次获得全部数据的查询。一般来说,如果一次取十万条记录到内存,就可能引起内存溢出。这个问题比较隐蔽,在上线前,数据库中数据较少,不容易出问题,上线后,数据库中数据多了,一次查询就有可能引起内存溢出。因此对于数据库查询尽量采用分页的方式查询。
检查List、MAP等集合对象是否有使用完后,未清除的问题。List、MAP等集合对象会始终存有对对象的引用,使得这些对象不能被GC回收。
第四步,使用内存查看工具动态查看内存使用情况。某个项目上线后,每次系统启动两天后,就会出现内存溢出的错误。这种情况一般是代码中出现了缓慢的内存泄漏,用上面三个步骤解决不了,这就需要使用内存查看工具了。
内存溢出内存查看工具有许多,比较有名的有:Optimizeit Profiler、JProbeProfiler、JinSight和Java1.5的Jconsole等。它们的基本工作原理大同小异,都是监测Java程序运行时所有对象的申请、释放等动作,将内存管理的所有信息进行统计、分析、可视化。开发人员可以根据这些信息判断程序是否有内存泄漏问题。一般来说,一个正常的系统在其启动完成后其内存的占用量是基本稳定的,而不应该是无限制地增长的。持续地观察系统运行时使用的内存的大小,可以看到在内存使用监控窗口中是基本规则的锯齿形的图线,如果内存的大小持续地增长,则说明系统存在内存泄漏问题。通过间隔一段时间取一次内存快照,然后对内存快照中对象的使用与引用等信息进行比对与分析,可以找出是哪个类的对象在泄漏。
通过以上四个步骤的分析与处理,基本能处理内存溢出的问题。当然,在这些过程中也需要相当的经验与敏感度,需要在实际的开发与调试过程中不断积累。
有的游戏在XP SP2系统下会出现内存溢出问题,比如在九阴真经、红色警戒3、穿越火线等游戏时出现死机、电脑自动重启等现象,解决方法是将系统升级到SP3或更换XP SP3系统。
以Android开发为例,在开发过程中经常遇到Android内存溢出的意外情况的发生。
以下是国内外总结造成内存溢出的几点现象。
1.大量位图的加载
Bitmap代表一张位图文件,扩展名是.bmp或者.dip,它是非压缩格式,其显示效果较好,但缺点就是需要占用大量的存储空间。它是windows标准格式图形文件,由点组成,每一个点代表一个像素。每个点可以由多种色彩表示,包括2、4、8、16、24和32位色彩。色彩越高,显示效果越好,但所占用的字节数也就越大。计算一张Bitmap所占内存大小主要由3个因数有关,即图片宽度,图片长度,单位像素所占用的字节数。大小=图像长度*图片宽度*单位像素占用的字节数。有时候我们需要从网络上获取大量的图片并且展现在view中,但是如果图片较大,一次性加载大量Bitmap,那么程序可用内存会瞬间增长,引起OOM。
2.位图对象没有及时释放
当程序中需要操作Bitmap 对象的时候,当它不在被使用的时候,可以调用Bitmap.recycle()方法回收此对象的像素所占用的内存,如果对Bitmap没有及时释放,在程序长期运行过程中,就很有可能造成OOM意外情况的发生。
3.查询数据库没有关闭游标
程序中经常会进行查询数据库的操作,但是经常会有使用完毕Cursor后没有关闭的情况。如果我们的查询结果集比较小,对内存的消耗不容易被发现,只有在常时间大量操作的情况下才会复现内存问题,这样就会给以后的测试和问题排查带来困难和风险。
4.构造Adapter时,没有使用缓存的convertView
以构造ListView的BaAdapter为例,在BaAdapter中提高了方法:publicView getView(int position,View convertView,ViewGroup parent)来向ListView提供每一个item所需要的view对象。初始时ListView 会从BaAdapter中根据当前的屏幕布局实例化一定数量的view对象,同时ListView会将这些view对象缓存起来。当向上滚动ListView时,原先位于最上面的list item的view对象会被回收,然后被用来构造新出现的最下面的listitem.这个构造过程就是由getView()方法完成的,getView()的第二个形参View convertVicw就是被缓存起来的listitem的view对象(初始化时缓存中没有view对象则convertView是null)。如果我们不去使用convertView,而是每次都在getView()中重新实例化一个View对象的话,即浪费资源也浪费时间,也会使得内存占用越来越大。
造成这种现象的原因通常有两种:
第一种是由于长期保持某些资源的引用,垃圾回收器无法回收它,从而使该资源不能够及时释放,也称为内存泄露;
另外一种是当需要保存多个耗用内存过大或当加载单个超大的对象时,该对象的大小超过了当前剩余的可用内存空间。
以Android程序为例:
1.由强引用造成的内存溢出
若所有的引用都是强引用,则大量内存会被占用,最终导致内存溢出。
解决方法:使用弱引用或软引用,软引用的对象在内存不足时可被GC回收,弱引用的对象在垃圾回收时可被回收。
2.由大量图片显示导致的内存溢出
为解决由大量图片显示造成的内存溢出,可以使用BitmapFactory.Options类,在返回参数时,只返回Bitmap的尺寸大小,而不将其加载到内存中,可有效减少内存溢出。同时在加载完后调用system.gc()通知系统及时回收。
3.从数据库中取出大量数据造成的内存溢出
检查在数据库查询中,是否有一次获得全部数据的查询。一般而言,如果一次取十万条记录到内存,就可能引起内存溢出。该问题比较隐蔽,在上线前,数据库中数据较少,通常运行正常,上线后,数据库中数据增多,一次查询即有可能引起内存溢出。因此,对于数据库查询,尽量采用分页的方式查询。
4.代码中存在死循环或循环产生过多重复对象实体造成的内存溢出
出现这种情况,只能通过查看日志找出产生该问题的原因,检查代码中是否有死循环、递归调用,或大循环重复产生的新对象实体。
避免内存溢出的常用方法众所周知,以Android 开发为例,每个Android应用程序在运行时都有一定的内存限制,限制大小一般为16MB或24MB(视平台而定)。当应用程序在实际运行过程中没有做到合理、有效利用内存空间,超过该限制大小就会内次溢出。
下面是列举了国内外在Android应用程序开发过程中应对内存溢出而经常采用的方法。
内存泄露的检测
内存溢出和内存泄露是两个不同的现象,内存泄露是指长期保持某些资源的引用,垃圾回收器无法回收它,从而造成该资源不能够及时释放,随着程序运行时间的增加,占用存储空间越来越多,致使有效可再利用的存储空间不足,当储存别的资源时引发内存溢出。
内存泄露是造成内存溢出的一个很主要的原因。因此,在实际的开发过程中要坚决杜绝内存泄露的现象发生。由于Android应用程序是基于虚拟机的,其内存管理都是由Dalivk代为管理,GC回收不是很及时。如果有一个正常的应用程序在其运行稳定后其内存的占用量是不会无限制的增长,是保持在一个稳定的水平。
同样,对任何一个类的对象的使用个数也有一个相对稳定的上限,没有出现持续增长的情况。当我们持续地观察某个应用程序运行过程中使用内存的大小和各实例的个数时,如果内存的大小持续增长,则说明系统存在内存泄露情况。比如一个Activity被关掉之后,其内存的引用对象会在下次GC回收的时候通过回收算法计算,如果这部分内存已经属于可回收的对象,那么这些对象会被一并回收。
在重复开发关闭某个应用程序的时候,内存一直在向上爬升,也就是说每次关闭这个Activity 的时候,有些应该释放的内存并没有被释放掉。
采用二级缓冲机制
每次需要加载图片的时候,首先从特定的内存中查找。如果内存中没有再从SD卡文件中查找,如果没找到,则通过网络获取。当获得来自网络数据时,先缓冲到底层由硬引用实现的缓冲中(一级缓冲),同时缓冲到文件中(二级缓冲)。
根据硬引用的特性,当回收垃圾的时候自动执行,人为无法干预,即使抛出OOM错误,致使应用系统异常终止,也不会随意回收具有强引用的对象来解决内存不足的问题。
假如当前的网络状态很好,下载速度很快的环境中,当快速翻动聊天列表需要快速加载并显示大量图片的时候,由于对这些图片是缓冲在LruCache实现的一级缓冲中的,当内存吃紧的时候一级缓冲自动回收,回收的速度远小于下载并缓冲图片速度,这时候就很容易导致OOM的发生。
等比例缩小位图文件
如果位图文件太大,则可以通过设置BitmapFactory.Options.inSampleSize(采样率)来实现等比例缩小该文件,并且设置BitmapFactory.Options的inJustDecodeBounds为true,先获取到宽高,这时候位图并不会加载到内存中,然后计算缩放比例再加载位图适应view控件,这样可以避免OOM的产生。
优化DalivkVM的堆内存
分配堆(heap)是VM中占用内存最多的部分,通常是通过动态分配来获得。其大小处于动态变化中,当堆实际的利用率偏离设定值时,虚拟机会在GC的时候调整堆的大小,从而使实际占用率呈偏大的趋势靠拢。
强制回收内存的信息
由于Android是采用Java语言实现,因此Android的内存回收也和Java内存回收一样的机制:通过GC自动管理内存。该机制是通过不定时检测是否有不被使用的对象,如果有则回收这些对象,释放内存。但是GC的回收时不规律的,人为无法控制的。
通常会通过System.gc()方法来强制启动GC来回收垃圾,以便减小OOM发生的概率。但该方法只是告诉机器回收垃圾,当也有可能不会立刻回收。具体情况取决于机器当时所处的运行情况。
本文发布于:2022-10-22 18:45:13,感谢您对本站的认可!
本文链接:http://www.wtabcd.cn/fanwen/fan/78/348312.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |