内存溢出及解决方案

简介: 首发公众号:码农架构

什么是内存溢出

JVM运行过程中,程序不断的申请内存空间用于保存运行时数据,当程序申请的内存空间系统无法满足时,就会抛出内存溢出错误。内存溢出发生的区域以及相应的解决方案都不相同,下面我们逐一分析内存溢出类型及解决方案。

OutOfMemoryError与StackOverflowError

JVM内存溢出分为两种情况,OutOfMemoryError和StackOverflowError。
  • OutOfMemoryError是在程序无法申请到足够的内存的时候抛出的异常。
  • StackOverflowError是线程申请的栈深度大于虚拟机所允许的深度所抛出的异常。

ERROR和Exception是有区别

Exception 和 Error 体现了 Java 平台设计者对不同异常情况的分类。
  • Exception 是程序正常运行中,可以预料的意外情况,可能并且应该被捕获,进行相应处理。
  • Error 是指在正常情况下,不大可能出现的情况,绝大部分的 Error 都会导致程序(比如 JVM 自身)处于非正常的、不可恢复状态。

OutOfMemoryError

OutOfMemoryError是在程序无法申请到足够的内存的时候抛出的异常,导致OutOfMemoryError异常的常见原因有以下几种:

  • 内存中加载的数据量过于庞大,如一次从数据库取出过多数据;
  • 集合类中有对对象的引用,使用完后未清空,使得JVM不能回收;
  • 代码中存在死循环或循环产生过多重复的对象实体;
  • 使用的第三方软件中的BUG;
  • 启动参数内存值设定的过小;

在不同的Web服务器或程序中,此错误常见的错误提示如下:

  • tomcat: java.lang.OutOfMemoryError: PermGen space
  • tomcat: java.lang.OutOfMemoryError: Java heap space
  • weblogic: Root cause of ServletException java.lang.OutOfMemoryError
  • resin: java.lang.OutOfMemoryError
  • java: java.lang.OutOfMemoryError

OOM错误发生的场景很多,比如下面这段代码,最终会发生OutOfMemoryError,为了能更快的出现错误,我们可以设置一下jvm中堆的最大值,设置jvm值的方法是通过-Xms(堆的最小值),-Xmx(堆的最大值)

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

StackOverflowError

StackOverflowError代表的是,当程序中栈深度所需空间大小,超过了虚拟机分配给线程的栈大小时就会出现此error。StackOverflowError发生于单个线程的栈大小无法满足程序所需的栈空间大小时。

java栈是java虚拟机的一个重要的组成部分,在栈里进行线程操作,存放方法参数等等。每个线程在创建时都会创建一个虚拟机栈,其内部保存一个个的栈帧(Stack Frame),对应着一次次的 Java 方法调用。栈在初始化过后是有一定的大小的,也可通过jvm参数-Xss设置每个线程的堆栈大小。栈帧中存储着局部变量表、操作数(operand)栈、动态链接、方法正常退出或者异常退出的定义等。

栈深度可理解为单个线程的堆栈空间最多能产生多少个栈帧,当堆栈总大小不变时,栈帧存储的信息越多,栈帧越大,每个线程堆栈深度越小。

image.png

以下代码将会报StackOverflowError:

public static void test(String str){
    System.out.println(str);
     test(str);
 }

内存溢出发生的区域

通常可以把 JVM 内存区域分为下面几个方面,其中,有的区域是以线程为单位,而有的区域则是整个 JVM 进程为单位的。

  • Method Area(方法区)
  • Java stack(java 虚拟机栈)
  • Native MethodStack(本地方法栈)
  • Heap(堆)
  • Program Counter Regster(程序计数器)

从下图中看出方法区和堆用黄色标记,和其他三个区域的不同点就是,方法区和堆是线程共享的,所有的运行在jvm上的程序都能访问这两个区域,堆,方法区和虚拟机的生命周期一样,随着虚拟机的启动而存在,而栈和程序计数器是依赖用户线程的启动和结束而建立和销毁。

image.png

  • Program Counter Regster(程序计数器):每一个用户线程对应一个程序计数器,用来指示当前线程所执行字节码的行号。由程序计数器给文字码解释器提供下一条要执行的字节码的的位置。根据jvm规范,这个区域不会发生内存溢出。
  • Java stack(java 虚拟机栈):这个区域是最容易出现内存异常的区域,每一个线程对应生成一个线程栈,线程每执行一个方法的时候,都会创建一个栈帧,用来存放方法的局部变量表,操作树栈,动态连接,方法入口。jvm规范对这个区域定义了两种内存异常。
    • 如果虚拟机在扩展栈时无法申请到足够的内存空间则抛出OutOfMemoryError
    • 如果线程请求的栈深度大于虚拟机所允许的最大深度,将会抛出StackOverflowError
  • Native MethodStack(本地方法栈):和虚拟机栈一样,不同的是处理的对象不一样,虚拟机栈处理java的字节码,而本地栈则是处理的Native方法。其他方面一致。
  • Heap(堆):前面说了堆是所有线程都能访问的,随着虚拟机的启动而存在,这块区域很大,因为所有的线程都在这个区域保存实例化的对象,因为每一个类型中,每个接口实现类需要的内存不一样,一个方法内的多个分支需要的内存也不尽相同,我们只有在运行的时候才能知道要创建多少对象,需要分配多大的地址空间。GC关注的正是这样的部分内容,所以很多时候也将堆称为GC堆。堆中肯定不会抛出StackOverflowError类型的异常,所以只有OutOfMemoryError相关类型的异常。
  • Method Area(方法区):用于存放已被虚拟机加载的类信息,常量,静态方法,即使编译后的代码。由于早期的 Hotspot JVM 实现,很多人习惯于将方法区称为永久代(Permanent Generation)。这个区域只能抛出OutOfMemoryError类型的错误,OutOfMemoryError: PermGen space。
  • OutOfMemoryError的类型及解决方案
    在发生OOM后需要重点排查以下几点:
  • 检查代码中是否有死循环或递归调用。
  • 检查是否有大循环重复产生新对象实体。

检查对数据库查询中,是否有一次获得全部数据的查询。一般来说,如果一次取十万条记录到内存,就可能引起内存溢出。这个问题比较隐蔽,在上线前,数据库中数据较少,不容易出问题,上线后,数据库中数据多了,一次查询就有可能引起内存溢出。因此对于数据库查询尽量采用分页的方式查询。

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

OutOfMemoryError: PermGen space

PermGen space的全称是Permanent Generation space,是指内存的永久保存区域。这块内存主要是被JVM存放Class和Meta信息的,Class在被Loader时就会被放到PermGen space中,它和存放类实例(Instance)的Heap区域不同,GC(Garbage Collection)不会在主程序运行期对PermGen space进行清理。

对于老版本的 Oracle JDK,因为永久代的大小是有限的,并且 JVM 对永久代垃圾回收(如,常量池回收、卸载不再需要的类型)非常不积极,所以当我们不断添加新类型的时候,永久代出现 OutOfMemoryError 也非常多见,尤其是在运行时存在大量动态类型生成的场合;类似 Intern 字符串缓存占用太多空间,也会导致 OOM 问题。对应的异常信息,会标记出来和永久代相关:“java.lang.OutOfMemoryError: PermGen space”。

解决方法:手动设置MaxPermSize大小修改TOMCAT_HOME/bin/catalina.sh

JAVA_OPTS="-server -XX:PermSize=64M -XX:MaxPermSize=128m"

OutOfMemoryError:Java heap space

发生在堆内存上的内存溢出。原因可能有很多种,例如,可能存在内存泄漏问题;也很有可能就是堆的大小不合理,比如我们要处理比较可观的数据量,但是没有显式指定 JVM 堆大小或者指定数值偏小;或者出现 JVM 处理引用不及时,导致堆积起来,内存无法释放等。

解决方案:增加jvm的内存大小。其中"-Xms128M"为初始内存,"-Xmx256M"为最大内存。

-Xmx2048m -Xms2048m

最后重要提示:

但是,对于内存泄漏问题,无法通过设置启动参数的方式来解决,这种情况下增加堆内存大小只会延缓OOM的出现时间,治标不治本。也不推荐一开始就将堆内存大小设置的很大,这样会掩盖测试期间可能出现的问题,导致线上问题的出现。

对于这种情况,我们应该对程序中可能出现内存泄漏的地方进行优化。主要包括避免死循环,应该及时释放种资源:内存, 数据库的各种连接,防止一次载入太多的数据。导致java.lang.OutOfMemoryError的根本原因是程序不健壮。因此,从根本上解决Java内存溢出的唯一方法就是修改程序,及时地释放没用的对象,释放内存空间。遇到该错误的时候要仔细检查程序。

原文链接

码农架构-公众号.jpg

相关文章
|
运维 监控 Java
内存溢出+CPU占用过高:问题排查+解决方案+复盘(超详细分析教程)
全网最全的内存溢出CPU占用过高排查文章,包含:问题出现现象+临时解决方案+复现问题+定位问题发生原因+优化代码+优化后进行压测,上线+复盘
2476 5
|
18天前
|
运维 监控 Java
为何内存不够用?微服务改造启动多个Spring Boot的陷阱与解决方案
本文记录并复盘了生产环境中Spring Boot应用内存占用过高的问题及解决过程。系统上线初期运行正常,但随着业务量上升,多个Spring Boot应用共占用了64G内存中的大部分,导致应用假死。通过jps和jmap工具排查发现,原因是运维人员未设置JVM参数,导致默认配置下每个应用占用近12G内存。最终通过调整JVM参数、优化堆内存大小等措施解决了问题。建议在生产环境中合理设置JVM参数,避免资源浪费和性能问题。
50 3
|
2月前
|
存储 架构师 Java
内存溢出原因与解决方案(4大主流方案详解)
本文详解内存溢出(OOM)的原因及解决方案。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
内存溢出原因与解决方案(4大主流方案详解)
|
5月前
|
Web App开发 缓存 JavaScript
技术分享:深入探索内存泄漏——识别、分类与解决方案
【8月更文挑战第27天】在软件开发的浩瀚星海中,内存管理始终是程序员们必须面对的重要课题。内存泄漏,作为内存管理不善的典型症状,不仅影响应用性能,还可能导致系统崩溃,是每位开发者都需警惕的“暗礁”。本文将带您深入探索内存泄漏的本质、常见类型及有效的解决策略,助力您的工作学习之旅更加顺畅。
61 0
|
7月前
|
存储 缓存 NoSQL
Redis是一种高性能的内存数据库,常用于高并发环境下的缓存解决方案
【6月更文挑战第18天】**Redis摘要:** 高性能内存数据库,擅长高并发缓存。数据存内存,访问迅速;支持字符串、列表等多元数据类型;具备持久化防止数据丢失;丰富命令集便于操作;通过节点集群实现数据分片与负载均衡,增强可用性和扩展性。理想的缓存解决方案。
94 1
|
6月前
|
设计模式 安全 Java
Java面试题:请列举三种常用的设计模式,并分别给出在Java中的应用场景?请分析Java内存管理中的主要问题,并提出相应的优化策略?请简述Java多线程编程中的常见问题,并给出解决方案
Java面试题:请列举三种常用的设计模式,并分别给出在Java中的应用场景?请分析Java内存管理中的主要问题,并提出相应的优化策略?请简述Java多线程编程中的常见问题,并给出解决方案
135 0
|
6月前
|
监控
LabVIEW程序内存泄漏分析与解决方案
LabVIEW程序内存泄漏分析与解决方案
196 0
|
8月前
|
Java
Handler内存泄漏原因及解决方案
Handler内存泄漏原因及解决方案
76 0
|
8月前
|
存储 监控 Java
JVM内存泄漏的分析与解决方案
JVM内存泄漏的分析与解决方案
130 2
|
8月前
|
存储 缓存 监控
Linux系统内存下降:原因、诊断与解决方案
Linux系统内存下降:原因、诊断与解决方案
188 0