内存溢出的解决方法
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了内存溢出的解决方法相关的知识,希望对你有一定的参考价值。
内存溢出是指应用系统中存在无法回收的内存或使用的内存过多,最终使得程序运行要用到的内存大于虚拟机能提供的最大内存。内存中加载的数据量过于庞大,如一次从数据库取出过多数据;集合类中有对对象的引用,使用完后未清空,使得JVM不能回收;代码中存在死循环或循环产生过多重复的对象实体;使用的第三方软件中的BUG;启动参数内存值设定的过小;
检查对数据库查询中,是否有一次获得全部数据的查询。一般来说,如果一次取十万条记录到内存,就可能引起内存溢出。这个问题比较隐蔽,在上线前,数据库中数据较少,不容易出问题,上线后,数据库中数据多了,一次查询就有可能引起内存溢出。因此对于数据库查询尽量采用分页的方式查询。
检查代码中是否有死循环或递归调用。 参考技术A
内存溢出虽然很棘手,但也有相应的解决办法,可以按照从易到难,一步步的解决。
第一步,就是修改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程序运行时所有对象的申请、释放等动作,将内存管理的所有信息进行统计、分析、可视化。开发人员可以根据这些信息判断程序是否有内存泄漏问题。一般来说,一个正常的系统在其启动完成后其内存的占用量是基本稳定的,而不应该是无限制的增长的。持续地观察系统运行时使用的内存的大小,可以看到在内存使用监控窗口中是基本规则的锯齿形的图线,如果内存的大小持续地增长,则说明系统存在内存泄漏问题。通过间隔一段时间取一次内存快照,然后对内存快照中对象的使用与引用等信息进行比对与分析,可以找出是哪个类的对象在泄漏。
通过以上四个步骤的分析与处理,基本能处理内存溢出的问题。当然,在这些过程中也需要相当的经验与敏感度,需要在实际的开发与调试过程中不断积累。
Java虚拟机系列---内存溢出情况及解决方法
因为Java虚拟机内存有堆内存、方法区、虚拟机栈、本地方法栈和程序计数器五部分组成,其中程序计数器是唯一一块不会发生内存溢出异常的内存区,所以只有四类内存区可能发生内存溢出异常,其中虚拟机栈和本地方法栈都是Java方法执行的内存模型,所以它们的异常发生情况几乎相同,另外,在方法区中。又有一块内存是常量池,所以内存溢出的情况可分为Java堆溢出、虚拟机栈和本地方法栈溢出、方法区和运行时常量池溢三种情况。
一、Java堆溢出
1、产生的原因:因为堆中存放的是对象实例和数组,所以当对象数量>最大堆容量限制时,就会发生内存溢出异常;
2、解决方案:
1)如果对象不是必须的,但是又有指向GC Root(后面章节介绍)的引用链,此时无法被GC,就会出现内存泄露,可通过定位泄露原因在代码中找到解决方案;
2)如果对象是必须的,就要检查虚拟机栈的堆参数能否调大(当虚拟机内存总容量小于物理内存时可以调大),如果能调大可通过修改该参数来解决:
--Xmx:最大堆内存
--Xms:最小堆内存
如果--Xmx和--Xms相同,则说明堆内存不可动态扩展。
二、虚拟机栈和本地方法栈溢出
1、发生内存溢出的原因:
由于在HotSpot虚拟机中,不区分虚拟机栈和本地方法栈,所以设置本地方法栈大小的参数--Xoss无效,一般通过--Xss参数设置栈容量(我猜测ss是stack size的缩写,这样比较好记)
虚拟机中定义了两种异常情况:
1)当线程申请的栈深度超过虚拟机允许的最大栈深度时,会发生StackOverflowError异常;
2)当栈内存扩展时,如果不能申请到足够的内存,就会发生OutOfMemoryError异常
我们知道这部分内存是线程私有的,每个线程都需要分配一块内存,所以当线程很多时就会发生内存溢出,下面来分析一下这句话背后的原理:
①内存容量=堆内存+方法区内存+程序计数器内存(可忽略)+栈内存(虚拟机栈和本地方法栈);
②因为栈容量在编译器就可知,且一旦分配在运行期就不会改变,在栈容量一定的情况下,每个虚拟机栈分配到的栈容量越大,可以创建的线程数就越少;
③当线程过多时,就会导致栈容量不足,从而发生内存溢出;
2、解决方法:
首先,判断能不能减少线程数,如果能则减少线程数;如果不能减少线程数,就只能通过减小最大堆内存容量和最大栈容量来解决:
1)--Xmx:减少
2)--Xss:减少
三、方法区和运行时常量池溢出
1、异常发生原因
方法区主要存储class的相关信息,如类,名、访问修饰符、常量池、字段描述信息、方法描述信息等,所以如果运行时产生大量的类去填满方法区,就能出现内存溢出异常。这里就涉及到如何动态产生大量类的方法,一般有如下两种:
1)使用反射机制或动态代理
2)使用CGLib直接操作字节码
2、解决方法:
通过调节方法区大小参数--XX:PermSize和-XX:MaxPermSize限制方法区大小,当设置成相同的值时不可扩展。
除以上三种虚拟机内存溢出情况之外,还有一种本机直接内存溢出,可通过调节参数-XX:MaxDirectMemorysize指定,若不指定,则和Java堆内存大小一样。
以上就是Java虚拟机中的几种内存溢出情况及解决方法。
以上是关于内存溢出的解决方法的主要内容,如果未能解决你的问题,请参考以下文章