在Java后端开发中,java.lang.OutOfMemoryError(简称OOM)是一个令开发者头疼的异常。它通常意味着Java虚拟机(JVM)在尝试分配新对象时,发现堆中没有足够的空间来容纳该对象,或者其他内存区域耗尽。OOM不仅会导致应用程序崩溃,还会影响系统的稳定性和可用性。

一、JVM内存区域概述

在深入探讨OOM之前,我们首先回顾一下JVM的运行时数据区域,因为不同区域的内存溢出对应着不同类型的OOM。

在这里插入图片描述

JVM内存主要分为以下几个区域:

  • 程序计数器(Program Counter Register):一块较小的内存空间,用于存储当前线程所执行的字节码的行号指示器。它是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。
  • Java虚拟机栈(Java Virtual Machine Stacks):每个线程私有的内存区域,用于存储栈帧,每个栈帧包含局部变量表、操作数栈、动态链接、方法出口等信息。当线程请求的栈深度大于虚拟机所允许的深度时,将抛出StackOverflowError;如果虚拟机栈可以动态扩展,当扩展时无法申请到足够的内存时,将抛出OutOfMemoryError
  • 本地方法栈(Native Method Stacks):与虚拟机栈类似,为虚拟机使用到的Native方法服务。同样可能抛出StackOverflowErrorOutOfMemoryError
  • Java堆(Java Heap):JVM管理的最大一块内存,被所有线程共享,用于存放对象实例和数组。这是垃圾收集器管理的主要区域。当堆中没有内存完成实例分配,并且堆也无法再扩展时,将抛出OutOfMemoryError: Java heap space
  • 方法区(Method Area):用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。在JDK 8之前,方法区通常被称为“永久代”(PermGen Space),在JDK 8之后,永久代被元空间(Metaspace)取代,元空间使用的是本地内存。当方法区无法满足内存分配需求时,将抛出OutOfMemoryError: PermGen space(JDK 7及以前)或OutOfMemoryError: Metaspace(JDK 8及以后)。

理解这些内存区域的职责,有助于我们更准确地定位OOM的发生位置和原因。

二、常见OutOfMemoryError类型及原因

OutOfMemoryError有多种类型,每种类型都对应着不同的内存区域耗尽或特定的内存问题。以下是几种常见的OOM类型及其原因:

1. Java heap space

这是最常见也是最经典的OOM类型,表示Java堆内存不足。

常见原因:

  • 内存泄漏(Memory Leak):应用程序中存在大量对象引用未被释放,导致垃圾回收器无法回收这些对象所占用的内存。例如,集合类对象(如ArrayListHashMap)持续添加元素但未及时清理,或者资源(如文件流、数据库连接)未正确关闭。
  • 大对象分配:尝试创建过大的对象,例如一个非常大的数组或集合,超出了当前堆的可用空间。即使堆内存总量足够,如果单个对象过大,也可能导致OOM。
  • 内存溢出(Memory Overflow):代码中存在逻辑错误,导致在短时间内创建了大量对象,迅速耗尽了堆内存。例如,循环中不断创建新对象,或者递归调用没有终止条件。
  • 堆内存设置过小:JVM启动参数中设置的堆内存(-Xmx)过小,无法满足应用程序的运行需求。
  • 不合理的缓存:应用程序使用了缓存,但缓存策略不合理,导致缓存中的对象越来越多,最终耗尽内存。

2. PermGen space(JDK 7及以前) / Metaspace (JDK 8及以后)

这两种OOM表示方法区内存不足。

常见原因:

  • 加载大量类:应用程序加载了大量的类,例如动态生成代理类、大量使用反射、或者在Web服务器中频繁部署和卸载应用(导致类加载器泄漏)。
  • 常量池溢出:在JDK 7之前,String.intern()方法使用不当,可能导致永久代中的字符串常量池溢出。
  • 方法区设置过小:JVM启动参数中设置的永久代(-XX:MaxPermSize)或元空间(-XX:MaxMetaspaceSize)过小。

3. GC overhead limit exceeded

这个错误是JDK 6引入的一种OOM类型,表示垃圾回收器在进行大量回收工作,但效果甚微。

常见原因:

  • 频繁GC但回收效率低:当JVM花费98%以上的时间进行垃圾回收,但回收的堆空间却不足2%时,就会抛出此错误。这通常发生在应用程序的内存使用量接近堆内存上限,并且存在大量“活”对象,导致GC无法有效释放内存。
  • 内存泄漏:与Java heap space类似,内存泄漏也可能导致GC频繁且效率低下。
  • 堆内存设置过小:堆内存设置过小,导致GC频繁触发,且每次回收的内存有限。

4. unable to create new native Thread

这个错误表示JVM无法创建新的本地线程。

常见原因:

  • 创建大量线程:应用程序创建了过多的线程,超出了操作系统或JVM的限制。每个线程都需要占用一定的内存(包括Java栈和本地栈),过多的线程会耗尽系统内存。
  • 系统资源限制:操作系统对单个进程可创建的线程数有限制。例如,Linux系统中的/proc/sys/kernel/pid_max/proc/sys/kernel/thread-maxulimit -u等参数会影响线程创建。
  • 栈内存设置过大:通过-Xss参数设置的每个线程栈内存过大,导致在创建大量线程时迅速耗尽内存。

5. Requested array size exceeds VM limit

这个错误表示尝试分配的数组大小超出了JVM的限制。

常见原因:

  • 不合理的超大数组分配:代码中尝试创建了一个理论上非常大的数组,其大小超出了JVM所能寻址的最大范围。这通常是由于编程错误或对数据量预估不足导致的。

6. Out of swap space

这个错误表示操作系统层面的交换空间(swap space)不足。

常见原因:

  • 物理内存不足:应用程序或系统中的其他进程消耗了大量的物理内存,导致操作系统不得不频繁使用交换空间,最终耗尽交换空间。
  • 交换空间设置过小:操作系统配置的交换空间大小不足以应对当前系统的内存压力。

7. stack_trace_with_native_method

这个错误通常表示在本地方法(Native Method)执行过程中发生了内存分配失败。

常见原因:

  • JNI代码或本地库问题:应用程序通过JNI(Java Native Interface)调用本地代码,而本地代码在执行过程中申请内存失败。这通常与C/C++等本地语言编写的库有关,排查难度较大。

三、OutOfMemoryError排查解决方案

当应用程序发生OOM时,我们需要一套系统的排查方法来定位问题并解决它。以下是通用的排查步骤和解决方案:

1. 收集OOM信息

  • 查看错误日志:OOM发生时,JVM会在控制台或日志文件中打印详细的错误信息,包括OOM的类型、发生位置(堆、栈、方法区等)以及一些提示信息。这是排查问题的第一手资料。
  • 配置JVM参数生成Heap Dump:在JVM启动参数中添加-XX:+HeapDumpOnOutOfMemoryError-XX:HeapDumpPath=/path/to/heapdump.hprof,可以在OOM发生时自动生成堆内存快照(Heap Dump)文件。这个文件包含了OOM发生时堆中所有对象的信息,是分析内存泄漏和内存溢出的关键。
  • 配置GC日志:添加-XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/path/to/gc.log等参数,可以打印详细的GC日志。通过分析GC日志,可以了解GC的频率、耗时、回收效果等,判断是否存在GC问题。

2. 分析Heap Dump文件

Heap Dump文件是排查OOM最重要的工具。我们可以使用专业的内存分析工具来打开和分析它。

  • Eclipse Memory Analyzer Tool (MAT):MAT是一个功能强大的Java堆内存分析工具,可以帮助我们快速定位内存泄漏、大对象以及不合理的内存使用模式。通过MAT,我们可以:
    • 分析支配树(Dominator Tree):找出占用内存最多的对象,通常是内存泄漏的“根源”。
    • 查找内存泄漏嫌疑(Leak Suspects):MAT会自动分析并给出内存泄漏的嫌疑报告。
    • 查看对象引用链:分析对象的引用关系,找出哪些对象阻止了垃圾回收。
    • 比较Heap Dump:如果能获取到OOM发生前后的多个Heap Dump文件,可以通过比较它们来发现内存增长的趋势和新增的大对象。
  • VisualVM:VisualVM是一个集成了多种JVM工具的图形化工具,可以用于监控、分析和诊断Java应用程序。它也支持加载和分析Heap Dump文件,并提供实时的内存、CPU、线程等监控功能。

3. 定位和解决问题

根据OOM类型和Heap Dump分析结果,采取相应的解决方案:

3.1 针对Java heap space
  • 优化代码,避免内存泄漏
    • 及时释放资源:确保文件流、数据库连接、网络连接等资源在使用完毕后及时关闭。
    • 清理集合对象:对于长期存活的集合(如缓存、监听器列表),定期清理不再需要的对象。
    • 弱引用/软引用:对于缓存等场景,可以考虑使用WeakHashMapSoftReference来存储对象,让GC在内存不足时优先回收。
    • 避免内部类持有外部类引用:非静态内部类会隐式持有外部类的引用,可能导致外部类无法被回收。
  • 检查大对象分配
    • 审查代码:检查是否存在创建超大数组或集合的代码,如果确实需要处理大量数据,考虑分批处理或使用流式处理。
    • 调整数据结构:选择更节省内存的数据结构。
  • 调整JVM堆内存参数
    • 增大堆内存:根据应用程序的实际内存使用情况,适当增大-Xmx-Xms参数的值。但并非越大越好,过大的堆内存可能导致GC停顿时间过长。
    • 合理设置新生代和老年代比例:通过-XX:NewRatio-Xmn参数调整新生代大小,影响GC的频率和效率。
3.2 针对PermGen space / Metaspace
  • 优化类加载
    • 减少不必要的类加载:避免在运行时动态生成过多不必要的类。
    • 清理Web应用:在Web服务器中,确保每次部署新版本时,旧版本的类加载器能够完全卸载,避免类加载器泄漏。
  • 调整方法区内存参数
    • 增大永久代/元空间:适当增大-XX:MaxPermSize(JDK 7及以前)或-XX:MaxMetaspaceSize(JDK 8及以后)的值。
3.3 针对GC overhead limit exceeded
  • 优化代码,减少对象创建:减少不必要的对象创建,复用对象,避免在循环中频繁创建临时对象。
  • 调整GC策略:根据应用程序的特点,选择合适的垃圾回收器(如G1、CMS等),并调整相关参数,以优化GC性能。
  • 增大堆内存:如果GC频繁且效率低下,可能是堆内存确实不足,适当增大堆内存可能缓解问题。
  • 禁用GC开销限制(不推荐):通过-XX:-UseGCOverheadLimit可以禁用此限制,但这样做只是延迟了OOM的发生,最终还是会以Java heap space的形式出现,并不能解决根本问题。
3.4 针对unable to create new native Thread
  • 减少线程创建
    • 使用线程池:合理使用线程池来管理和复用线程,避免频繁创建和销毁线程。
    • 检查业务逻辑:审查代码,看是否存在不必要的线程创建,或者线程创建后未及时关闭。
  • 调整系统参数
    • 增大操作系统线程限制:根据需要调整Linux系统中的ulimit -u/proc/sys/kernel/pid_max/proc/sys/kernel/thread-max等参数。
  • 调整JVM栈内存参数
    • 减小线程栈大小:适当减小-Xss参数的值,但要注意过小的栈可能导致StackOverflowError
3.5 针对Requested array size exceeds VM limit
  • 审查代码:仔细检查代码中所有数组的创建,特别是那些大小由动态计算或用户输入决定的数组。确保数组大小在合理范围内,并进行边界检查。
  • 分批处理或流式处理:如果需要处理的数据量确实很大,考虑将数据分批加载和处理,或者使用流式处理方式,避免一次性将所有数据加载到内存中。
3.6 针对Out of swap space
  • 增加物理内存:这是最直接有效的解决方案。
  • 增大交换空间:在操作系统层面增加交换空间的大小。
  • 检查其他进程:查看系统上是否有其他进程占用了大量内存,如果可以,尝试优化或迁移这些进程。
3.7 针对stack_trace_with_native_method
  • 排查本地代码:这通常需要具备本地代码(C/C++)的调试能力,使用操作系统提供的工具(如stracelsofgdb等)来分析本地方法的内存使用情况。
  • 更新或替换本地库:如果问题出在第三方本地库,尝试更新到最新版本或寻找替代方案。

四、示例代码与JVM参数配置

为了更好地理解和排查OOM,以下提供一些示例代码和常用的JVM参数配置。

1. Java heap space 示例

以下是一个简单的Java代码示例,可能导致 java.lang.OutOfMemoryError: Java heap space

import java.util.ArrayList;
import java.util.List;public class OOMHeapSpace {public static void main(String[] args) {List<Object> list = new ArrayList<>();while (true) {list.add(new Object()); // 不断创建新对象并添加到列表中,导致内存泄漏}}
}

运行上述代码时,如果JVM堆内存设置较小,很快就会出现 OutOfMemoryError: Java heap space

2. JVM参数配置示例

以下是一些常用的JVM参数配置,用于OOM的排查和内存调优:

  • 设置堆内存大小

    -Xms512m -Xmx1024m
    
    • -Xms:设置JVM初始堆内存为512MB。
    • -Xmx:设置JVM最大堆内存为1024MB。
  • OOM时生成Heap Dump文件

    -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/data/heapdump.hprof
    
    • -XX:+HeapDumpOnOutOfMemoryError:当发生OOM时,自动生成Heap Dump文件。
    • -XX:HeapDumpPath:指定Heap Dump文件的保存路径。
  • 打印GC详细日志

    -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/data/gc.log
    
    • -XX:+PrintGCDetails:打印详细的GC日志。
    • -XX:+PrintGCDateStamps:在GC日志中打印时间戳。
    • -Xloggc:指定GC日志的保存路径。
  • 设置元空间大小(JDK 8及以后)

    -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=256m
    
    • -XX:MetaspaceSize:设置元空间初始大小为128MB。
    • -XX:MaxMetaspaceSize:设置元空间最大大小为256MB。

通过合理配置这些参数,可以帮助我们更好地监控和诊断JVM的内存问题。

总结

OutOfMemoryError是Java应用程序中常见的性能问题,但通过系统的排查方法和对JVM内存模型的深入理解,我们可以有效地定位并解决这些问题。关键在于:

  • 预防为主:在开发阶段就注意编写高质量的代码,避免内存泄漏和不合理的大对象分配。
  • 监控先行:在生产环境中,持续监控JVM的内存使用情况和GC行为,及时发现潜在的内存问题。
  • 工具辅助:熟练使用jmapjstackJConsoleVisualVMMAT等工具进行问题诊断。
  • 参数调优:根据应用程序的特点和实际运行情况,合理调整JVM启动参数,优化内存分配和垃圾回收策略。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/86373.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/86373.shtml
英文地址,请注明出处:http://en.pswp.cn/web/86373.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

吐槽之前后端合作开发

大家好&#xff0c;我是佳瑞&#xff0c;从事10多年java开发程序员&#xff0c;爆照一张&#xff0c;存活互联网。 也做过vue开发自己的网站&#xff0c;觉得前端是真比后端开发轻松很多&#xff0c;就是画页面调样式&#xff0c;打包发布&#xff0c;当然不说是高级源码修改…

Oracle LogMiner日志分析工具介绍

Oracle LogMiner日志分析工具介绍 LogMiner使用须知LogMiner字典使用online catalog作为日志挖掘字典使用redo日志文件作为日志挖掘字典使用文本文件作为日志挖掘字典Redo日志文件自动获取日志文件手动获取日志文件启动LogMiner进行分析V$LOGMNR_CONTENTS视图LogMiner使用须知 …

2-4 Dockerfile指令(个人笔记)

以下指令基于 ubuntu Dockerfile整体示例 From&#xff1a;设置基础镜像 Maintainer &#xff1a;镜像维护者信息 COPY/ADD&#xff1a;添加本地文件到镜像中 WorkDir&#xff1a;设置工作目录 Run&#xff1a;执行命令 CMD/EntryPoint&#xff1a;配置容器启动时执行的命令

Redis主从架构哨兵模式

文章目录 概述一、主从搭建实例二、主从同步原理三、哨兵架构3.1、搭建哨兵架构3.2、演示故障恢复3.3、哨兵日志 概述 在生产环境下&#xff0c;Redis通常不会单机部署&#xff0c;为了保证高可用性&#xff0c;通常使用主从模式或集群架构&#xff0c;同时也面临着一些问题&am…

基于深度学习yolov5的安全帽实时识别检测系统

摘要&#xff1a;在现代工业和建筑行业中&#xff0c;确保员工的安全是至关重要的一环。安全帽作为一项基础的个人防护设备&#xff0c;对于降低头部受伤的风险发挥着关键作用。然而&#xff0c;确保工作人员在施工现场始终正确佩戴安全帽并非易事。传统的人工检查方法不仅效率…

GitLab 18.1 发布 Runner、无效的个人访问令牌查看等功能,可升级体验!

GitLab 是一个全球知名的一体化 DevOps 平台&#xff0c;很多人都通过私有化部署 GitLab 来进行源代码托管。极狐GitLab 是 GitLab 在中国的发行版&#xff0c;专门为中国程序员服务。可以一键式部署极狐GitLab。 学习极狐GitLab 的相关资料&#xff1a; 极狐GitLab 官网极狐…

量子计算与AI融合 - 企业级安全威胁应对

量子计算&#xff08;QC&#xff09;虽带来万亿级市场机遇&#xff08;2025-2035年&#xff09;&#xff0c;但潜藏重大安全风险&#xff1a;可能破解现有加密系统&#xff0c;催生"现在窃取&#xff0c;未来解密"攻击。美国NIST已启动后量子加密标准&#xff0c;但技…

Excel:filter函数实现动态筛选的方法

filter的意思是“过滤、筛选”&#xff0c;动态筛选&#xff0c;FILTER()函数可以将对筛选区域内容&#xff0c;并将结果自动溢出生成一个新区域&#xff0c;以下是函数的使用方法&#xff1a; &#xff08;一&#xff09;情景&#xff1a;给定两列数据&#xff0c;我需要根据…

兰洋科技上合组织论坛发表专题分享,全球液冷布局引领绿色算力未来

2025年6月17-19日&#xff0c;中国—上海合作组织数字技术合作发展论坛在新疆克拉玛依市举办。作为第四次上海合作组织成员国信息通信技术发展部门负责人会议的配套会议&#xff0c;论坛以“数字化转型助力可持续发展&#xff0c;数字包容促进上合共同繁荣”为主题&#xff0c;…

LED-Merging: 无需训练的模型合并框架,兼顾LLM安全和性能!!

摘要&#xff1a;对预训练大型语言模型&#xff08;LLMs&#xff09;进行微调以适应特定任务&#xff0c;会带来巨大的计算和数据成本。虽然模型合并提供了一种无需训练的解决方案&#xff0c;用于整合多个特定任务的模型&#xff0c;但现有方法存在安全性与效用性之间的冲突&a…

火山引擎向量数据库 Milvus 版正式开放

资料来源&#xff1a;火山引擎-开发者社区 随着AI技术的不断演进发展&#xff0c;非结构化数据也迎来了爆发式的增长。Milvus作为一款为大规模向量相似度搜索和 AI 应用开发设计的开源向量数据库系统&#xff0c;目前已在业界占据领导地位。当前 Milvus 已经被 5,000 家企业所…

SQL SERVER存储过程

什么是存储过程 SQL 存储过程&#xff08;Stored Procedure&#xff09;是一个在数据库中预编译并存储的一组 SQL 语句。它们可以包含查询、插入、更新、删除等数据库操作&#xff0c;甚至包括控制流语句&#xff08;如条件判断、循环等&#xff09;。存储过程可以通过调用来执…

Lombok注解 - 提高Java开发效率

01 繁琐编码 初入 Java 开发领域时&#xff0c;编写实体类的琐碎经历想必各位都深有感触。 每当创建一个实体类&#xff0c;铺天盖地的 getter、setter、toString 方法接踵而至&#xff0c;手指在键盘上频繁敲击&#xff0c;酸痛不已。 而 Lombok 这一神器的出现&#xff0c…

Linux修改uboot启动延时方法详细攻略,触觉智能RK3568开发板演示

修改uboot延时 首先查找defconfig文件 ./build.sh uboot #通过编译日志查看使用的defconfig文件ls u-boot/configs/*3568* #在SDK根目录下执行该操作 如图标注处就是所使用的u-boot配置文件。 然后修改延时数&#xff1a; vim u-boot/configs/rk3568_defconfig 将CONFIG_BOO…

dockers virbox 安装

sudo apt remove docker docker-engine docker.io containerd runc 更新包索引并安装依赖 sudo apt update sudo apt install ca-certificates curl gnupg 添加Docker官方GPG密钥 sudo install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux…

Restormer: Efficient Transformer for High-Resolution Image Restoration 论文阅读

题目 (Title): Restormer&#xff1a;用于高分辨率图像恢复的高效Transformer 摘要 (Abstract): 由于卷积神经网络&#xff08;CNN&#xff09;在从大规模数据中学习可泛化的图像先验方面表现出色&#xff0c;这些模型已被广泛应用于图像恢复及相关任务。最近&#xff0c;另一…

音视频开发协议栈全景解析

音视频开发协议栈全景解析 引言&#xff1a;协议栈的重要性与演进 在当今数字化时代&#xff0c;音视频技术已成为互联网基础设施的核心组成部分。从视频会议、直播到智能安防、元宇宙应用&#xff0c;音视频协议栈的设计直接影响着用户体验质量(QoE)。作为开发者&#xff0c…

Java面试题025:一文深入了解数据库Redis(1)

欢迎大家关注我的JAVA面试题专栏,该专栏会持续更新,从原理角度覆盖Java知识体系的方方面面。 一文吃透JAVA知识体系(面试题)https://bl

Python:调用json.dumps处理datetime对象数据

文章目录 前言一、查询SQL语句中数据转换1、思路2、示例3、常用格式化模式4、注意事项 二、自定义JSONEncoder处理1、思路2、示例3、使用方法 写在结尾 前言 使用Python开发查询PostgreSQL数据库&#xff0c;返回数据中有timestamp类型数据字段。如果使用json.dumps转换成json对…

QT6 源(130)视图模型架构中的字符串列表模型 QStringListModel:成员函数,本类的继承关系图以及源码注释

&#xff08;1&#xff09;字符串列表型的 model &#xff0c;可以交给视图 view 来显示&#xff0c;也可以由组合框 comboBox 读取其中的内容 &#xff1a; &#xff08;2&#xff09;以下开始学习本字符串 model 里的成员函数&#xff0c;本类没有再定义信号与槽函数 &#x…