1、基本概念java
内存溢出:简单地说内存溢出就是指程序运行过程当中申请的内存大于系统可以提供的内存,致使没法申请到足够的内存,因而就发生了内存溢出。算法
内存泄漏:内存泄漏指程序运行过程当中分配内存给临时变量,用完以后却没有被GC回收,始终占用着内存,既不能被使用也不能分配给其余程序,因而就发生了内存泄漏。数据库
内存溢出 out of memory,是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory;编程
内存泄露 memory leak,是指程序在申请内存后,没法释放已申请的内存空间,一次内存泄露危害能够忽略,但内存泄露堆积后果很严重,不管多少内存,早晚会被占光。服务器
memory leak会最终会致使out of memory!网络
内存泄露是指无用对象(再也不使用的对象)持续占有内存或无用对象的内存得不到及时释放,从而形成的内存空间的浪费称为内存泄露。内存泄露有时不严重且不易察觉,这样开发者就不知道存在内存泄露,但有时也会很严重,会提示你Out of memory。jvm
2、内存溢出的常见状况socket
内存溢出有如下几种常见的状况:函数
一、java.lang.OutOfMemoryError: PermGen space (持久带溢出)工具
咱们知道jvm经过持久带实现了java虚拟机规范中的方法区,而运行时常量池就是保存在方法区中的,所以发生这种溢出多是运行时常量池溢出,或是因为程序中使用了大量的jar或class,使得方法区中保存的class对象没有被及时回收或者class信息占用的内存超过了配置的大小。
二、java.lang.OutOfMemoryError: Java heap space (堆溢出)
发生这种溢出的缘由通常是建立的对象太多,在进行垃圾回收以前对象数量达到了最大堆的容量限制。
解决这个区域异常的方法通常是经过内存映像分析工具对Dump出来的堆转储快照进行分析,看究竟是内存溢出仍是内存泄漏。若是是内存泄漏,可进一步经过工具查看泄漏对象到GC Roots的引用链,定位出泄漏代码的位置,修改程序或算法;若是不存在泄漏,就是说内存中的对象确实都还必须存活,那就应该检查虚拟机的堆参数-Xmx(最大堆大小)和-Xms(初始堆大小),与机器物理内存对比看是否能够调大。
三、虚拟机栈和本地方法栈溢出
若是线程请求的栈深度大于虚拟机所容许的最大深度,将抛出StackOverflowError。
若是虚拟机在扩展栈时没法申请到足够的内存空间,则抛出OutOfMemoryError。
3、内存泄漏
内存泄漏的根本缘由是长生命周期的对象持有短生命周期对象的引用,尽管短生命周期的对象已经再也不须要,但因为长生命周期对象持有它的引用而致使不能被回收。
以发生的方式来分类,内存泄漏能够分为4类:
一、常发性内存泄漏。发生内存泄漏的代码会被屡次执行到,每次被执行的时候都会致使一块内存泄漏。
二、偶发性内存泄漏。发生内存泄漏的代码只有在某些特定环境或操做过程下才会发生。常发性和偶发性是相对的。对于特定的环境,偶发性的也许就变成了常发性的。因此测试环境和测试方法对检测内存泄漏相当重要。
三、一次性内存泄漏。发生内存泄漏的代码只会被执行一次,或者因为算法上的缺陷,致使总会有一块仅且一块内存发生泄漏。好比,在类的构造函数中分配内存,在析构函数中却没有释放该内存,因此内存泄漏只会发生一次。
四、隐式内存泄漏。程序在运行过程当中不停的分配内存,可是直到结束的时候才释放内存。严格的说这里并无发生内存泄漏,由于最终程序释放了全部申请的内存。可是对于一个服务器程序,须要运行几天,几周甚至几个月,不及时释放内存也可能致使最终耗尽系统的全部内存。因此,咱们称这类内存泄漏为隐式内存泄漏。
从用户使用程序的角度来看,内存泄漏自己不会产生什么危害,做为通常的用户,根本感受不到内存泄漏的存在。真正有危害的是内存泄漏的堆积,这会最终消耗尽系统全部的内存。从这个角度来讲,一次性内存泄漏并无什么危害,由于它不会堆积,而隐式内存泄漏危害性则很是大,由于较之于常发性和偶发性内存泄漏它更难被检测到。
下面总结几种常见的内存泄漏:
一、静态集合类引发的内存泄漏:
像HashMap、Vector等的使用最容易出现内存泄露,这些静态变量的生命周期和应用程序一致,他们所引用的全部的对象Object也不能被释放,从而形成内存泄漏,由于他们也将一直被Vector等引用着。
Vector<Object> v=new Vector<Object>(100); for (int i = 1; i<100; i++) { Object o = new Object(); v.add(o); o = null; }
在这个例子中,循环申请Object 对象,并将所申请的对象放入一个Vector 中,若是仅仅释放引用自己(o=null),那么Vector 仍然引用该对象,因此这个对象对GC 来讲是不可回收的。所以,若是对象加入到Vector 后,还必须从Vector 中删除,最简单的方法就是将Vector对象设置为null。
二、修改HashSet中对象的参数值,且参数是计算哈希值的字段
当一个对象被存储到HashSet集合中之后,修改了这个对象中那些参与计算哈希值的字段后,这个对象的哈希值与最初存储在集合中的就不一样了,这种状况下,用contains方法在集合中检索对象是找不到的,这将会致使没法从HashSet中删除当前对象,形成内存泄漏,举例以下:
public static void main(String[] args){ Set<Person> set = new HashSet<Person>(); Person p1 = new Person("张三","1",25); Person p2 = new Person("李四","2",26); Person p3 = new Person("王五","3",27); set.add(p1); set.add(p2); set.add(p3); System.out.println("总共有:"+set.size()+" 个元素!"); //结果:总共有:3 个元素! p3.setAge(2); //修改p3的年龄,此时p3元素对应的hashcode值发生改变 set.remove(p3); //此时remove不掉,形成内存泄漏 set.add(p3); //从新添加,能够添加成功 System.out.println("总共有:"+set.size()+" 个元素!"); //结果:总共有:4 个元素! for (Person person : set){ System.out.println(person); } }
三、监听器
在java 编程中,咱们都须要和监听器打交道,一般一个应用当中会用到不少监听器,咱们会调用一个控件的诸如addXXXListener()等方法来增长监听器,但每每在释放对象的时候却没有记住去删除这些监听器,从而增长了内存泄漏的机会。
四、各类链接
好比数据库链接(dataSourse.getConnection()),网络链接(socket)和io链接,除非其显式的调用了其close() 方法将其链接关闭,不然是不会自动被GC 回收的。对于Resultset 和Statement 对象能够不进行显式回收,但Connection 必定要显式回收,由于Connection 在任什么时候候都没法自动回收,而Connection一旦回收,Resultset 和Statement 对象就会当即为NULL。可是若是使用链接池,状况就不同了,除了要显式地关闭链接,还必须显式地关闭Resultset Statement 对象(关闭其中一个,另一个也会关闭),不然就会形成大量的Statement 对象没法释放,从而引发内存泄漏。这种状况下通常都会在try里面去链接,在finally里面释放链接。
五、单例模式
若是单例对象持有外部对象的引用,那么这个外部对象将不能被jvm正常回收,致使内存泄露。
不正确使用单例模式是引发内存泄露的一个常见问题,单例对象在被初始化后将在JVM的整个生命周期中存在(以静态变量的方式),若是单例对象持有外部对象的引用,那么这个外部对象将不能被jvm正常回收,致使内存泄露,考虑下面的例子:
class A{ public A(){ B.getInstance().setA(this); } .... } //B类采用单例模式 class B{ private A a; private static B instance=new B(); public B(){} public static B getInstance(){ return instance; } public void setA(A a){ this.a=a; } //getter... }
显然B采用singleton模式,它持有一个A对象的引用,而这个A类的对象将不能被回收。想象下若是A是个比较复杂的对象或者集合类型会发生什么状况。
一、尽早释放无用对象的引用。
二、避免在循环中建立对象。
三、使用字符串处理时避免使用String,应使用StringBuffer。
四、尽可能少使用静态变量,由于静态变量存放在永久代,基本不参与垃圾回收。