内存溢出的解决思路

内存溢出是指应用系统中存在没法回收的内存或使用的内存过多,最终使得程序运行要用到的内存大于虚拟机能提供的最大内存。java

   引发内存溢出的缘由有不少种,常见的有如下几种:
  1.内存中加载的数据量过于庞大,如一次从数据库取出过多数据;
  2.集合类中有对对象的引用,使用完后未清空,使得JVM不能回收;
  3.代码中存在死循环或循环产生过多重复的对象实体;
  4.使用的第三方软件中的BUG;
  5.启动参数内存值设定的太小;程序员

内存溢出的解决方案
      第一步,修改JVM启动参数,直接增长内存。(-Xms,-Xmx参数必定不要忘记加。)web

  第二步,检查错误日志,查看“OutOfMemory”错误前是否有其它异常或错误。算法

  第三步,对代码进行走查和分析,找出可能发生内存溢出的位置。数据库

重点排查如下几点:
  1.检查对数据库查询中,是否有一次得到所有数据的查询。通常来讲,若是一次取十万条记录到内存,就可能引发内存溢出。这个问题比较隐蔽,在上线前,数据库中数据较少,不容易出问题,上线后,数据库中数据多了,一次查询就有可能引发内存溢出。所以对于数据库查询尽可能采用分页的方式查询。
  2.检查代码中是否有死循环或递归调用。 编程

  3.检查是否有大循环重复产生新对象实体。 windows

  4.检查对数据库查询中,是否有一次得到所有数据的查询。通常来讲,若是一次取十万条记录到内存,就可能引发内存溢出。这个问题比较隐蔽,在上线前,数据库中   数据较少,不容易出问题,上线后,数据库中数据多了,一次查询就有可能引发内存溢出。所以对于数据库查询尽可能采用分页的方式查询。 数组

  5.检查List、MAP等集合对象是否有使用完后,未清除的问题。List、MAP等集合对象会始终存有对对象的引用,使得这些对象不能被GC回收。tomcat

  第四步,使用内存查看工具动态查看内存使用状况服务器

从内存溢出看Java 环境中的内存结构

  做为有个java程序员,我想你们对下面出现的这几个场景并不陌生,倍感亲切,深恶痛绝,抓心挠肝,必定会回过头来问为何为何为何会这样,嘿嘿,让咱们看一下咱们平常在开发过程当中接触内存溢出的异常:  

复制代码
Exception in thread "main" [Full GCjava.lang.OutOfMemoryError: Java heap space 
    at java.util.Arrays.copyOf(Unknown Source)
    at java.util.Arrays.copyOf(Unknown Source)
    at java.util.ArrayList.grow(Unknown Source)
    at java.util.ArrayList.ensureExplicitCapacity(Unknown Source)
    at java.util.ArrayList.ensureCapacityInternal(Unknown Source)
    at java.util.ArrayList.add(Unknown Source)
    at oom.HeapOOM.main(HeapOOM.java:21)  
复制代码
复制代码
Exception in thread "main" java.lang.StackOverflowError
    at java.nio.CharBuffer.arrayOffset(Unknown Source)
    at sun.nio.cs.UTF_8.updatePositions(Unknown Source)
    at sun.nio.cs.UTF_8$Encoder.encodeArrayLoop(Unknown Source)
    at sun.nio.cs.UTF_8$Encoder.encodeLoop(Unknown Source)
    at java.nio.charset.CharsetEncoder.encode(Unknown Source)
    at sun.nio.cs.StreamEncoder.implWrite(Unknown Source)
    at sun.nio.cs.StreamEncoder.write(Unknown Source)
    at java.io.OutputStreamWriter.write(Unknown Source)
    at java.io.BufferedWriter.flushBuffer(Unknown Source)
    at java.io.PrintStream.write(Unknown Source)
    at java.io.PrintStream.print(Unknown Source)
    at java.io.PrintStream.println(Unknown Source)
复制代码
java.lang.OutOfMemoryError: PermGen space 
Exception in thread "main" java.lang.OutOfMemoryError
    at sun.misc.Unsafe.allocateMemory(Native Method)
    at oom.DirectMemoryOOM.main(DirectMemoryOOM.java:23)

  是否是有你们很熟悉的,碰见这样的问题解决起来可能不简单,可是若是如今让你们写个程序,故意让程序出现下面的异常,估计能很快写出来的也不是不少,这就要求开发人员对于java内存区域以及jvm规范有比较深的了解。

  既然抛出了异常,首先咱们确定这些都是内存异常,只是内存异常中的不一样种类,咱们就试着了解一下为何会出现以上的异常,能够看出有两种异常情况::

  OutOfMemoryError

  StackOverflowError

  其中OutOfMemoryError是在程序没法申请到足够的内存的时候抛出的异常,StackOverflowError是线程申请的栈深度大于虚拟机所容许的深度所抛出的异常。 但是从上面列出的异常内容也能够看出在OutOfMemoryError类型的一场中也存在这不少异常的可能。这是为何?觉得是在内存的不一样结构中出现的错误,因此抛出的异常也就形形色色,说道这咱们不得不介绍一下java的内存结构,请看下图(从网上摘的):

  

  在运行时的内存区域有5个部分,Method Area(方法区),Java stack(java 虚拟机栈),Native MethodStack(本地方法栈),Heap(堆),Program Counter Regster(程序计数器)。从图中看出方法区和堆用黄色标记,和其余三个区域的不一样点就是,方法区和堆是线程共享的,全部的运行在jvm上的程序都能访问这两个区域,堆,方法区和虚拟机的生命周期同样,随着虚拟机的启动而存在,而栈和程序计数器是依赖用户线程的启动和结束而创建和销毁。

  Program Counter Regster(程序计数器):每个用户线程对应一个程序计数器,用来指示当前线程所执行字节码的行号。由程序计数器给文字码解释器提供吓一条要执行的字节码的的位置。根据jvm规范,在这个区域中不会抛出OutOfMemoryError的内存异常。

  Java stack(java 虚拟机栈):这个区域是最容易出现内存异常的区域,每个线程对应生成一个线程栈,线程每执行一个方法的时候,都会建立一个栈帧,用来存放方法的局部变量表,操做树栈,动态链接,方法入口,这和C#是不同的,在C#CLR中没有栈帧的概念,都是在线程栈中经过压栈和出栈的方式进行数据的保存。jvm规范对这个区域定义了两种内存异常,OutOfMemoryError,StackOverflowError。

  Native MethodStack(本地方法栈):和虚拟机栈同样,不一样的是处理的对象不同,虚拟机栈处理java的字节码,而本地栈则是处理的Native方法。其余方面一致。

  Heap(堆):前面说了堆是全部线程都能访问的,随着虚拟机的启动而存在,这块区域很大,由于全部的线程都在这个区域保存实例化的对象,由于每个类型中,每一个接口实现类须要的内存不同,一个方法内的多个分支须要的内存也不尽相同,咱们只有在运行的时候才能知道要建立多少对象,须要分配多大的地址空间。GC关注的正是这样的部份内容,因此不少时候也将堆称为GC堆。堆中确定不会抛出StackOverflowError类型的异常,因此只有OutOfMemoryError相关类型的异常。

  Method Area(方法区):用于存放已被虚拟机加载的类信息,常量,静态方法,即便编译后的代码。一样只能抛出OutOfMemoryError相关类型的异常。

  介绍完jvm内存结构中的常见区域,下面该是和咱们主题呼应的时候了,在什么状况下,在那个区域,如何才能复现开始提到的异常信息?从第一个开始,异常信息的内容为:  

Exception in thread "main" [Full GCjava.lang.OutOfMemoryError: Java heap space 
    at java.util.Arrays.copyOf(Unknown Source)
    at java.util.Arrays.copyOf(Unknown Source)
    at java.util.ArrayList.grow(Unknown Source)
    at java.util.ArrayList.ensureExplicitCapacity(Unknown Source)
    at java.util.ArrayList.ensureCapacityInternal(Unknown Source)
    at java.util.ArrayList.add(Unknown Source)
    at oom.HeapOOM.main(HeapOOM.java:21)

  可想而知是在堆中出现的问题,如何重现,因为是在堆中出现这个异常,那么就要处理好,不能被垃圾回收器给回收了,设置一下jvm中堆的最大值(这样才可以更快的出现错误),设置jvm值的方法是经过-Xms(堆的最小值),-Xmx(堆的最大值)。下面动手试一下:

package oom;

import java.util.ArrayList;
import java.util.List;

import testbean.UserBean;

/*** 
 * 
 * @author Think
 * 
 */
public class HeapOOM {
    static class OOMObject {
    }

    public static void main(String[] args) {
        List<UserBean> users = new ArrayList<UserBean>();
        while (true) {
            users.add(new UserBean());
        }
    }
}

UserBean对象定义以下:

package testbean;

public class UserBean {
    String name;
    int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public UserBean() {
        super();
    }

}

而后在运行的时候设置jvm参数,以下:

  

 运行一下看看结果:  

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at java.util.Arrays.copyOf(Unknown Source)
    at java.util.Arrays.copyOf(Unknown Source)
    at java.util.ArrayList.grow(Unknown Source)
    at java.util.ArrayList.ensureExplicitCapacity(Unknown Source)
    at java.util.ArrayList.ensureCapacityInternal(Unknown Source)
    at java.util.ArrayList.add(Unknown Source)
    at oom.HeapOOM.main(HeapOOM.java:21)

成功在java虚拟机堆中溢出。下面看第二个关于栈的异常,内容以下:  

 
Exception in thread "main" java.lang.StackOverflowError
    at java.nio.CharBuffer.arrayOffset(Unknown Source)
    at sun.nio.cs.UTF_8.updatePositions(Unknown Source)
    at sun.nio.cs.UTF_8$Encoder.encodeArrayLoop(Unknown Source)
    at sun.nio.cs.UTF_8$Encoder.encodeLoop(Unknown Source)
    at java.nio.charset.CharsetEncoder.encode(Unknown Source)
    at sun.nio.cs.StreamEncoder.implWrite(Unknown Source)
    at sun.nio.cs.StreamEncoder.write(Unknown Source)
    at java.io.OutputStreamWriter.write(Unknown Source)
    at java.io.BufferedWriter.flushBuffer(Unknown Source)
    at java.io.PrintStream.write(Unknown Source)
    at java.io.PrintStream.print(Unknown Source)
    at java.io.PrintStream.println(Unknown Source)
复制代码

 由于是与栈相关的话,那么咱们在重现异常的时候就要相应的将栈内存容量设置的小一些,设置栈大小的方法是设置-Xss参数,看以下实现:  

package oom;

import testbean.Recursion;

/*** 
 * 
 * 
 * 
 */
public class VMStackOOM { 

    public static void main(String[] args) {
        Recursion recursion = new Recursion();
        try {
            recursion.recursionself();
        } catch (Throwable e) {
            System.out.println("current value :" + recursion.currentValue);
            throw e;
        }
    }

}

 Recursion的定义以下:  

复制代码
package testbean;

public class Recursion {
    public int currentValue = 0;

    public void recursionself() {
        currentValue += 1;
        recursionself();
    }
}
复制代码

  运行时jvm参数的设置以下:

  

  运行结果以下:  

 
current value :999
Exception in thread "main" java.lang.StackOverflowError
    at testbean.Recursion.recursionself(Recursion.java:7)
    at testbean.Recursion.recursionself(Recursion.java:8)
    at testbean.Recursion.recursionself(Recursion.java:8)
    at testbean.Recursion.recursionself(Recursion.java:8)
    at testbean.Recursion.recursionself(Recursion.java:8)
    at testbean.Recursion.recursionself(Recursion.java:8)
 

  第三个异常是关于perm的异常内容,咱们须要的是设置方法区的大小,实现方式是经过设置-XX:PermSize和-XX:MaxPermSize参数,内容以下:  

java.lang.OutOfMemoryError: PermGen space

  若是程序加载的类过多,例如tomcatweb容器,就会出现PermGen space异常,若是我将HeapOOM类的运行时的XX:PermSize设置为2M,以下:

  

  那么程序就不会执行成功,执行的时候出现以下异常:  

复制代码
Error occurred during initialization of VM
java.lang.OutOfMemoryError: PermGen space
    at sun.misc.Launcher$ExtClassLoader.getExtClassLoader(Unknown Source)
    at sun.misc.Launcher.<init>(Unknown Source)
    at sun.misc.Launcher.<clinit>(Unknown Source)
    at java.lang.ClassLoader.initSystemClassLoader(Unknown Source)
    at java.lang.ClassLoader.getSystemClassLoader(Unknown Source)
复制代码

  第四个异常估计遇到的人就很少了,是DirectMemory内存相关的,内容以下:  

Exception in thread "main" java.lang.OutOfMemoryError
    at sun.misc.Unsafe.allocateMemory(Native Method)
    at oom.DirectMemoryOOM.main(DirectMemoryOOM.java:23)

  DirectMemoruSize能够经过设置 -XX:MaxDirectMemorySize参数指定容量大小,若是不指定的话,那么就跟堆的最大值一致,下面是代码实现:  

复制代码
package oom;

import java.lang.reflect.Field;

import sun.misc.Unsafe;

/*** 
 * 
 * @author Think
 * 
 */
public class DirectMemoryOOM {

    private static final int _1MB = 1024 * 1024;

    public static void main(String[] args) throws IllegalArgumentException,
            IllegalAccessException {
        Field unsafeField = Unsafe.class.getDeclaredFields()[0];
        unsafeField.setAccessible(true);
        Unsafe unsafe = (Unsafe) unsafeField.get(null);
        while (true) {
            unsafe.allocateMemory(_1MB);
        }

    }
}
复制代码

  运行时设置的jvm参数以下:

  

  很容易就复线了异常信息:  

Exception in thread "main" java.lang.OutOfMemoryError
    at sun.misc.Unsafe.allocateMemory(Native Method)
    at oom.DirectMemoryOOM.main(DirectMemoryOOM.java:23)

 

关于JAVA中内存溢出的解决办法

J2ee应用系统是运行在J2EE应用服务器上的,而j2ee应用服务器又是运行在JVM上的,

生成环境中JVM参数的优化和设置对于J2EE应用系统性能有着决定性的做用。要优化系统,则须要对JVM参数进行合理的设置,因此咱们须要了解究竟在什么地方进行设置、有哪些参数以及各参数的意义分别是什么,而且咱们还得了解JVM的内存管理机制到底是个什么玩意儿?其实咱们在网上搜索引擎上,一搜就有能够获取到一大把相关信息,关键是咱们如何深刻的理解它们。那么下面咱们就简单的介绍一下究竟什么是JVM的内存管理机制吧~!   

JVM的早期版本并无进行分区管理;这样的后果是JVM进行垃圾回收时,不得不扫描JVM所管理的整片内存,因此搜集垃圾是很耗费资源的事情,也是早起JAVA程序的性能低下的主要缘由。随着JVM的发展,JVM引进了分区管理的机制。 

JVM所管理的全部内存资源分为2个大的部分。永久存储区(Permanent Space) 和堆空间(The Heap Space)。其中对空间又分为新生区和养老区,新生区又分为伊甸园,幸存者0区、幸存1区。以下图:

 

 

 

关于个分区的用途,你们能够参考其余相关文档。本教程所要处理的问题是如何解决内存溢出的问题。接下来以tomcat服务器为例:

咱们首先得找到内存管理所要设置的参数在哪一个文件:<CATALINA_HOME>/bin/catalina.bat。

须要添加一行代码:

JAVA_OPTS="-Xms512m-Xmx512m -Xss1024K -XX:PermSize=256m -XX:MaxPermSize=256m"

下面分别对各参数进行介绍和解释:

JVM 相关参数:

参数名参数说明

-server 启用可以执行优化的编译器, 显著提升服务器的性能,但使用可以执行优化的编译器时,服务器的预备时间将会较长。生产环境的服务器强烈推荐设置此参数。

-Xss 单个线程堆栈大小值;JDK5.0 之后每一个线程堆栈大小为1M,之前每一个线程堆栈大小为256K。在相同物理内存下,减少这个值能生成更多的线程。可是操做系统对一个进程内的线程数仍是有限制的,不能无限生成,经验值在3000~5000左右。

-XX:+UseParNewGC 可用来设置年轻代为并发收集【多CPU】,若是你的服务器有多个CPU,你能够开启此参数;开启此参数,多个CPU 可并发进行垃圾回收,可提升垃圾回收的速度。此参数和+UseParallelGC,-XX:ParallelGCThreads搭配使用。

+UseParallelGC 选择垃圾收集器为并行收集器。此配置仅对年轻代有效。即上述配置下,年轻代使用并发收集,而年老代仍旧使用串行收集。可提升系统的吞吐量。

-XX:ParallelGCThreads 年轻代并行垃圾收集的前提下(对并发也有效果)的线程数,增长并行度,即:同时多少个线程一块儿进行垃圾回收。此值最好配置与处理器数目相等。永久存储区相关参数:参数名参数说明

-Xnoclassgc 每次永久存储区满了后通常GC 算法在作扩展分配内存前都会触发一次FULL GC,除非设置了-Xnoclassgc.

-XX:PermSize 应用服务器启动时,永久存储区的初始内存大

-XX:MaxPermSize 应用运行中,永久存储区的极限值。为了避免消耗扩大JVM 永久存储区分配的开销,将此参数和-XX:PermSize这个两个值设为相等。堆空间相关参数参数名参数说明

-Xms 启动应用时,JVM 堆空间的初始大小值。

-Xmx 应用运行中,JVM 堆空间的极限值。为了避免消耗扩大JVM 堆控件分配的开销,将此参数和-Xms 这个两个值设为相等,考虑到须要开线程,讲此值设置为总内存的80%.

-Xmn 此参数硬性规定堆空间的新生代空间大小,推荐设为堆空间大小的1/4。

上面所列的JVM 参数关系到系统的性能,而其中-XX:PermSize,

-XX:MaxPermSize,-Xms,-Xmx 和-Xmn 这5 个参数更是直接关系到系统的性能,系统是否会出现内存溢出。

-XX:PermSize 和-XX:MaxPermSize 分别设置应用服务器启动时,永久存储区的初始大小和极限大小;在生成环境中强烈推荐将这个两个值设置为相同的值,以免分配永久存储区的开销,具体的值可取系统“疲劳测试”获取到的永久存储区的极限值;若是不进行设置-XX:MaxPermSize 默认值为64M,通常来讲系统的类定义文件大小都会超过这个默认值。

-Xms 和-Xmx 分别是服务器启动时,堆空间的初始大小和极限值。-Xms的默认值是物理内存的1/64 但小于1G,-Xmx 的默认值是物理内存的1/4 但小于1G.在生产环境中这些默认值是确定不能知足咱们的须要的。也就是你的服务器有8g 的内存,不对JVM 参数进行设置优化,应用服务器启动时仍是按默认值来分配和约束JVM 对内存资源的使用,不会充分的利用全部的内存资源。

 

结论:“永久存储区溢出(java.lang.OutOfMemoryError:Java Permanent Space)”乃是永久存储区设置过小,不能知足系统须要的大小,此时只须要调整-XX:PermSize 和-XX:MaxPermSize 这两个参数便可。“JVM 堆空间溢出(java.lang.OutOfMemoryError: Java heap space)”错误是JVM 堆空间不足,此时只须要调整-Xms 和-Xmx 这两个参数便可。

 

到此咱们知道了,当系统出现内存溢出时,是哪些参数设置不合理须要调整。但咱们怎么知道服务器启动时,到底JVM 内存相关参数的值是多少呢?

这个问题其实Sun公司早已经意料到了,因此给咱们开发了内存使用监控工具jvmstat.

你们能够到ORACLE官网进行下载。用它能够很方便的看到咱们的服务器内存使用状况。

将下载的jvmstat包解压到如“C:\ProgramFiles\Java\jvmstat”(这是我本地java路径,你们能够根据本身所安装的java环境的路径进行解压)。启动完以后咱们就可使用visualgc命令了,cmd进入命令符窗口,输入tasklist(windows下查看进程任务PID)查找到你要检测进程PID.而后直接输入visuglgc PID 就会弹出三个可见视图。

以下图:

 

 

  

内存溢出与数据库锁表的问题,能够说是开发人员的噩梦,通常的程序异常,老是能够知道在何时或是在什么操做步骤上出现了异常,并且根据堆栈信息也很容易定位到程序中是某处出现了问题。内存溢出与锁表则否则,通常现象是操做通常时间后系统愈来愈慢,直到死机,但并不能明确是在什么操做上出现的,发生的时间点也没有规律,查看日志或查看数据库也不能定位出问题的代码。

更严重的是内存溢出与数据库锁表在系统开发和单元测试阶段并不容易被发现,当系统正式上线通常时间后,操做的并发量上来了,数据也积累了一些,系统就容易出现内存溢出或是锁表的现象,而此时系统又不能随意停机或重启,为修正BUG带来很大的困难。

本文以笔者开发和支持的多个项目为例,与你们分享在开发过程当中遇到的Java内存溢出和数据库锁表的检测和处理解决过程。

2.内存溢出的分析
内存溢出是指应用系统中存在没法回收的内存或使用的内存过多,最终使得程序运行要用到的内存大于虚拟机能提供的最大内存。为了解决Java中内存溢出问题,咱们首先必须了解Java是如何管理内存的。Java的内存管理就是对象的分配和释放问题。在Java中,内存的分配是由程序完成的,而内存的释放是由垃圾收集器(Garbage Collection,GC)完成的,程序员不须要经过调用GC函数来释放内存,由于不一样的JVM实现者可能使用不一样的算法管理GC,有的是内存使用到达必定程度时,GC才开始工做,也有定时执行的,有的是中断式执行GC。但GC只能回收无用而且再也不被其它对象引用的那些对象所占用的空间。Java的内存垃圾回收机制是从程序的主要运行对象开始检查引用链,当遍历一遍后发现没有被引用的孤立对象就做为垃圾回收。

引发内存溢出的缘由有不少种,常见的有如下几种:

l         内存中加载的数据量过于庞大,如一次从数据库取出过多数据;

l         集合类中有对对象的引用,使用完后未清空,使得JVM不能回收;

l         代码中存在死循环或循环产生过多重复的对象实体;

l         使用的第三方软件中的BUG;

l         启动参数内存值设定的太小;

3.内存溢出的解决
内存溢出虽然很棘手,但也有相应的解决办法,能够按照从易到难,一步步的解决。

第一步,就是修改JVM启动参数,直接增长内存。这一点看上去彷佛很简单,但很容易被忽略。JVM默承认以使用的内存为64M,Tomcat默承认以使用的内存为128MB,对于稍复杂一点的系统就会不够用。在某项目中,就由于启动参数使用的默认值,常常报“OutOfMemory”错误。所以,-Xms,-Xmx参数必定不要忘记加。

第二步,检查错误日志,查看“OutOfMemory”错误前是否有其它异常或错误。在一个项目中,使用两个数据库链接,其中专用于发送短信的数据库链接使用DBCP链接池管理,用户为不将短信发出,有意将数据库链接用户名改错,使得日志中有许多数据库链接异常的日志,一段时间后,就出现“OutOfMemory”错误。经分析,这是因为DBCP链接池BUG引发的,数据库链接不上后,没有将链接释放,最终使得DBCP报“OutOfMemory”错误。通过修改正确数据库链接参数后,就没有再出现内存溢出的错误。

查看日志对于分析内存溢出是很是重要的,经过仔细查看日志,分析内存溢出前作过哪些操做,能够大体定位有问题的模块。

第三步,安排有经验的编程人员对代码进行走查和分析,找出可能发生内存溢出的位置。重点排查如下几点:

        检查代码中是否有死循环或递归调用。

l         检查是否有大循环重复产生新对象实体。

l         检查对数据库查询中,是否有一次得到所有数据的查询。通常来讲,若是一次取十万条记录到内存,就可能引发内存溢出。这个问题比较隐蔽,在上线前,数据库中数据较少,不容易出问题,上线后,数据库中数据多了,一次查询就有可能引发内存溢出。所以对于数据库查询尽可能采用分页的方式查询。

l         检查List、MAP等集合对象是否有使用完后,未清除的问题。List、MAP等集合对象会始终存有对对象的引用,使得这些对象不能被GC回收。

第四步,使用内存查看工具动态查看内存使用状况。某个项目上线后,每次系统启动两天后,就会出现内存溢出的错误。这种状况通常是代码中出现了缓慢的内存泄漏,用上面三个步骤解决不了,这就须要使用内存查看工具了。

内存查看工具备许多,比较有名的有:Optimizeit Profiler、JProbe Profiler、JinSight和Java1.5的Jconsole等。它们的基本工做原理大同小异,都是监测Java程序运行时全部对象的申请、释放等动做,将内存管理的全部信息进行统计、分析、可视化。开发人员能够根据这些信息判断程序是否有内存泄漏问题。通常来讲,一个正常的系统在其启动完成后其内存的占用量是基本稳定的,而不该该是无限制的增加的。持续地观察系统运行时使用的内存的大小,能够看到在内存使用监控窗口中是基本规则的锯齿形的图线,若是内存的大小持续地增加,则说明系统存在内存泄漏问题。经过间隔一段时间取一次内存快照,而后对内存快照中对象的使用与引用等信息进行比对与分析,能够找出是哪一个类的对象在泄漏。

经过以上四个步骤的分析与处理,基本能处理内存溢出的问题。固然,在这些过程当中也须要至关的经验与敏感度,须要在实际的开发与调试过程当中不断积累。

整体上来讲,产生内存溢出是因为代码写的很差形成的,所以提升代码的质量是最根本的解决办法。有的人认为先把功能实现,有BUG时再在测试阶段进行修正,这种想法是错误的。正如一件产品的质量是在生产制造的过程当中决定的,而不是质量检测时决定的,软件的质量在设计与编码阶段就已经决定了,测试只是对软件质量的一个验证,由于测试不可能找出软件中全部的BUG。

 

--------------------------------------------------------------------------------------------------------------------------------

 

缘由有不少种,好比:

1.数据量过于庞大;死循环 ;静态变量和静态方法过多;递归;没法肯定是否被引用的对象;

2.虚拟机不回收内存(内存泄漏);

    说白了就是程序运行要用到的内存大于虚拟机能提供的最大内存就发生内存溢出了。 内存溢出的问题要看业务和系统大小而定,对于某些系统可能内存溢出不常见,但某些系统仍是很常见的解决的方法,

一个是优化程序代码,若是业务庞大,逻辑复杂,尽可能减小全局变量的引用,让程序使用完变量的时候释放该引用可以让垃圾回收器回收,释放资源。
二就是物理解决,增大物理内存,而后经过:-Xms256m -Xmx256m -XX:MaxNewSize=256m -XX:MaxPermSize=256m的修改

1、内存溢出类型 
1 、 java.lang.OutOfMemoryError: PermGen space

JVM 管理两种类型的内存,堆和非堆。堆是给开发人员用的上面说的就是,是在 JVM 启动时建立;非堆是留给 JVM 本身用的,用来存放类的信息的。它和堆不一样,运行期内 GC 不会释放空间。若是 web app 用了大量的第三方 jar 或者应用有太多的 class 文件而刚好 MaxPermSize 设置较小,超出了也会致使这块内存的占用过多形成溢出,或者 tomcat 热部署时侯不会清理前面加载的环境,只会将 context 更改成新部署的,非堆存的内容就会愈来愈多。

2 、 java.lang.OutOfMemoryError: Java heap space

第一种状况是个补充,主要存在问题就是出如今这个状况中。其默认空间 ( 即 -Xms) 是物理内存的 1/64 ,最大空间 (-Xmx) 是物理内存的 1/4 。若是内存剩余不到 40 %, JVM 就会增大堆到 Xmx 设置的值,内存剩余超过 70 %, JVM 就会减少堆到 Xms 设置的值。因此服务器的 Xmx 和 Xms 设置通常应该设置相同避免每次 GC 后都要调整虚拟机堆的大小。假设物理内存无限大,那么 JVM 内存的最大值跟操做系统有关,通常 32 位机是 1.5g 到 3g 之间,而 64 位的就不会有限制了。

注意:若是 Xms 超过了 Xmx 值,或者堆最大值和非堆最大值的总和超过了物理内存或者操做系统的最大限制都会引发服务器启动不起来。

垃圾回收 GC 的角色

JVM 调用 GC 的频度仍是很高的,主要两种状况下进行垃圾回收:

当应用程序线程空闲;另外一个是 java 内存堆不足时,会不断调用 GC ,若连续回收都解决不了内存堆不足的问题时,就会报 out of memory 错误。由于这个异常根据系统运行环境决定,因此没法预期它什么时候出现。

根据 GC 的机制,程序的运行会引发系统运行环境的变化,增长 GC 的触发机会。

为了不这些问题,程序的设计和编写就应避免垃圾对象的内存占用和 GC 的开销。显示调用 System.GC() 只能建议 JVM 须要在内存中对垃圾对象进行回收,但不是必须立刻回收,

一个是并不能解决内存资源耗空的局面,另外也会增长 GC 的消耗。

2、 JVM 内存区域组成 
简单的说 java中的堆和栈

java把内存分两种:一种是栈内存,另外一种是堆内存

1。在函数中定义的基本类型变量和对象的引用变量都在函数的栈内存中分配;

2。堆内存用来存放由 new建立的对象和数组

在函数(代码块)中定义一个变量时, java就在栈中为这个变量分配内存空间,当超过变量的做用域后, java会自动释放掉为该变量所分配的内存空间;在堆中分配的内存由 java虚拟机的自动垃圾回收器来管理

堆的优点是能够动态分配内存大小,生存期也没必要事先告诉编译器,由于它是在运行时动态分配内存的。缺点就是要在运行时动态分配内存,存取速度较慢;

栈的优点是存取速度比堆要快,缺点是存在栈中的数据大小与生存期必须是肯定的无灵活 性。

java 堆分为三个区: New 、 Old 和 Permanent

GC 有两个线程:

新建立的对象被分配到 New 区,当该区被填满时会被 GC 辅助线程移到 Old 区,当 Old 区也填满了会触发 GC 主线程遍历堆内存里的全部对象。 Old 区的大小等于 Xmx 减去 -Xmn

java栈存放

栈调整:参数有 +UseDefaultStackSize -Xss256K,表示每一个线程可申请 256k的栈空间

每一个线程都有他本身的 Stack

3、 JVM如何设置虚拟内存 
提示:在 JVM中若是 98%的时间是用于 GC且可用的 Heap size 不足 2%的时候将抛出此异常信息。

提示: Heap Size 最大不要超过可用物理内存的 80%,通常的要将 -Xms和 -Xmx选项设置为相同,而 -Xmn为 1/4的 -Xmx值。

提示: JVM初始分配的内存由 -Xms指定,默认是物理内存的 1/64; JVM最大分配的内存由 -Xmx指定,默认是物理内存的 1/4。

默认空余堆内存小于 40%时, JVM就会增大堆直到 -Xmx的最大限制;空余堆内存大于 70%时, JVM会减小堆直到 -Xms的最小限制。所以服务器通常设置 -Xms、 -Xmx相等以免在每次 GC 后调整堆的大小。

提示:假设物理内存无限大的话, JVM内存的最大值跟操做系统有很大的关系。

简单的说就 32位处理器虽然可控内存空间有 4GB,可是具体的操做系统会给一个限制,

这个限制通常是 2GB-3GB(通常来讲 Windows系统下为 1.5G-2G, Linux系统下为 2G-3G), 而 64bit以上的处理器就不会有限制了

提示:注意:若是 Xms超过了 Xmx值,或者堆最大值和非堆最大值的总和超过了物理内 存或者操做系统的最大限制都会引发服务器启动不起来。

提示:设置 NewSize、 MaxNewSize相等, “new”的大小最好不要大于 “old”的一半,缘由是 old区若是不够大会频繁的触发 “主 ” GC ,大大下降了性能

JVM使用 -XX:PermSize设置非堆内存初始值,默认是物理内存的 1/64;

由 XX:MaxPermSize设置最大非堆内存的大小,默认是物理内存的 1/4。

解决方法:手动设置 Heap size

修改 TOMCAT_HOME/bin/catalina.bat

在“ echo “Using CATALINA_BASE: $CATALINA_BASE””上面加入如下行:

  1. JAVA_OPTS=”-server -Xms800m -Xmx800m -XX:MaxNewSize=256m”   

4、性能检查工具使用 
定位内存泄漏:

JProfiler 工具主要用于检查和跟踪系统(限于 Java 开发的)的性能。 JProfiler 能够经过时时的监控系统的内存使用状况,随时监视垃圾回收,线程运行情况等手段,从而很好的监视 JVM 运行状况及其性能。


1. 应用服务器内存长期不合理占用,内存常常处于高位占用,很难回收到低位;

2. 应用服务器极为不稳定,几乎每两天从新启动一次,有时甚至天天从新启动一次;

3. 应用服务器常常作 Full GC(Garbage Collection),并且时间很长,大约须要 30-40秒,应用服务器在作 Full GC的时候是不响应客户的交易请求的,很是影响系统性能。

由于开发环境和产品环境会有不一样,致使该问题发生有时会在产品环境中发生, 一般可使用工具跟踪系统的内存使用状况,在有些个别状况下或许某个时刻确实 是使用了大量内存致使 out of memory,这时应继续跟踪看接下来是否会有降低,

若是一直居高不下这确定就由于程序的缘由致使内存泄漏。

5、不健壮代码的特征及解决办法 
1 、尽早释放无用对象的引用。好的办法是使用临时变量的时候,让引用变量在退出活动域后,自动设置为 null ,暗示垃圾收集器来收集该对象,防止发生内存泄露。

对于仍然有指针指向的实例, jvm 就不会回收该资源 , 由于垃圾回收会将值为 null 的对象做为垃圾,提升 GC 回收机制效率;

2 、咱们的程序里不可避免大量使用字符串处理,避免使用 String ,应大量使用 StringBuffer ,每个 String 对象都得独立占用内存一块区域;

  1. String str = “aaa”;   
  2.   
  3. String str2 = “bbb”;   
  4.   
  5. String str3 = str + str2;// 假如执行这次以后 str ,str2 之后再不被调用 , 那它就会被放在内存中等待 Java 的 gc 去回收 , 程序内过多的出现这样的状况就会报上面的那个错误 , 建议在使用字符串时能使用 StringBuffer 就不要用 String, 这样能够省很多开销;   

3 、尽可能少用静态变量,由于静态变量是全局的, GC 不会回收的;

4 、避免集中建立对象尤为是大对象, JVM 会忽然须要大量内存,这时必然会触发 GC 优化系统内存环境;显示的声明数组空间,并且申请数量还极大。

这是一个案例想定供你们警惕:

使用jspsmartUpload做文件上传,如今运行过程当中常常出现java.outofMemoryError的错误,用top命令看看进程使用状况,发现内存不足2M,花了很长时间,发现是jspsmartupload的问题。把jspsmartupload组件的源码文件(class文件)反编译成Java文件,如梦方醒:

  1. m_totalBytes = m_request.getContentLength();        
  2. m_binArray = new byte[m_totalBytes];      

变量m_totalBytes表示用户上传的文件的总长度,这是一个很大的数。若是用这样大的数去声明一个byte数组,并给数组的每一个元素分配内存空间,并且m_binArray数组不能立刻被释放,JVM的垃圾回收确实有问题,致使的结果就是内存溢出。

jspsmartUpload为什末要这样做,有他的缘由,根据RFC1867的http上传标准,获得一个文件流,并不知道文件流的长度。设计者若是想文件的长度,只有操做servletinputstream一次才知道,由于任何流都不知道大小。只有知道文件长度了,才能够限制用户上传文件的长度。为了省去这个麻烦,jspsmartUpload设计者直接在内存中打开文件,判断长度是否符合标准,符合就写到服务器的硬盘。这样产生内存溢出,这只是个人一个猜想而已。

因此编程的时候,不要在内存中申请大的空间,由于web服务器的内存有限,而且尽量的使用流操做,例如

  1. byte[] mFileBody = new byte[512];   
  2.          Blob vField= rs.getBlob("FileBody");   
  3.       InputStream instream=vField.getBinaryStream();   
  4.       FileOutputStream fos=new FileOutputStream(saveFilePath+CFILENAME);   
  5.          int b;   
  6.                       while( (b =instream.read(mFileBody)) != -1){   
  7.                         fos.write(mFileBody,0,b);   
  8.                          }   
  9.         fos.close();   
  10.       instream.close();  

5 、尽可能运用对象池技术以提升系统性能;生命周期长的对象拥有生命周期短的对象时容易引起内存泄漏,例如大集合对象拥有大数据量的业务对象的时候,能够考虑分块进行处理,而后解决一块释放一块的策略。

6 、不要在常常调用的方法中建立对象,尤为是忌讳在循环中建立对象。能够适当的使用 hashtable , vector 建立一组对象容器,而后从容器中去取那些对象,而不用每次 new 以后又丢弃

7 、通常都是发生在开启大型文件或跟数据库一次拿了太多的数据,形成 Out Of Memory Error 的情况,这时就大概要计算一下数据量的最大值是多少,而且设定所需最小及最大的内存空间值。

相关文章
相关标签/搜索